Improve stability issues
This commit is contained in:
@@ -32,7 +32,7 @@ from PySide6.QtCore import (
|
||||
QObject, QThread, Signal, QMutex, QReadWriteLock, QSize, QSemaphore, QWaitCondition,
|
||||
QByteArray, QBuffer, QIODevice, Qt, QTimer, QRunnable, QThreadPool, QFile
|
||||
)
|
||||
from PySide6.QtGui import QImage, QImageReader, QImageIOHandler
|
||||
from PySide6.QtGui import QImage, QImageReader, QImageIOHandler, QIcon
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from constants import (
|
||||
@@ -134,11 +134,11 @@ class ScannerWorker(QRunnable):
|
||||
sizes_to_check = self.target_sizes if self.target_sizes is not None \
|
||||
else SCANNER_GENERATE_SIZES
|
||||
|
||||
fd = None
|
||||
try:
|
||||
if self._is_cancelled:
|
||||
return
|
||||
|
||||
fd = None
|
||||
# Optimize: Open file once to reuse FD for stat and xattrs
|
||||
fd = os.open(self.path, os.O_RDONLY)
|
||||
stat_res = os.fstat(fd)
|
||||
@@ -196,8 +196,11 @@ class ScannerWorker(QRunnable):
|
||||
tags, rating = res_meta.tags, res_meta.rating
|
||||
self.result = (self.path, smallest_thumb_for_signal,
|
||||
curr_mtime, tags, rating, curr_inode, curr_dev)
|
||||
except (FileNotFoundError, PermissionError) as e:
|
||||
logger.debug(f"Skipping {self.path} due to access issue: {e}")
|
||||
self.result = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing image {self.path}: {e}")
|
||||
logger.warning(f"Unexpected error processing image {self.path}: {e}")
|
||||
self.result = None
|
||||
finally:
|
||||
if fd is not None:
|
||||
@@ -265,7 +268,7 @@ def generate_thumbnail(path, size, fd=None):
|
||||
# better quality for upscaling.
|
||||
return img.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating thumbnail for {path}: {e}")
|
||||
logger.debug(f"Could not generate thumbnail for {path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -523,10 +526,19 @@ class ThumbnailCache(QObject):
|
||||
self._db_lock = QMutex() # Lock specifically for _db_handles access
|
||||
self._db_handles = {} # Cache for LMDB database handles (dbi)
|
||||
self._cancel_loading = False
|
||||
self._broken_cache = {} # (dev, inode, size) -> (mtime, error_msg)
|
||||
self._cache_bytes_size = 0
|
||||
self._cache_writer = None
|
||||
self._cache_loader = None
|
||||
|
||||
# Pre-generate broken images for standard tiers in the main thread
|
||||
self._broken_images = {}
|
||||
for size in THUMBNAIL_SIZES:
|
||||
icon = QIcon.fromTheme("image-missing",
|
||||
QIcon.fromTheme("broken-image",
|
||||
QIcon.fromTheme("dialog-error")))
|
||||
self._broken_images[size] = icon.pixmap(size, size).toImage()
|
||||
|
||||
self.lmdb_open()
|
||||
|
||||
def lmdb_open(self):
|
||||
@@ -739,6 +751,21 @@ class ThumbnailCache(QObject):
|
||||
return 256
|
||||
return 512
|
||||
|
||||
def mark_broken(self, path, size, mtime, inode, dev_id, error_msg):
|
||||
"""Marks a thumbnail load as failed with a message."""
|
||||
key = (dev_id, struct.pack('Q', inode) if inode is not None else None, size)
|
||||
with self._write_lock():
|
||||
self._broken_cache[key] = (mtime, error_msg)
|
||||
|
||||
def get_broken_info(self, path, size, mtime, inode, dev_id):
|
||||
"""Returns the error message if a thumbnail is known to have failed, else None."""
|
||||
key = (dev_id, struct.pack('Q', inode) if inode is not None else None, size)
|
||||
with self._read_lock():
|
||||
info = self._broken_cache.get(key)
|
||||
if info and info[0] == mtime:
|
||||
return info[1]
|
||||
return None
|
||||
|
||||
def _resolve_file_identity(self, path, curr_mtime, inode, device_id):
|
||||
"""Helper to resolve file mtime, device, and inode."""
|
||||
mtime = curr_mtime
|
||||
@@ -859,6 +886,11 @@ class ThumbnailCache(QObject):
|
||||
if mtime is None:
|
||||
return EMPTY_THUMBNAIL
|
||||
|
||||
# Check if known to be broken
|
||||
broken_msg = self.get_broken_info(path, target_tier, mtime, inode, device_id)
|
||||
if broken_msg:
|
||||
return ThumbnailResult(self._broken_images.get(target_tier), mtime, target_tier)
|
||||
|
||||
best_img, best_mtime, best_tier = None, 0, 0
|
||||
|
||||
with self._read_lock():
|
||||
@@ -1455,13 +1487,14 @@ class ImageScanner(QThread):
|
||||
more_files_available = Signal(int, int) # Last loaded index, remainder
|
||||
|
||||
def __init__(self, cache, paths, is_file_list=False, viewers=None,
|
||||
thread_pool_manager=None):
|
||||
thread_pool_manager=None, target_sizes=None):
|
||||
# is_file_list is not used
|
||||
if not paths or not isinstance(paths, (list, tuple)):
|
||||
logger.warning("ImageScanner initialized with empty or invalid paths")
|
||||
paths = []
|
||||
super().__init__()
|
||||
self.cache = cache
|
||||
self.target_sizes = target_sizes
|
||||
self.all_files = []
|
||||
self.thread_pool_manager = thread_pool_manager
|
||||
self._viewers = viewers
|
||||
@@ -1818,7 +1851,7 @@ class ImageScanner(QThread):
|
||||
return
|
||||
|
||||
for f_path, _ in tasks:
|
||||
r = ScannerWorker(self.cache, f_path, semaphore=sem)
|
||||
r = ScannerWorker(self.cache, f_path, semaphore=sem, target_sizes=self.target_sizes)
|
||||
r.setAutoDelete(False)
|
||||
runnables.append(r)
|
||||
self._current_workers.append(r)
|
||||
|
||||
Reference in New Issue
Block a user