Skip to content

Commit b24bcd9

Browse files
committed
remember recent paths to scan
choose drives to scan drirectly fix cross-thread updates
1 parent 46d0a85 commit b24bcd9

5 files changed

Lines changed: 133 additions & 22 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ appdirs==1.4.4
33
send2trash==1.8.3
44
zstandard==0.25.0
55
tkinterdnd2==0.4.3
6+
psutil==7.1.3
67
numpy==2.3.5
78
scipy==1.16.3
89
exifread==3.5.1

src/core.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,17 +1563,18 @@ def remove_from_data_pool(self,size,crc,index_tuple_list,file_callback=None,crc_
15631563
self.log.info('remove_from_data_pool size:%s crc:%s tuples:%s',size,crc,index_tuple_list)
15641564

15651565
if operation_mode in (MODE_SIMILARITY,MODE_GPS):
1566-
if crc in self.files_of_images_groups:
1567-
for pathnr,path,file,ctime,dev,inode,size_file in list(self.files_of_images_groups[crc]):
1566+
self_files_of_images_groups=self.files_of_images_groups
1567+
if crc in self_files_of_images_groups:
1568+
for pathnr,path,file,ctime,dev,inode,size_file in list(self_files_of_images_groups[crc]):
15681569
for index_tuple in index_tuple_list:
15691570
try:
1570-
self.files_of_images_groups[crc].remove(index_tuple)
1571+
self_files_of_images_groups[crc].remove(index_tuple)
15711572
file_callback(size_file,crc,index_tuple)
15721573

15731574
except Exception as e:
15741575
self.log.error(' %s',e)
15751576
self.log.error(' index_tuple: %s',index_tuple)
1576-
self.log.error(' self.files_of_images_groups[%s]:%s',crc,self.files_of_images_groups[crc])
1577+
self.log.error(' self.files_of_images_groups[%s]:%s',crc,self_files_of_images_groups[crc])
15771578
else:
15781579
self.log.warning('remove_from_data_pool - crc already removed')
15791580

src/dude.py

Lines changed: 127 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from signal import signal,SIGINT
3333

3434
from 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
3636
from tkinter.filedialog import askdirectory,asksaveasfilename
3737

3838
if TkVersion<9.0:
@@ -59,6 +59,9 @@
5959

6060
from 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
@@ -139,6 +142,7 @@
139142
CFG_KEY_SHOW_PREVIEW = 'preview_shown'
140143

141144
CFG_LANG = 'lang'
145+
CFG_RECENTS = 'recents'
142146

143147
cfg_defaults={
144148
CFG_THEME:'Vista' if windows else 'Clam',
@@ -176,7 +180,8 @@
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

182187
NAME={DELETE:'Delete',SOFTLINK:'Softlink',HARDLINK:'Hardlink',WIN_LNK:'.lnk file'}
@@ -187,6 +192,29 @@
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+
190218
class 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

src/icons/drive.png

612 Bytes
Loading

src/icons/recent.png

815 Bytes
Loading

0 commit comments

Comments
 (0)