Improve history loading and thumbnail caching UX

Refactors the history loading process to emit entries as soon as they are loaded, allowing the UI to display immediately. Thumbnail downloads are now handled in the background, with widgets updated via a new signal when thumbnails are cached. This prevents UI freezes and improves responsiveness by showing placeholders until thumbnails are available.
This commit is contained in:
oop7
2026-01-21 17:16:48 +02:00
parent a92ffd3b01
commit ea54f469ac
@@ -42,14 +42,18 @@ if TYPE_CHECKING:
class HistoryLoaderThread(QThread):
"""Thread to load history and pre-fetch thumbnails."""
finished = Signal(list)
entries_loaded = Signal(list)
thumbnail_loaded = Signal(str)
def run(self):
try:
# HistoryManager uses get_all_entries, not get_all
entries = HistoryManager.get_all_entries()
# Pre-fetch thumbnails so UI doesn't freeze
# Emit entries immediately so UI shows up
self.entries_loaded.emit(entries)
# Pre-fetch thumbnails in background
for entry in entries:
thumbnail_url = entry.get("thumbnail_url")
entry_id = entry.get("id", "")
@@ -69,14 +73,13 @@ class HistoryLoaderThread(QThread):
image = Image.open(BytesIO(response.content))
image.save(thumbnail_path, "JPEG", quality=95, optimize=True)
self.thumbnail_loaded.emit(entry_id)
except Exception as e:
logger.debug(f"Error caching thumbnail in background: {e}")
self.finished.emit(entries)
except Exception as e:
logger.error(f"Error loading history: {e}")
self.finished.emit([])
self.entries_loaded.emit([])
class HistoryEntryWidget(QFrame):
@@ -246,40 +249,13 @@ class HistoryEntryWidget(QFrame):
except Exception as e:
logger.debug(f"Error loading cached thumbnail: {e}")
# Download thumbnail
try:
response = requests.get(thumbnail_url, timeout=5)
response.raise_for_status()
image = Image.open(BytesIO(response.content))
# Don't resize, keep original quality and just save at higher quality
# The QPixmap scaling will handle the display size with high quality
# Save to cache with higher quality
try:
APP_THUMBNAILS_DIR.mkdir(parents=True, exist_ok=True)
image.save(thumbnail_path, "JPEG", quality=95, optimize=True)
except Exception as e:
logger.debug(f"Error caching thumbnail: {e}")
# Convert to QPixmap
image_bytes = BytesIO()
image.save(image_bytes, format="JPEG", quality=95)
image_bytes.seek(0)
pixmap = QPixmap()
pixmap.loadFromData(image_bytes.read())
if not pixmap.isNull():
# Don't scale here, let setScaledContents handle it
self.thumbnail_label.setPixmap(pixmap)
else:
# If not in cache, just set placeholder.
# Background thread will download it and notify parent to reload.
self.set_placeholder_thumbnail()
except Exception as e:
logger.debug(f"Error downloading thumbnail: {e}")
self.set_placeholder_thumbnail()
def reload_thumbnail(self):
"""Reload thumbnail from cache (called when background download finishes)."""
self.load_thumbnail()
def set_placeholder_thumbnail(self):
"""Set a placeholder when thumbnail is not available."""
@@ -401,6 +377,7 @@ class HistoryDialog(QDialog):
super().__init__(parent)
self.parent_app = parent
self.entry_widgets = []
self.entry_widgets_map = {} # Map entry_id -> widget
self.setup_ui()
@@ -414,13 +391,19 @@ class HistoryDialog(QDialog):
def start_loading_history(self):
"""Start the background thread to load history."""
self.loader_thread = HistoryLoaderThread()
self.loader_thread.finished.connect(self.on_history_loaded)
self.loader_thread.entries_loaded.connect(self.on_history_loaded)
self.loader_thread.thumbnail_loaded.connect(self.update_entry_thumbnail)
self.loader_thread.start()
def on_history_loaded(self, entries):
"""Called when history is loaded from background thread."""
self.load_history_entries(entries)
def update_entry_thumbnail(self, entry_id: str):
"""Called when a thumbnail is downloaded in the background."""
if entry_id in self.entry_widgets_map:
self.entry_widgets_map[entry_id].reload_thumbnail()
def setup_ui(self):
"""Setup the dialog UI."""
self.setWindowTitle(_("history.title"))
@@ -528,6 +511,7 @@ class HistoryDialog(QDialog):
for widget in self.entry_widgets:
widget.deleteLater()
self.entry_widgets.clear()
self.entry_widgets_map.clear()
def show_loading_state(self):
"""Show loading indicator."""
@@ -572,6 +556,11 @@ class HistoryDialog(QDialog):
widget.redownload_requested.connect(self.handle_redownload)
self.history_layout.addWidget(widget)
# Map widget by ID for updates
entry_id = entry.get("id")
if entry_id:
self.entry_widgets_map[entry_id] = widget
self.entry_widgets.append(widget)
# Update status