3232from signal import signal ,SIGINT
3333
3434from tkinter import Tk ,Toplevel ,PhotoImage ,Menu ,PanedWindow ,Label ,LabelFrame ,Frame ,StringVar ,BooleanVar ,IntVar ,TclVersion ,TkVersion
35- from tkinter .ttk import Checkbutton ,Radiobutton ,Treeview ,Scrollbar ,Button ,Entry ,Combobox ,Scale ,Style
35+ from tkinter .ttk import Checkbutton ,Radiobutton ,Treeview ,Scrollbar ,Button ,Entry ,Combobox ,Scale ,Style , Menubutton
3636from tkinter .filedialog import askdirectory ,asksaveasfilename
3737
3838if TkVersion < 9.0 :
5959
6060from os .path import abspath ,normpath ,dirname ,join as path_join ,isfile as path_isfile ,split as path_split ,exists as path_exists ,isdir , splitext as path_splitext
6161
62+ from collections import deque
63+ from psutil import disk_partitions
64+
6265#lazyfied
6366#from configparser import ConfigParser
6467#from subprocess import Popen
139142CFG_KEY_SHOW_PREVIEW = 'preview_shown'
140143
141144CFG_LANG = 'lang'
145+ CFG_RECENTS = 'recents'
142146
143147cfg_defaults = {
144148 CFG_THEME :'Vista' if windows else 'Clam' ,
176180 CFG_KEY_MARK_RE_0 :False ,
177181 CFG_KEY_MARK_RE_1 :False ,
178182 CFG_KEY_SHOW_PREVIEW :True ,
179- CFG_LANG :'English'
183+ CFG_LANG :'English' ,
184+ CFG_RECENTS :''
180185}
181186
182187NAME = {DELETE :'Delete' ,SOFTLINK :'Softlink' ,HARDLINK :'Hardlink' ,WIN_LNK :'.lnk file' }
187192
188193#DE_NANO = 1_000_000_000
189194
195+ def get_dev_labes_dict ():
196+ from subprocess import run as subprocess_run
197+ from json import loads as json_loads
198+
199+ lsblk = subprocess_run (['lsblk' ,'-fJ' ],capture_output = True ,text = True )
200+ lsblk .dict = json_loads (lsblk .stdout )
201+
202+ res_map = {}
203+ def dict_parse (src_dict ,res_map ):
204+ if 'children' in src_dict :
205+ for l_elem in src_dict ['children' ]:
206+ d_l_elem = dict (l_elem )
207+ dict_parse (d_l_elem ,res_map )
208+
209+ for mountpoint in src_dict ['mountpoints' ]:
210+ res_map [mountpoint ] = src_dict ['label' ]
211+
212+ for l_elem in lsblk .dict ['blockdevices' ]:
213+ d_l_elem = dict (l_elem )
214+ dict_parse (d_l_elem ,res_map )
215+
216+ return res_map
217+
190218class Config :
191219 def __init__ (self ,config_dir ):
192220 from configparser import ConfigParser
@@ -744,6 +772,8 @@ def __init__(self,cwd,paths_to_add=None,exclude=None,exclude_regexp=None,norun=N
744772 self .ico_warning = self_ico ['warning' ]
745773 self .ico_search_text = self_ico ['search_text' ]
746774 self .ico_empty = self_ico ['empty' ]
775+ self .ico_drive = self_ico ['drive' ]
776+ self .ico_recent = self_ico ['recent' ]
747777
748778 self .main_icon_tuple = (self .ico_dude ,self .ico_dude_small )
749779
@@ -1233,9 +1263,23 @@ def self_folder_tree_yview(*args):
12331263
12341264 Label (buttons_fr ,relief = 'flat' ,text = STR ('Specify manually, or drag and drop here, up to 8 paths to scan' ),bg = bg_color ,fg = 'gray' ).pack (side = 'right' ,pady = 4 ,padx = 4 , fill = 'x' ,expand = True )
12351265
1266+
1267+ self .add_dev_button = Menubutton (buttons_fr ,width = 18 ,image = self .ico_drive ,underline = 0 )
1268+ self .add_dev_button .pack (side = 'left' ,pady = 4 ,padx = 4 )
1269+ self .widget_tooltip (self .add_dev_button ,STR ("Select device to scan." ))
1270+
1271+ self .drives_menu = Menu (self .add_dev_button , tearoff = 0 ,postcommand = self .set_dev_to_scan_menu )
1272+ self .add_dev_button ["menu" ] = self .drives_menu
1273+
1274+ self .add_rec_button = Menubutton (buttons_fr ,width = 18 ,image = self .ico_recent ,underline = 0 )
1275+ self .add_rec_button .pack (side = 'left' ,pady = 4 ,padx = 4 )
1276+ self .widget_tooltip (self .add_rec_button ,STR ("Select recent path to scan." ))
1277+
1278+ self .recents_menu = Menu (self .add_rec_button , tearoff = 0 ,postcommand = self .set_rec_to_scan_menu )
1279+ self .add_rec_button ["menu" ] = self .recents_menu
1280+
12361281 self .add_path_button = Button (buttons_fr ,width = 18 ,image = self_ico ['open' ], command = self .path_to_scan_add_dialog ,underline = 0 )
12371282 self .add_path_button .pack (side = 'left' ,pady = 4 ,padx = 4 )
1238-
12391283 self .widget_tooltip (self .add_path_button ,STR ("Add path to scan" ))
12401284
12411285 self .paths_frame .grid_columnconfigure (1 , weight = 1 )
@@ -1676,6 +1720,45 @@ def help_cascade_post():
16761720 self_main .mainloop ()
16771721 #######################################################################
16781722
1723+ def set_dev_to_scan_menu (self ):
1724+ self .drives_menu .delete (0 ,'end' )
1725+
1726+ if not windows :
1727+ try :
1728+ labes_dict = get_dev_labes_dict ()
1729+ except Exception as lsblk_ex :
1730+ print (lsblk_ex )
1731+
1732+ for part in disk_partitions (all = False ):
1733+ filesystem_label = None
1734+
1735+ if windows :
1736+ if 'cdrom' in part .opts or part .fstype == '' :
1737+ # skip cd-rom drives with no disk in it; they may raise
1738+ # ENOENT, pop-up a Windows GUI error for a non-ready
1739+ # partition or just hang.
1740+ continue
1741+
1742+ try :
1743+ filesystem_label = GetVolumeInformation (part .mountpoint )[0 ]
1744+ except Exception as lab_ex :
1745+ print (lab_ex )
1746+ else :
1747+ try :
1748+ filesystem_label = labes_dict [part .mountpoint ]
1749+ except Exception as lab_ex :
1750+ print (lab_ex )
1751+
1752+ if part .fstype != 'squashfs' :
1753+ self .drives_menu .add_command (label = f'{ part .mountpoint } ({ filesystem_label } )' if filesystem_label else part .mountpoint ,command = lambda dev = part .mountpoint ,label = filesystem_label : self .path_to_scan_add (normpath (abspath (dev ))) )
1754+
1755+ def set_rec_to_scan_menu (self ):
1756+ self .recents_menu .delete (0 ,'end' )
1757+
1758+ for path in self .cfg .get (CFG_RECENTS ).split ('|' )[0 :16 ]:
1759+ if path :
1760+ self .recents_menu .add_command (label = path ,command = lambda path = path : self .path_to_scan_add (path ) )
1761+
16791762 hg_index = 0
16801763
16811764 def get_hg_ico (self ):
@@ -4128,6 +4211,11 @@ def scan(self):
41284211 self .get_info_dialog_on_scan ().show ('Error. Fix paths selection.' ,res )
41294212 return False
41304213
4214+ recents = set (self .cfg .get (CFG_RECENTS ).split ('|' ))
4215+ recents .update (paths_to_scan_from_entry )
4216+
4217+ self .cfg .set (CFG_RECENTS ,'|' .join (sorted (list (recents ))))
4218+
41314219 dude_core .scan_update_info_path_nr = self .scan_update_info_path_nr
41324220
41334221 self .main_update ()
@@ -4833,10 +4921,15 @@ def settings_reset(self):
48334921 _ = {var .set (cfg_defaults [key ]) for var ,key in self .settings }
48344922 _ = {var .set (cfg_defaults [key ]) for var ,key in self .settings_str }
48354923
4836- def file_remove_callback (self ,size ,crc ,index_tuple ):
4837- #print('file_remove_callback',size,crc,index_tuple)
4838- #l_info(f'file_remove_callback {size},{crc},{index_tuple}')
4924+ file_remove_callback_deque = deque ()
4925+ file_remove_callback_deque_append = file_remove_callback_deque .append
48394926
4927+ @logwrapper
4928+ def file_remove_callback_schedule (self ,size ,crc ,index_tuple ):
4929+ self .file_remove_callback_deque_append ( (size ,crc ,index_tuple ) )
4930+
4931+ @logwrapper
4932+ def file_remove_callback (self ,size ,crc ,index_tuple ):
48404933 try :
48414934 if self .operation_mode in (MODE_SIMILARITY ,MODE_GPS ):
48424935 (pathnr ,path ,file_name ,ctime ,dev ,inode ,size_file )= index_tuple
@@ -4857,11 +4950,15 @@ def file_remove_callback(self,size,crc,index_tuple):
48574950 self .selected [self .groups_tree ]= None
48584951 l_error (f'file_remove_callback,{ size } ,{ crc } ,{ index_tuple } ,{ e } ' )
48594952
4860- #l_info('file_remove_callback done')
4953+ crc_remove_callback_deque = deque ()
4954+ crc_remove_callback_deque_append = crc_remove_callback_deque .append
48614955
4862- def crc_remove_callback (self ,crc ):
4863- #print('crc_remove_callback',crc)
4956+ @logwrapper
4957+ def crc_remove_callback_schedule (self ,crc ):
4958+ self .crc_remove_callback_deque_append (crc )
48644959
4960+ @logwrapper
4961+ def crc_remove_callback (self ,crc ):
48654962 try :
48664963 self .groups_tree .delete (crc )
48674964
@@ -5899,7 +5996,7 @@ def hide_group (self):
58995996 index_tuple = self_groups_tree_item_to_data [item ][3 ]
59005997 tuples_to_hide_add (index_tuple )
59015998
5902- self_file_remove_callback = self .file_remove_callback
5999+ self_file_remove_callback = self .file_remove_callback_schedule
59036000 self_crc_remove_callback = self .crc_remove_callback
59046001
59056002 orglist = self .tree_children [self .groups_tree ]
@@ -6476,8 +6573,8 @@ def process_files_core(self,action,processed_items,remaining_items):
64766573 directories_to_check = set ()
64776574 directories_to_check_add = directories_to_check .add
64786575
6479- self_file_remove_callback = self .file_remove_callback
6480- self_crc_remove_callback = self .crc_remove_callback
6576+ self_file_remove_callback_schedule = self .file_remove_callback_schedule
6577+ self_crc_remove_callback_schedule = self .crc_remove_callback_schedule
64816578
64826579 if action == DELETE :
64836580 if self .operation_mode in (MODE_SIMILARITY ,MODE_GPS ):
@@ -6511,7 +6608,7 @@ def process_files_core(self,action,processed_items,remaining_items):
65116608
65126609 dummy_size = ''
65136610
6514- if resmsg := dude_core_delete_file_wrapper (dummy_size ,group ,tuples_to_delete ,to_trash ,self_file_remove_callback , self_crc_remove_callback ,True ):
6611+ if resmsg := dude_core_delete_file_wrapper (dummy_size ,group ,tuples_to_delete ,to_trash ,self_file_remove_callback_schedule , self_crc_remove_callback_schedule ,True ):
65156612 resmsg_str = '\n ' .join (resmsg )
65166613 l_error (resmsg_str )
65176614 end_message_list_append (resmsg_str )
@@ -6544,7 +6641,7 @@ def process_files_core(self,action,processed_items,remaining_items):
65446641 if path :
65456642 directories_to_check_add ( tuple ( [pathnr ] + path .strip (sep ).split (sep ) ) )
65466643
6547- if resmsg := dude_core_delete_file_wrapper (size ,crc ,tuples_to_delete ,to_trash ,self_file_remove_callback , self_crc_remove_callback ):
6644+ if resmsg := dude_core_delete_file_wrapper (size ,crc ,tuples_to_delete ,to_trash ,self_file_remove_callback_schedule , self_crc_remove_callback_schedule ):
65486645 resmsg_str = '\n ' .join (resmsg )
65496646 l_error (resmsg_str )
65506647 end_message_list_append (resmsg_str )
@@ -6596,7 +6693,7 @@ def process_files_core(self,action,processed_items,remaining_items):
65966693
65976694 self .process_files_core_info1 = f'crc:{ crc } '
65986695
6599- if resmsg := dude_core_link_wrapper (SOFTLINK , do_rel_symlink , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for item in items_dict .values () ],to_trash ,self_file_remove_callback , self_crc_remove_callback ):
6696+ if resmsg := dude_core_link_wrapper (SOFTLINK , do_rel_symlink , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for item in items_dict .values () ],to_trash ,self_file_remove_callback_schedule , self_crc_remove_callback_schedule ):
66006697 l_error (resmsg )
66016698
66026699 end_message_list_append (resmsg )
@@ -6620,7 +6717,7 @@ def process_files_core(self,action,processed_items,remaining_items):
66206717 self .process_files_core_info0 = f'size:{ bytes_to_str (size )} '
66216718 self .process_files_core_info1 = f'crc:{ crc } '
66226719
6623- if resmsg := dude_core_link_wrapper (WIN_LNK , False , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for item in items_dict .values () ],to_trash ,self_file_remove_callback , self_crc_remove_callback ):
6720+ if resmsg := dude_core_link_wrapper (WIN_LNK , False , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for item in items_dict .values () ],to_trash ,self_file_remove_callback_schedule , self_crc_remove_callback_schedule ):
66246721 l_error (resmsg )
66256722
66266723 end_message_list_append (resmsg )
@@ -6643,7 +6740,7 @@ def process_files_core(self,action,processed_items,remaining_items):
66436740 self .process_files_core_info0 = f'size:{ bytes_to_str (size )} '
66446741 self .process_files_core_info1 = f'crc:{ crc } '
66456742
6646- if resmsg := dude_core_link_wrapper (HARDLINK , False , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for index ,item in items_dict .items () if index != 0 ],to_trash ,self_file_remove_callback , self_crc_remove_callback ):
6743+ if resmsg := dude_core_link_wrapper (HARDLINK , False , size ,crc , index_tuple_ref , [self_groups_tree_item_to_data [item ][3 ] for index ,item in items_dict .items () if index != 0 ],to_trash ,self_file_remove_callback_schedule , self_crc_remove_callback_schedule ):
66476744 l_error (resmsg )
66486745
66496746 end_message_list_append (resmsg )
@@ -6805,9 +6902,17 @@ def process_files(self,action,processed_items,scope_title):
68056902
68066903 dialog_area_main_update = dialog .area_main .update
68076904
6905+ def something_to_delete_check ():
6906+ while self .file_remove_callback_deque :
6907+ size ,crc ,index_tuple = self .file_remove_callback_deque .popleft ()
6908+ self .file_remove_callback (size ,crc ,index_tuple )
6909+
6910+ while self .crc_remove_callback_deque :
6911+ crc = self .crc_remove_callback_deque .popleft ()
6912+ self .crc_remove_callback (crc )
6913+
68086914 #############################################
68096915 while run_processing_thread_is_alive ():
6810-
68116916 dialog_update_lab_text (0 ,self .process_files_core_info0 )
68126917 dialog_update_lab_text (1 ,self .process_files_core_info1 )
68136918 dialog_update_lab_text (2 , f'...{ self .process_files_core_info2 [- 50 :0 ]} ' )
@@ -6821,8 +6926,12 @@ def process_files(self,action,processed_items,scope_title):
68216926 self_main_after (100 ,lambda : wait_var_set (not wait_var_get ()))
68226927 self_main_wait_variable (wait_var )
68236928
6929+ something_to_delete_check ()
6930+
68246931 dialog_area_main_update ()
68256932
6933+ something_to_delete_check ()
6934+
68266935 #############################################
68276936
68286937 end_message_list ,final_info = self .process_files_result
0 commit comments