Skip to content

Commit f721bc5

Browse files
committed
App: Improve thumbnail performance
[skip ci]
1 parent bb323b8 commit f721bc5

1 file changed

Lines changed: 65 additions & 32 deletions

File tree

‎scripts/gdrive_app.py‎

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from gdrive import gcache
2020

2121
from PySide6.QtSvg import QSvgRenderer
22-
from PySide6.QtCore import QByteArray, Qt, QRunnable, QObject, Signal, QThreadPool, Slot
22+
from PySide6.QtCore import QByteArray, Qt, QRunnable, Signal, QThreadPool, Slot, QTimer
2323
from PySide6.QtGui import QPainter, QImage
2424

2525
from collections import OrderedDict
@@ -28,24 +28,34 @@
2828
import gdrive_base as gdrive_base
2929
from strutils import thumbnail_path_for_file
3030

31-
class ThumbnailSignals(QObject):
32-
result = Signal(str, QImage)
3331

3432
class ThumbnailWorker(QRunnable):
35-
def __init__(self, file_data):
33+
def __init__(self, item, cancel_flag, emit_callback):
3634
super().__init__()
37-
self.file_data = file_data
38-
self.signals = ThumbnailSignals()
35+
self.item = item
36+
self.cancel_flag = cancel_flag
37+
self.emit_callback = emit_callback
38+
39+
def is_cancelled(self):
40+
return self.cancel_flag[0]
3941

4042
@Slot()
4143
def run(self):
42-
file_id = self.file_data.get('id', '')
43-
mime = self.file_data.get('mimeType', '')
44+
if self.is_cancelled():
45+
return
46+
self._process_item(self.item)
47+
48+
def _process_item(self, item):
49+
file_id = item.get('id', '')
50+
mime = item.get('mimeType', '')
4451
img = None
4552

4653
try:
4754
from gdrive import gcache
48-
cache_path = gcache.get_cache_path_for_file(self.file_data)
55+
cache_path = gcache.get_cache_path_for_file(item)
56+
57+
if self.is_cancelled(): return
58+
4959
if cache_path and cache_path.exists():
5060
if mime == 'application/pdf':
5161
thumbnail_bytes = pdfutils.get_cached_pdf_thumbnail(cache_path, size='large')
@@ -57,31 +67,20 @@ def run(self):
5767
img = QImage(str(thumb_path))
5868
else:
5969
if mime == 'application/pdf':
70+
if self.is_cancelled(): return
6071
thumbnail_bytes = gdrive_base.fetch_preview_image(file_id, size=256)
6172
if thumbnail_bytes:
73+
if self.is_cancelled(): return
6274
img = QImage()
6375
img.loadFromData(thumbnail_bytes)
6476
thumb_path.parent.mkdir(parents=True, exist_ok=True)
6577
thumb_path.write_bytes(thumbnail_bytes)
6678
except Exception as e:
67-
print(f"Error fetching thumbnail for {file_id}: {e}")
79+
if not self.is_cancelled():
80+
print(f"Error fetching thumbnail for {file_id}: {e}")
6881

69-
if img and not img.isNull():
70-
self.signals.result.emit(file_id, img)
71-
72-
class ThumbnailKickoffWorker(QRunnable):
73-
def __init__(self, items, callback):
74-
super().__init__()
75-
self.items = items
76-
self.callback = callback
77-
78-
@Slot()
79-
def run(self):
80-
pool = QThreadPool.globalInstance()
81-
for item in self.items:
82-
worker = ThumbnailWorker(item)
83-
worker.signals.result.connect(self.callback)
84-
pool.start(worker)
82+
if img and not img.isNull() and not self.is_cancelled():
83+
self.emit_callback(file_id, img)
8584

8685
class LRUCache:
8786
def __init__(self, capacity: int):
@@ -168,6 +167,8 @@ class HistoryEntry:
168167
clicked_item_id: Optional[str] = None
169168

170169
class GDriveApp(QMainWindow):
170+
thumbnail_loaded_signal = Signal(str, QImage)
171+
171172
def __init__(self):
172173
super().__init__()
173174
self.setWindowTitle("Google Drive Explorer")
@@ -177,10 +178,13 @@ def __init__(self):
177178
self.history_index = -1
178179
self.current_folder_id = None
179180

180-
self.threadpool = QThreadPool.globalInstance()
181-
self.threadpool.setMaxThreadCount(12)
181+
self.thumbnail_pool = QThreadPool(self)
182+
self.thumbnail_pool.setMaxThreadCount(10)
182183
self.thumbnail_cache = LRUCache(500)
183-
self.item_mapping: Dict[QListWidgetItem] = {}
184+
self.item_mapping: Dict[str, QListWidgetItem] = {}
185+
self.current_cancel_flag = [False]
186+
self.queued_thumbnails = set()
187+
self.thumbnail_loaded_signal.connect(self.on_thumbnail_loaded)
184188

185189
self.init_ui()
186190
self.load_root("my_drive")
@@ -249,6 +253,7 @@ def init_ui(self):
249253
central_widget.setSizes([200, 800])
250254

251255
self.update_nav_buttons()
256+
self.file_view.verticalScrollBar().valueChanged.connect(self.update_visible_thumbnails)
252257
self.file_view.setFocus()
253258

254259
def load_root(self, root_type: str, add_history=True, highlight_fileid: str | None = None, clicked_item_id: str | None = None):
@@ -322,6 +327,12 @@ def on_nav_clicked(self, item: QListWidgetItem):
322327
self.load_root(root_type)
323328

324329
def populate_files(self, items: List[Dict[str, Any]]):
330+
if hasattr(self, 'current_cancel_flag'):
331+
self.current_cancel_flag[0] = True
332+
self.current_cancel_flag = [False]
333+
self.thumbnail_pool.clear()
334+
self.queued_thumbnails.clear()
335+
325336
self.file_view.clear()
326337
self.item_mapping.clear()
327338

@@ -367,10 +378,32 @@ def populate_files(self, items: List[Dict[str, Any]]):
367378
list_item.setData(Qt.UserRole, item)
368379
self.file_view.addItem(list_item)
369380
self.item_mapping[file_id] = list_item
381+
382+
QTimer.singleShot(0, self.update_visible_thumbnails)
383+
self.file_view.setFocus(Qt.FocusReason.OtherFocusReason)
384+
385+
def update_visible_thumbnails(self):
386+
viewport_rect = self.file_view.viewport().rect()
387+
# Expand rect to load items slightly out of view
388+
expanded_rect = viewport_rect.adjusted(0, -viewport_rect.height(), 0, viewport_rect.height())
389+
390+
for i in range(self.file_view.count()):
391+
item = self.file_view.item(i)
392+
file_data = item.data(Qt.UserRole)
393+
mime = file_data.get('mimeType', '')
394+
file_id = file_data.get('id', '')
370395

371-
if items_needing_thumbnails:
372-
kickoff_worker = ThumbnailKickoffWorker(items_needing_thumbnails, self.on_thumbnail_loaded)
373-
self.threadpool.start(kickoff_worker)
396+
if mime == 'application/pdf' and file_id not in self.queued_thumbnails:
397+
if not self.thumbnail_cache.get(file_id):
398+
item_rect = self.file_view.visualItemRect(item)
399+
if expanded_rect.intersects(item_rect):
400+
self.queued_thumbnails.add(file_id)
401+
worker = ThumbnailWorker(
402+
file_data,
403+
self.current_cancel_flag,
404+
self.thumbnail_loaded_signal.emit
405+
)
406+
self.thumbnail_pool.start(worker)
374407

375408
def on_thumbnail_loaded(self, file_id: str, img: QImage):
376409
pixmap = QPixmap.fromImage(img)

0 commit comments

Comments
 (0)