22import os
33import subprocess
44from typing import List , Dict , Any , Optional
5+ from dataclasses import dataclass
56
67from PySide6 .QtWidgets import (QApplication , QMainWindow , QWidget , QVBoxLayout ,
78 QHBoxLayout , QListWidget , QListWidgetItem ,
@@ -134,6 +135,10 @@ def get_mime_icon(mime_type: str) -> QIcon:
134135 color = QApplication .palette ().windowText ().color ().name ()
135136 if mime_type == 'application/vnd.google-apps.folder' :
136137 return get_icon (FilledIcon .FOLDER , is_filled = True , color = color )
138+ elif mime_type == 'application/vnd.google-apps.shortcut+file' :
139+ return get_icon (OutlineIcon .FILE_SYMLINK , color = "#999999" )
140+ elif mime_type == 'application/vnd.google-apps.shortcut+folder' :
141+ return get_icon (OutlineIcon .FOLDER_SYMLINK , color = "#999999" )
137142 elif mime_type == 'application/vnd.google-apps.document' :
138143 return get_icon (OutlineIcon .FILE_TEXT , color = "#4285F4" )
139144 elif mime_type == 'application/vnd.google-apps.spreadsheet' :
@@ -155,20 +160,26 @@ def get_mime_icon(mime_type: str) -> QIcon:
155160 else :
156161 return get_icon (OutlineIcon .FILE , color = color )
157162
163+ @dataclass
164+ class HistoryEntry :
165+ id : str
166+ name : str
167+ clicked_item_id : Optional [str ] = None
168+
158169class GDriveApp (QMainWindow ):
159170 def __init__ (self ):
160171 super ().__init__ ()
161172 self .setWindowTitle ("Google Drive Explorer" )
162173 self .resize (1000 , 600 )
163174
164- self .history = []
175+ self .history : List [ HistoryEntry ] = []
165176 self .history_index = - 1
166177 self .current_folder_id = None
167178
168179 self .threadpool = QThreadPool .globalInstance ()
169180 self .threadpool .setMaxThreadCount (12 )
170181 self .thumbnail_cache = LRUCache (500 )
171- self .item_mapping = {}
182+ self .item_mapping : Dict [ QListWidgetItem ] = {}
172183
173184 self .init_ui ()
174185 self .load_root ("my_drive" )
@@ -239,7 +250,7 @@ def init_ui(self):
239250 self .update_nav_buttons ()
240251 self .file_view .setFocus ()
241252
242- def load_root (self , root_type : str , add_history = True ):
253+ def load_root (self , root_type : str , add_history = True , highlight_fileid : str | None = None , clicked_item_id : str | None = None ):
243254 if root_type == "my_drive" :
244255 items = gcache .get_root_my_drive_children ()
245256 self .address_bar .setText ("My Drive" )
@@ -249,41 +260,56 @@ def load_root(self, root_type: str, add_history=True):
249260
250261 self .current_folder_id = root_type
251262 if add_history :
252- self .add_to_history (root_type )
263+ self .add_to_history (root_type , clicked_item_id )
253264 self .populate_files (items )
265+ if highlight_fileid and highlight_fileid in self .item_mapping :
266+ item = self .item_mapping [highlight_fileid ]
267+ item .setSelected (True )
268+ self .file_view .setCurrentItem (item )
269+ self .file_view .repaint ()
254270
255- def load_folder (self , folder_id : str , folder_name : str , add_history = True ):
271+ def load_folder (self , folder_id : str , folder_name : str , add_history = True , highlight_fileid : str | None = None , clicked_item_id : str | None = None ):
256272 items = gcache .get_children (folder_id )
257273 self .current_folder_id = folder_id
258274 self .address_bar .setText (folder_name )
259275 if add_history :
260- self .add_to_history (folder_id )
276+ self .add_to_history (folder_id , clicked_item_id )
261277 self .populate_files (items )
278+ if highlight_fileid and highlight_fileid in self .item_mapping :
279+ item = self .item_mapping [highlight_fileid ]
280+ item .setSelected (True )
281+ self .file_view .setCurrentItem (item )
282+ self .file_view .repaint () # Don't let QT wait for the async thumbnails
262283
263- def add_to_history (self , folder_id : str ):
284+ def add_to_history (self , folder_id : str , clicked_item_id : str | None = None ):
264285 # Trim future history if we navigated back then clicked a new folder
265286 self .history = self .history [:self .history_index + 1 ]
266- self .history .append ((folder_id , self .address_bar .text ()))
287+ self .history .append (HistoryEntry (
288+ id = folder_id ,
289+ name = self .address_bar .text (),
290+ clicked_item_id = clicked_item_id
291+ ))
267292 self .history_index += 1
268293 self .update_nav_buttons ()
269294
270295 def go_back (self ):
271296 if self .history_index > 0 :
297+ current_entry = self .history [self .history_index ]
272298 self .history_index -= 1
273- folder_id , name = self .history [self .history_index ]
274- self ._load_from_history (folder_id , name )
299+ prev_entry = self .history [self .history_index ]
300+ self ._load_from_history (prev_entry . id , prev_entry . name , highlight_fileid = current_entry . clicked_item_id )
275301
276302 def go_forward (self ):
277303 if self .history_index < len (self .history ) - 1 :
278304 self .history_index += 1
279- folder_id , name = self .history [self .history_index ]
280- self ._load_from_history (folder_id , name )
305+ entry = self .history [self .history_index ]
306+ self ._load_from_history (entry . id , entry . name )
281307
282- def _load_from_history (self , folder_id : str , name : str ):
308+ def _load_from_history (self , folder_id : str , name : str , highlight_fileid : str | None = None ):
283309 if folder_id in ["my_drive" , "shared_with_me" ]:
284- self .load_root (folder_id , add_history = False )
310+ self .load_root (folder_id , add_history = False , highlight_fileid = highlight_fileid )
285311 else :
286- self .load_folder (folder_id , name , add_history = False )
312+ self .load_folder (folder_id , name , add_history = False , highlight_fileid = highlight_fileid )
287313 self .update_nav_buttons ()
288314
289315 def update_nav_buttons (self ):
@@ -298,23 +324,33 @@ def populate_files(self, items: List[Dict[str, Any]]):
298324 self .file_view .clear ()
299325 self .item_mapping .clear ()
300326
301- # Sort folders first , then files, both alphabetically
327+ # Show folders, then folder shortcuts, then files
302328 folders = []
329+ folder_shortcuts = []
303330 files = []
304331 items_needing_thumbnails = []
305332 for item in items :
306333 mime = item .get ('mimeType' , '' )
307334 if mime == 'application/vnd.google-apps.folder' :
308335 folders .append (item )
336+ elif mime == 'application/vnd.google-apps.shortcut' and item ['shortcutDetails' ]['targetMimeType' ] == 'application/vnd.google-apps.folder' :
337+ folder_shortcuts .append (item )
309338 else :
310339 files .append (item )
311-
312- folders .sort (key = lambda x : x .get ('name' , '' ).lower ())
313- files .sort (key = lambda x : x .get ('name' , '' ).lower ())
340+
341+ sortkey = lambda x : x .get ('name' , '' ).lower ()
342+ folders .sort (key = sortkey )
343+ folder_shortcuts .sort (key = sortkey )
344+ files .sort (key = sortkey )
314345
315- for item in folders + files :
346+ for item in folders + folder_shortcuts + files :
316347 name = item .get ('name' , 'Unknown' )
317348 mime = item .get ('mimeType' , '' )
349+ if item .get ('shortcutDetails' ):
350+ if item ['shortcutDetails' ]['targetMimeType' ] == 'application/vnd.google-apps.folder' :
351+ mime += '+folder'
352+ else :
353+ mime += "+file"
318354 file_id = item .get ('id' , '' )
319355
320356 list_item = QListWidgetItem (name )
@@ -348,7 +384,16 @@ def on_item_activated(self, item: QListWidgetItem):
348384 mime = file_data .get ('mimeType' , '' )
349385
350386 if mime == 'application/vnd.google-apps.folder' :
351- self .load_folder (file_data ['id' ], file_data ['name' ])
387+ self .load_folder (file_data ['id' ], file_data ['name' ], clicked_item_id = file_data ['id' ])
388+ elif mime == 'application/vnd.google-apps.shortcut' :
389+ if file_data ['shortcutDetails' ]['targetMimeType' ] == 'application/vnd.google-apps.folder' :
390+ target_folder = gcache .get_item (file_data ['shortcutDetails' ]['targetId' ])
391+ target_file = None
392+ else :
393+ target_file = gcache .get_item (file_data ['shortcutDetails' ]['targetId' ])
394+ target_folder = gcache .get_item (target_file ['parent_id' ])
395+ target_file = target_file ['id' ]
396+ self .load_folder (target_folder ['id' ], target_folder ['name' ], highlight_fileid = target_file , clicked_item_id = file_data ['id' ])
352397 else :
353398 self .open_file (file_data )
354399
0 commit comments