v0.9.26
This commit is contained in:
@@ -14,7 +14,7 @@ Classes:
|
|||||||
MainWindow: The main application window containing the thumbnail grid and docks.
|
MainWindow: The main application window containing the thumbnail grid and docks.
|
||||||
"""
|
"""
|
||||||
__appname__ = "BagheeraView"
|
__appname__ = "BagheeraView"
|
||||||
__version__ = "0.9.25"
|
__version__ = "0.9.26"
|
||||||
__author__ = "Ignacio Serantes"
|
__author__ = "Ignacio Serantes"
|
||||||
__email__ = "kde@aynoa.net"
|
__email__ = "kde@aynoa.net"
|
||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
@@ -1768,13 +1768,18 @@ class MainWindow(QMainWindow):
|
|||||||
UITexts.MENU_DETECT_CURRENT_SEARCH)
|
UITexts.MENU_DETECT_CURRENT_SEARCH)
|
||||||
detect_current_action.triggered.connect(self.start_duplicate_detection)
|
detect_current_action.triggered.connect(self.start_duplicate_detection)
|
||||||
|
|
||||||
detect_all_action = duplicates_menu.addAction(UITexts.MENU_DETECT_ALL)
|
|
||||||
detect_all_action.triggered.connect(self.detect_all_duplicates)
|
|
||||||
|
|
||||||
force_full_action = duplicates_menu.addAction(UITexts.MENU_FORCE_FULL_ANALYSIS)
|
force_full_action = duplicates_menu.addAction(UITexts.MENU_FORCE_FULL_ANALYSIS)
|
||||||
force_full_action.triggered.connect(
|
force_full_action.triggered.connect(
|
||||||
lambda: self.start_duplicate_detection(force_full=True))
|
lambda: self.start_duplicate_detection(force_full=True))
|
||||||
|
|
||||||
|
detect_all_action = duplicates_menu.addAction(UITexts.MENU_DETECT_ALL)
|
||||||
|
detect_all_action.triggered.connect(self.detect_all_duplicates)
|
||||||
|
|
||||||
|
force_full_all_action = duplicates_menu.addAction(
|
||||||
|
UITexts.MENU_FORCE_FULL_ALL_ANALYSIS)
|
||||||
|
force_full_all_action.triggered.connect(
|
||||||
|
lambda: self.detect_all_duplicates(force_full=True))
|
||||||
|
|
||||||
review_ignored_action = duplicates_menu.addAction(UITexts.MENU_REVIEW_IGNORED)
|
review_ignored_action = duplicates_menu.addAction(UITexts.MENU_REVIEW_IGNORED)
|
||||||
review_ignored_action.triggered.connect(self.review_ignored_duplicates)
|
review_ignored_action.triggered.connect(self.review_ignored_duplicates)
|
||||||
|
|
||||||
@@ -1784,6 +1789,13 @@ class MainWindow(QMainWindow):
|
|||||||
QIcon.fromTheme("edit-clear-all"), UITexts.MENU_CLEAN_UP_HASHES)
|
QIcon.fromTheme("edit-clear-all"), UITexts.MENU_CLEAN_UP_HASHES)
|
||||||
clean_hashes_action.triggered.connect(self.clean_duplicate_hashes)
|
clean_hashes_action.triggered.connect(self.clean_duplicate_hashes)
|
||||||
|
|
||||||
|
repair_index_action = duplicates_menu.addAction(UITexts.MENU_REPAIR_DATABASE)
|
||||||
|
repair_index_action.triggered.connect(self.repair_duplicate_index)
|
||||||
|
|
||||||
|
clear_exceptions_action = duplicates_menu.addAction(
|
||||||
|
UITexts.MENU_CLEAR_EXCEPTIONS)
|
||||||
|
clear_exceptions_action.triggered.connect(self.clear_ignored_duplicates)
|
||||||
|
|
||||||
if self.duplicate_cache:
|
if self.duplicate_cache:
|
||||||
count, size_bytes = self.duplicate_cache.get_hash_stats()
|
count, size_bytes = self.duplicate_cache.get_hash_stats()
|
||||||
size_mb = size_bytes / (1024 * 1024)
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
@@ -1828,7 +1840,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
menu.exec(self.menu_btn.mapToGlobal(QPoint(0, self.menu_btn.height())))
|
menu.exec(self.menu_btn.mapToGlobal(QPoint(0, self.menu_btn.height())))
|
||||||
|
|
||||||
def detect_all_duplicates(self):
|
def detect_all_duplicates(self, force_full=False):
|
||||||
"""Gathers files from whitelist (respecting blacklist) and runs detector."""
|
"""Gathers files from whitelist (respecting blacklist) and runs detector."""
|
||||||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||||
try:
|
try:
|
||||||
@@ -1849,7 +1861,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# By default, we use optimized (incremental) mode to avoid repeating
|
# By default, we use optimized (incremental) mode to avoid repeating
|
||||||
# comparisons.
|
# comparisons.
|
||||||
self.start_duplicate_detection(force_full=False, custom_paths=paths)
|
self.start_duplicate_detection(force_full=force_full, custom_paths=paths)
|
||||||
|
|
||||||
def _gather_files_for_duplicates(self):
|
def _gather_files_for_duplicates(self):
|
||||||
"""Helper to collect image paths based on whitelist and blacklist settings."""
|
"""Helper to collect image paths based on whitelist and blacklist settings."""
|
||||||
@@ -1892,6 +1904,40 @@ class MainWindow(QMainWindow):
|
|||||||
count = self.duplicate_cache.clean_stale_hashes()
|
count = self.duplicate_cache.clean_stale_hashes()
|
||||||
self.status_lbl.setText(f"Cleaned up {count} stale hash entries.")
|
self.status_lbl.setText(f"Cleaned up {count} stale hash entries.")
|
||||||
|
|
||||||
|
def repair_duplicate_index(self):
|
||||||
|
"""Regenerates the BK-Tree and reverse index."""
|
||||||
|
if not self.duplicate_cache:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.duplicate_detector and self.duplicate_detector.isRunning():
|
||||||
|
QMessageBox.information(self, UITexts.DUPLICATE_DETECTION_TITLE,
|
||||||
|
UITexts.DUPLICATE_ALREADY_RUNNING)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.status_lbl.setText(UITexts.REPAIRING_DATABASE)
|
||||||
|
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||||
|
try:
|
||||||
|
self.duplicate_cache.regenerate_bktree()
|
||||||
|
self.status_lbl.setText(UITexts.READY)
|
||||||
|
finally:
|
||||||
|
QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
|
def clear_ignored_duplicates(self):
|
||||||
|
"""Clears the ignored pairs database after user confirmation."""
|
||||||
|
if not self.duplicate_cache:
|
||||||
|
return
|
||||||
|
|
||||||
|
confirm = QMessageBox(self)
|
||||||
|
confirm.setIcon(QMessageBox.Question)
|
||||||
|
confirm.setWindowTitle(UITexts.CONFIRM_CLEAR_EXCEPTIONS_TITLE)
|
||||||
|
confirm.setText(UITexts.CONFIRM_CLEAR_EXCEPTIONS_TEXT)
|
||||||
|
confirm.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||||
|
confirm.setDefaultButton(QMessageBox.No)
|
||||||
|
|
||||||
|
if confirm.exec() == QMessageBox.Yes:
|
||||||
|
self.duplicate_cache.clear_exceptions()
|
||||||
|
self.status_lbl.setText(UITexts.READY)
|
||||||
|
|
||||||
def clear_duplicate_hashes(self):
|
def clear_duplicate_hashes(self):
|
||||||
if not self.duplicate_cache:
|
if not self.duplicate_cache:
|
||||||
return
|
return
|
||||||
|
|||||||
34
constants.py
34
constants.py
@@ -29,7 +29,7 @@ if FORCE_X11:
|
|||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
PROG_NAME = "Bagheera Image Viewer"
|
PROG_NAME = "Bagheera Image Viewer"
|
||||||
PROG_ID = "bagheeraview"
|
PROG_ID = "bagheeraview"
|
||||||
PROG_VERSION = "0.9.25"
|
PROG_VERSION = "0.9.26"
|
||||||
PROG_AUTHOR = "Ignacio Serantes"
|
PROG_AUTHOR = "Ignacio Serantes"
|
||||||
|
|
||||||
# --- CACHE SETTINGS ---
|
# --- CACHE SETTINGS ---
|
||||||
@@ -75,6 +75,8 @@ DUPLICATE_CACHE_PATH = os.path.join(APP_DATA_DIR, "duplicates")
|
|||||||
DUPLICATE_HASH_DB_NAME = b"hashes"
|
DUPLICATE_HASH_DB_NAME = b"hashes"
|
||||||
DUPLICATE_EXCEPTIONS_DB_NAME = b"exceptions"
|
DUPLICATE_EXCEPTIONS_DB_NAME = b"exceptions"
|
||||||
DUPLICATE_PENDING_DB_NAME = b"pending"
|
DUPLICATE_PENDING_DB_NAME = b"pending"
|
||||||
|
DUPLICATE_BKTREE_DB_NAME = b"bktree"
|
||||||
|
DUPLICATE_HASH_TO_FILES_DB_NAME = b"hash_to_files"
|
||||||
|
|
||||||
|
|
||||||
def save_app_config():
|
def save_app_config():
|
||||||
@@ -523,9 +525,16 @@ _UI_TEXTS = {
|
|||||||
"MENU_DUPLICATES": "Duplicates",
|
"MENU_DUPLICATES": "Duplicates",
|
||||||
"MENU_DETECT_CURRENT_SEARCH": "Detect in current search",
|
"MENU_DETECT_CURRENT_SEARCH": "Detect in current search",
|
||||||
"MENU_DETECT_ALL": "Detect all",
|
"MENU_DETECT_ALL": "Detect all",
|
||||||
|
"MENU_FORCE_FULL_ALL_ANALYSIS": "Force full all analysis",
|
||||||
"MENU_FORCE_FULL_ANALYSIS": "Force full analysis",
|
"MENU_FORCE_FULL_ANALYSIS": "Force full analysis",
|
||||||
"MENU_REVIEW_IGNORED": "Review ignored",
|
"MENU_REVIEW_IGNORED": "Review ignored",
|
||||||
"MENU_CLEAN_UP_HASHES": "Clean up",
|
"MENU_CLEAN_UP_HASHES": "Clean up",
|
||||||
|
"MENU_REPAIR_DATABASE": "Repair index",
|
||||||
|
"MENU_CLEAR_EXCEPTIONS": "Clear ignored pairs",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TITLE": "Confirm Clear Ignored Pairs",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TEXT": "Are you sure you want to clear all "
|
||||||
|
"ignored duplicate pairs? They will be detected again in the next scan.",
|
||||||
|
"REPAIRING_DATABASE": "Repairing duplicate index...",
|
||||||
"MENU_CLEAR_HASHES": "Clear hashes ({} items, {:.1f} MB on disk)",
|
"MENU_CLEAR_HASHES": "Clear hashes ({} items, {:.1f} MB on disk)",
|
||||||
"CONFIRM_CLEAR_HASHES_TITLE": "Confirm Clear Hashes",
|
"CONFIRM_CLEAR_HASHES_TITLE": "Confirm Clear Hashes",
|
||||||
"CONFIRM_CLEAR_HASHES_TEXT": "Are you sure you want to permanently delete "
|
"CONFIRM_CLEAR_HASHES_TEXT": "Are you sure you want to permanently delete "
|
||||||
@@ -786,7 +795,8 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
||||||
"FILE_RENAMED": "File renamed to {}",
|
"FILE_RENAMED": "File renamed to {}",
|
||||||
"ERROR_RENAME": "Could not rename file: {}",
|
"ERROR_RENAME": "Could not rename file: {}",
|
||||||
"ERROR_JPEG_METADATA_LIMIT": "Metadata size limit exceeded for '{}'. This JPEG file has too much existing metadata (XMP) to save more.",
|
"ERROR_JPEG_METADATA_LIMIT": "Metadata size limit exceeded for '{}'. This "
|
||||||
|
"JPEG file has too much existing metadata (XMP) to save more.",
|
||||||
"MAIN_DOCK_TITLE": "",
|
"MAIN_DOCK_TITLE": "",
|
||||||
"LAYOUTS_TAB": "Layouts",
|
"LAYOUTS_TAB": "Layouts",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
||||||
@@ -1067,9 +1077,16 @@ _UI_TEXTS = {
|
|||||||
"MENU_DUPLICATES": "Duplicados",
|
"MENU_DUPLICATES": "Duplicados",
|
||||||
"MENU_DETECT_CURRENT_SEARCH": "Detectar en búsqueda actual",
|
"MENU_DETECT_CURRENT_SEARCH": "Detectar en búsqueda actual",
|
||||||
"MENU_DETECT_ALL": "Detectar todos",
|
"MENU_DETECT_ALL": "Detectar todos",
|
||||||
|
"MENU_FORCE_FULL_ALL_ANALYSIS": "Forzar análisis completo de todo",
|
||||||
"MENU_FORCE_FULL_ANALYSIS": "Forzar análisis completo",
|
"MENU_FORCE_FULL_ANALYSIS": "Forzar análisis completo",
|
||||||
"MENU_REVIEW_IGNORED": "Revisar ignorados",
|
"MENU_REVIEW_IGNORED": "Revisar ignorados",
|
||||||
"MENU_CLEAN_UP_HASHES": "Limpiar",
|
"MENU_CLEAN_UP_HASHES": "Limpiar",
|
||||||
|
"MENU_REPAIR_DATABASE": "Reparar índice",
|
||||||
|
"MENU_CLEAR_EXCEPTIONS": "Limpiar parejas ignoradas",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TITLE": "Confirmar Limpieza de Ignorados",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TEXT": "¿Seguro que quieres borrar todas las parejas "
|
||||||
|
"de duplicados ignoradas? Se volverán a detectar en el próximo escaneo.",
|
||||||
|
"REPAIRING_DATABASE": "Reparando índice de duplicados...",
|
||||||
"MENU_CLEAR_HASHES": "Limpiar hashes ({} ítems, {:.1f} MB en disco)",
|
"MENU_CLEAR_HASHES": "Limpiar hashes ({} ítems, {:.1f} MB en disco)",
|
||||||
"CONFIRM_CLEAR_HASHES_TITLE": "Confirmar Limpieza de Hashes",
|
"CONFIRM_CLEAR_HASHES_TITLE": "Confirmar Limpieza de Hashes",
|
||||||
"CONFIRM_CLEAR_HASHES_TEXT": "¿Seguro que quieres eliminar permanentemente "
|
"CONFIRM_CLEAR_HASHES_TEXT": "¿Seguro que quieres eliminar permanentemente "
|
||||||
@@ -1340,7 +1357,8 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "El archivo '{}' ya existe.",
|
"RENAME_ERROR_EXISTS": "El archivo '{}' ya existe.",
|
||||||
"FILE_RENAMED": "Archivo renombrado a {}",
|
"FILE_RENAMED": "Archivo renombrado a {}",
|
||||||
"ERROR_RENAME": "No se pudo renombrar el archivo: {}",
|
"ERROR_RENAME": "No se pudo renombrar el archivo: {}",
|
||||||
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este archivo JPEG ya tiene demasiados metadatos (XMP) para guardar más.",
|
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este "
|
||||||
|
"archivo JPEG ya tiene demasiados metadatos (XMP) para guardar más.",
|
||||||
"MAIN_DOCK_TITLE": "Panel principal",
|
"MAIN_DOCK_TITLE": "Panel principal",
|
||||||
"LAYOUTS_TAB": "Diseños",
|
"LAYOUTS_TAB": "Diseños",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Nombre", "Última Modificación"],
|
"LAYOUTS_TABLE_HEADER": ["Nombre", "Última Modificación"],
|
||||||
@@ -1625,9 +1643,16 @@ _UI_TEXTS = {
|
|||||||
"MENU_DUPLICATES": "Duplicados",
|
"MENU_DUPLICATES": "Duplicados",
|
||||||
"MENU_DETECT_CURRENT_SEARCH": "Detectar na busca actual",
|
"MENU_DETECT_CURRENT_SEARCH": "Detectar na busca actual",
|
||||||
"MENU_DETECT_ALL": "Detectar todos",
|
"MENU_DETECT_ALL": "Detectar todos",
|
||||||
|
"MENU_FORCE_FULL_ALL_ANALYSIS": "Forzar análise completa de todo",
|
||||||
"MENU_FORCE_FULL_ANALYSIS": "Forzar análise completa",
|
"MENU_FORCE_FULL_ANALYSIS": "Forzar análise completa",
|
||||||
"MENU_REVIEW_IGNORED": "Revisar ignorados",
|
"MENU_REVIEW_IGNORED": "Revisar ignorados",
|
||||||
"MENU_CLEAN_UP_HASHES": "Limpar",
|
"MENU_CLEAN_UP_HASHES": "Limpar",
|
||||||
|
"MENU_REPAIR_DATABASE": "Reparar índice",
|
||||||
|
"MENU_CLEAR_EXCEPTIONS": "Limpar parellas ignoradas",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TITLE": "Confirmar Limpeza de Ignorados",
|
||||||
|
"CONFIRM_CLEAR_EXCEPTIONS_TEXT": "Seguro que queres borrar todas as parellas "
|
||||||
|
"de duplicados ignoradas? Volveranse detectar no vindeiro escaneo.",
|
||||||
|
"REPAIRING_DATABASE": "Reparando índice de duplicados...",
|
||||||
"MENU_CLEAR_HASHES": "Limpar hashes ({} elementos, {:.1f} MB en disco)",
|
"MENU_CLEAR_HASHES": "Limpar hashes ({} elementos, {:.1f} MB en disco)",
|
||||||
"CONFIRM_CLEAR_HASHES_TITLE": "Confirmar Limpeza de Hashes",
|
"CONFIRM_CLEAR_HASHES_TITLE": "Confirmar Limpeza de Hashes",
|
||||||
"CONFIRM_CLEAR_HASHES_TEXT": "Seguro que queres eliminar permanentemente toda "
|
"CONFIRM_CLEAR_HASHES_TEXT": "Seguro que queres eliminar permanentemente toda "
|
||||||
@@ -1896,7 +1921,8 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "O ficheiro '{}' xa existe.",
|
"RENAME_ERROR_EXISTS": "O ficheiro '{}' xa existe.",
|
||||||
"FILE_RENAMED": "Ficheiro renomeado a {}",
|
"FILE_RENAMED": "Ficheiro renomeado a {}",
|
||||||
"ERROR_RENAME": "Non se puido renomear o ficheiro: {}",
|
"ERROR_RENAME": "Non se puido renomear o ficheiro: {}",
|
||||||
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este ficheiro JPEG xa ten demasiados metadatos (XMP) para gardar máis.",
|
"ERROR_JPEG_METADATA_LIMIT": "Límite de metadatos excedido para '{}'. Este "
|
||||||
|
"ficheiro JPEG xa ten demasiados metadatos (XMP) para gardar máis.",
|
||||||
"MAIN_DOCK_TITLE": "Panel principal",
|
"MAIN_DOCK_TITLE": "Panel principal",
|
||||||
"LAYOUTS_TAB": "Deseños",
|
"LAYOUTS_TAB": "Deseños",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Nome", "Última Modificación"],
|
"LAYOUTS_TABLE_HEADER": ["Nome", "Última Modificación"],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,8 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
from utils import preserve_mtime
|
from utils import preserve_mtime
|
||||||
from constants import RATING_XATTR_NAME, XATTR_NAME
|
from constants import RATING_XATTR_NAME, XATTR_NAME, UITexts
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "bagheeraview"
|
name = "bagheeraview"
|
||||||
version = "0.9.25"
|
version = "0.9.26"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Ignacio Serantes" }
|
{ name = "Ignacio Serantes" }
|
||||||
]
|
]
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bagheeraview",
|
name="bagheeraview",
|
||||||
version="0.9.25",
|
version="0.9.26",
|
||||||
author="Ignacio Serantes",
|
author="Ignacio Serantes",
|
||||||
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
||||||
long_description="A fast image viewer built with PySide6, featuring search and "
|
long_description="A fast image viewer built with PySide6, featuring search and "
|
||||||
|
|||||||
Reference in New Issue
Block a user