Skip to content

Commit

Permalink
option to create windows .lnk files (on windows)
Browse files Browse the repository at this point in the history
  • Loading branch information
PJDude committed Mar 1, 2024
1 parent 154ad7b commit decdbe3
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 17 deletions.
50 changes: 46 additions & 4 deletions src/core.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,18 @@

from os.path import dirname,relpath,normpath,join as path_join,abspath as abspath,exists as path_exists,isdir as path_isdir

from subprocess import run as subprocess_run

from pickle import dumps,loads
from zstandard import ZstdCompressor,ZstdDecompressor

from send2trash import send2trash

DELETE=0
SOFTLINK=1
HARDLINK=2
WIN_LNK=3

def localtime_catched(t):
try:
#mtime sometimes happens to be negative (Virtual box ?)
Expand Down Expand Up @@ -814,6 +821,21 @@ def do_soft_link(self,src,dest,relative,l_info):
self.log.error(e)
return 'Error on soft linking:%s' % e

def do_win_lnk_link(self,src,dest,l_info):
l_info('win-lnk-linking %s<-%s',src,dest)
try:
powershell_cmd = f'$ol=(New-Object -ComObject WScript.Shell).CreateShortcut("{dest}")\n\r$ol.TargetPath="{src}"\n\r$ol.Save()'
l_info(f'{powershell_cmd=}')

res = subprocess_run(["powershell", "-Command", powershell_cmd], capture_output=True)

if res.returncode != 0:
return f"Error on win lnk code: {res.returncode} error: {res.stderr}"

except Exception as e:
self.log.error(e)
return 'Error on win lnk linking:%s' % e

def do_hard_link(self,src,dest,l_info):
l_info('hard-linking %s<-%s',src,dest)
try:
Expand Down Expand Up @@ -874,12 +896,31 @@ def delete_file_wrapper(self,size,crc,index_tuple_set,to_trash=False,file_callba

return messages

def win_lnk_wrapper (self,\
size,crc,\
index_tuple_ref,index_tuple_list,file_callback=None,crc_callback=None):

l_info = self.log.info

l_info(f'win_lnk_wrapper:{size},{crc},{index_tuple_ref},{index_tuple_list}')

(path_nr_keep,path_keep,file_keep,ctime_keep,dev_keep,inode_keep)=index_tuple_ref

self_get_full_path_scanned = self.get_full_path_scanned
self_files_of_size_of_crc_size_crc = self.files_of_size_of_crc[size][crc]

self_rename_file = self.rename_file
self_delete_file = self.delete_file

full_file_path_keep=self_get_full_path_scanned(path_nr_keep,path_keep,file_keep)

def link_wrapper(self,\
soft,relative,size,crc,\
kind,relative,size,crc,\
index_tuple_ref,index_tuple_list,file_callback=None,crc_callback=None):

l_info = self.log.info

l_info('link_wrapper:%s,%s,%s,%s,%s,%s',soft,relative,size,crc,index_tuple_ref,index_tuple_list)
l_info('link_wrapper:%s,%s,%s,%s,%s,%s',kind,relative,size,crc,index_tuple_ref,index_tuple_list)

(path_nr_keep,path_keep,file_keep,ctime_keep,dev_keep,inode_keep)=index_tuple_ref

Expand All @@ -891,7 +932,8 @@ def link_wrapper(self,\

full_file_path_keep=self_get_full_path_scanned(path_nr_keep,path_keep,file_keep)

link_command = (lambda p : self.do_soft_link(full_file_path_keep,p,relative,l_info)) if soft else (lambda p : self.do_hard_link(full_file_path_keep,p,l_info))
#link_command = (lambda p : self.do_soft_link(full_file_path_keep,p,relative,l_info)) if soft else (lambda p : self.do_hard_link(full_file_path_keep,p,l_info))
link_command = (lambda p : self.do_soft_link(full_file_path_keep,p,relative,l_info)) if kind==SOFTLINK else (lambda p : self.do_win_lnk_link(full_file_path_keep,str(p) + ".lnk",l_info)) if kind==WIN_LNK else (lambda p : self.do_hard_link(full_file_path_keep,p,l_info))

if index_tuple_ref not in self_files_of_size_of_crc_size_crc:
return 'link_wrapper - Internal Data Inconsistency:%s / %s' % (full_file_path_keep,index_tuple_ref)
Expand Down Expand Up @@ -926,7 +968,7 @@ def link_wrapper(self,\

tuples_to_remove.add(index_tuple)

if not soft:
if kind==HARDLINK:
tuples_to_remove.add(index_tuple_ref)

self.remove_from_data_pool(size,crc,tuples_to_remove,file_callback,crc_callback)
Expand Down
63 changes: 50 additions & 13 deletions src/dude.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@
CFG_KEY_EXCLUDE:''
}

DELETE=0
SOFTLINK=1
HARDLINK=2

NAME={DELETE:'Delete',SOFTLINK:'Softlink',HARDLINK:'Hardlink'}

HOMEPAGE='https://github.com/PJDude/dude'
Expand Down Expand Up @@ -1162,7 +1158,7 @@ def get_settings_dialog(self):
bfr.grid(row=row,column=0) ; row+=1

Button(bfr, text='Set defaults',width=14, command=self.settings_reset).pack(side='left', anchor='n',padx=5,pady=5)
Button(bfr, text='OK', width=14, command=self.settings_ok ).pack(side='left', anchor='n',padx=5,pady=5)
Button(bfr, text='OK', width=14, command=self.settings_ok ).pack(side='left', anchor='n',padx=5,pady=5,fill='both')
self.cancel_button=Button(bfr, text='Cancel', width=14 ,command=self.settings_dialog.hide )
self.cancel_button.pack(side='right', anchor='n',padx=5,pady=5)

Expand Down Expand Up @@ -2378,13 +2374,16 @@ def context_menu_show(self,event):

#marks_state=('disabled','normal')[len(tree.tag_has(self.MARK))!=0]
marks_state=('disabled','normal')[bool(self.tagged)]
marks_state_win=('disabled','normal')[bool(self.tagged) and windows ]

c_local_add_command(label = 'Remove Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(DELETE,0),accelerator="Delete",state=marks_state, image = self.ico_empty,compound='left')
c_local_entryconfig(19,foreground='red',activeforeground='red')
c_local_add_command(label = 'Softlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(SOFTLINK,0),accelerator="Insert",state=marks_state, image = self.ico_empty,compound='left')
c_local_entryconfig(20,foreground='red',activeforeground='red')
c_local_add_command(label = 'Hardlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(HARDLINK,0),accelerator="Shift+Insert",state=marks_state, image = self.ico_empty,compound='left')
c_local_add_command(label = 'Create *.lnk for Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(WIN_LNK,0),state=marks_state_win, image = self.ico_empty,compound='left')
c_local_entryconfig(21,foreground='red',activeforeground='red')
c_local_add_command(label = 'Hardlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(HARDLINK,0),accelerator="Shift+Insert",state=marks_state, image = self.ico_empty,compound='left')
c_local_entryconfig(22,foreground='red',activeforeground='red')

pop_add_cascade(label = 'Local (this CRC group)',menu = c_local,state=item_actions_state, image = self.ico_empty,compound='left')
pop_add_separator()
Expand Down Expand Up @@ -2427,8 +2426,10 @@ def context_menu_show(self,event):
c_all.entryconfig(21,foreground='red',activeforeground='red')
c_all.add_command(label = 'Softlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(SOFTLINK,1),accelerator="Ctrl+Insert",state=marks_state, image = self.ico_empty,compound='left')
c_all.entryconfig(22,foreground='red',activeforeground='red')
c_all.add_command(label = 'Hardlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(HARDLINK,1),accelerator="Ctrl+Shift+Insert",state=marks_state, image = self.ico_empty,compound='left')
c_all.add_command(label = 'Create *.lnk for Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(WIN_LNK,1),state=marks_state_win, image = self.ico_empty,compound='left')
c_all.entryconfig(23,foreground='red',activeforeground='red')
c_all.add_command(label = 'Hardlink Marked Files ...',command=lambda : self.process_files_in_groups_wrapper(HARDLINK,1),accelerator="Ctrl+Shift+Insert",state=marks_state, image = self.ico_empty,compound='left')
c_all.entryconfig(24,foreground='red',activeforeground='red')

pop_add_cascade(label = 'All Files',menu = c_all,state=item_actions_state, image = self.ico_empty,compound='left')

Expand All @@ -2455,6 +2456,7 @@ def context_menu_show(self,event):

else:
dir_actions_state=('disabled','normal')[self.sel_kind in (self.DIR,self.DIRLINK)]
dir_actions_state_win=('disabled','normal')[(self.sel_kind in (self.DIR,self.DIRLINK)) and windows]

c_local = Menu(pop,tearoff=0,bg=self.bg_color)
c_local_add_command = c_local.add_command
Expand All @@ -2472,13 +2474,15 @@ def context_menu_show(self,event):

#marks_state=('disabled','normal')[len(tree.tag_has(self.MARK))!=0]
marks_state=('disabled','normal')[bool(self.current_folder_items_tagged)]
marks_state_win=('disabled','normal')[bool(self.current_folder_items_tagged) and windows]

c_local_add_command(label = 'Remove Marked Files ...',command=lambda : self.process_files_in_folder_wrapper(DELETE,0),accelerator="Delete",state=marks_state, image = self.ico_empty,compound='left')
c_local_add_command(label = 'Softlink Marked Files ...',command=lambda : self.process_files_in_folder_wrapper(SOFTLINK,0),accelerator="Insert",state=marks_state, image = self.ico_empty,compound='left')
c_local_add_command(label = 'Create *.lnk for Marked Files ...',command=lambda : self.process_files_in_folder_wrapper(WIN_LNK,0),state=marks_state_win, image = self.ico_empty,compound='left')

c_local_entryconfig(8,foreground='red',activeforeground='red')
c_local_entryconfig(9,foreground='red',activeforeground='red')
#c_local_entryconfig(10,foreground='red',activeforeground='red')
c_local_entryconfig(10,foreground='red',activeforeground='red')

pop_add_cascade(label = 'Local (this folder)',menu = c_local,state=item_actions_state, image = self.ico_empty,compound='left')
pop_add_separator()
Expand All @@ -2491,9 +2495,11 @@ def context_menu_show(self,event):

c_sel_sub_add_command(label = 'Remove Marked Files in Subdirectory Tree ...',command=lambda : self.process_files_in_folder_wrapper(DELETE,True),accelerator="Delete",state=dir_actions_state, image = self.ico_empty,compound='left')
c_sel_sub_add_command(label = 'Softlink Marked Files in Subdirectory Tree ...',command=lambda : self.process_files_in_folder_wrapper(SOFTLINK,True),accelerator="Insert",state=dir_actions_state, image = self.ico_empty,compound='left')
c_sel_sub_add_command(label = 'Create *.lnk for Marked Files in Subdirectory Tree ...',command=lambda : self.process_files_in_folder_wrapper(WIN_LNK,True),state=dir_actions_state_win, image = self.ico_empty,compound='left')

c_sel_sub.entryconfig(3,foreground='red',activeforeground='red')
c_sel_sub.entryconfig(4,foreground='red',activeforeground='red')
c_sel_sub.entryconfig(5,foreground='red',activeforeground='red')

pop_add_cascade(label = 'Selected Subdirectory',menu = c_sel_sub,state=dir_actions_state, image = self.ico_empty,compound='left')

Expand Down Expand Up @@ -4134,7 +4140,7 @@ def process_files_check_correctness(self,action,processed_items,remaining_items)
incorrect_groups_append(crc)

problem_header = 'All files marked'
if action==SOFTLINK:
if action==SOFTLINK or action==WIN_LNK:
problem_message = "Keep at least one file unmarked\nor enable option:\n\"Skip groups with invalid selection\""
else:
problem_message = "Keep at least one file unmarked\nor enable option:\n\"Skip groups with invalid selection\"\nor enable option:\n\"Allow deletion of all copies\""
Expand Down Expand Up @@ -4211,6 +4217,11 @@ def process_files_confirm(self,action,processed_items,remaining_items,scope_titl
message=[]
message_append = message.append

if action==WIN_LNK:
message_append('Link files will be created with the names of the listed files with the ".lnk" suffix.')
message_append('Original files will be removed.')
message_append('')

self_item_full_path = self.item_full_path

self_groups_tree_item_to_data = self.groups_tree_item_to_data
Expand All @@ -4230,7 +4241,7 @@ def process_files_confirm(self,action,processed_items,remaining_items,scope_titl

message_append(' ' + (self_item_full_path(item) if show_full_path else file) + '|RED' )

if action==SOFTLINK:
if action==SOFTLINK or action==WIN_LNK:
if remaining_items[crc]:
item = remaining_items[crc][0]
if cfg_show_links_targets:
Expand All @@ -4244,7 +4255,11 @@ def process_files_confirm(self,action,processed_items,remaining_items,scope_titl
if not self.text_ask_dialog.res_bool:
return True
elif action==SOFTLINK:
self.get_text_ask_dialog().show('Soft-Link marked files to first unmarked file in group ?','Scope: ' + scope_title + '\n\n' + size_info + '\n'+'\n'.join(message))
self.get_text_ask_dialog().show('Soft-Link marked files to the first unmarked file in the group ?','Scope: ' + scope_title + '\n\n' + size_info + '\n'+'\n'.join(message))
if not self.text_ask_dialog.res_bool:
return True
elif action==WIN_LNK:
self.get_text_ask_dialog().show('replace marked files with .lnk files pointing to the first unmarked file in the group ?','Scope: ' + scope_title + '\n\n' + size_info + '\n'+'\n'.join(message))
if not self.text_ask_dialog.res_bool:
return True
elif action==HARDLINK:
Expand Down Expand Up @@ -4333,6 +4348,7 @@ def process_files_core(self,action,processed_items,remaining_items):
dude_core_delete_file_wrapper = dude_core.delete_file_wrapper

dude_core_link_wrapper = dude_core.link_wrapper
dude_core_win_lnk_wrapper = dude_core.win_lnk_wrapper

final_info=[]

Expand Down Expand Up @@ -4402,7 +4418,28 @@ def process_files_core(self,action,processed_items,remaining_items):
index_tuple_ref=self_groups_tree_item_to_data[to_keep_item][3]
size=self_groups_tree_item_to_data[to_keep_item][1]

if resmsg:=dude_core_link_wrapper(True, do_rel_symlink, size,crc, index_tuple_ref, [self_groups_tree_item_to_data[item][3] for item in items_dict.values() ],self.file_remove_callback,self.crc_remove_callback ):
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() ],self.file_remove_callback,self.crc_remove_callback ):
l_error(resmsg)

end_message_list_append(resmsg)

if abort_on_error:
break

if counter%16==0:
self_status('processing crc groups %s ...' % counter)


elif action==WIN_LNK:

for crc,items_dict in processed_items.items():
counter+=1
to_keep_item=remaining_items[crc][0]

index_tuple_ref=self_groups_tree_item_to_data[to_keep_item][3]
size=self_groups_tree_item_to_data[to_keep_item][1]

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() ],self.file_remove_callback,self.crc_remove_callback ):
l_error(resmsg)

end_message_list_append(resmsg)
Expand All @@ -4420,7 +4457,7 @@ def process_files_core(self,action,processed_items,remaining_items):
index_tuple_ref=self_groups_tree_item_to_data[ref_item][3]
size=self_groups_tree_item_to_data[ref_item][1]

if resmsg:=dude_core_link_wrapper(False, False, size,crc, index_tuple_ref, [self_groups_tree_item_to_data[item][3] for index,item in items_dict.items() if index!=0 ],self.file_remove_callback,self.crc_remove_callback ):
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 ],self.file_remove_callback,self.crc_remove_callback ):
l_error(resmsg)

end_message_list_append(resmsg)
Expand Down

0 comments on commit decdbe3

Please sign in to comment.