1919from gdrive import gcache
2020
2121from 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
2323from PySide6 .QtGui import QPainter , QImage
2424
2525from collections import OrderedDict
2828import gdrive_base as gdrive_base
2929from strutils import thumbnail_path_for_file
3030
31- class ThumbnailSignals (QObject ):
32- result = Signal (str , QImage )
3331
3432class 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
8685class LRUCache :
8786 def __init__ (self , capacity : int ):
@@ -168,6 +167,8 @@ class HistoryEntry:
168167 clicked_item_id : Optional [str ] = None
169168
170169class 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