diff --git a/README.md b/README.md index 418881a..413b9a1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A file cataloging program with extensive customization options to suit user pref **The software and this document are under development and in a pre-release state.** ## Features: -The primary purpose of this software is to enable users to catalog their files, particularly on removable media like memory cards and portable drives. It allows user to add metadata, referred to here as **custom data**, and facilitates future searching through the created records. **Custom data** consists of textual information acquired through the execution of user-chosen commands or scripts. **Custom data** may include any text data customized to meet the user's requirements, restricted only by the available memory and the software accessible for data retrieval. The retrieved data is stored in a newly created database record and can be utilized for search or verification purposes. +The primary purpose of this software is to enable users to catalog their files, particularly on removable media like memory cards and portable drives. It allows user to add metadata, referred to here as **custom data**, and facilitates future searching through the created records. **Custom data** consists of textual information acquired through the execution of user-chosen commands or scripts. **Custom data** may include any text data customized to meet the user's requirements, restricted only by the available memory and the software accessible for data retrieval. The retrieved data is stored in a newly created database record and can be utilized for search or verification purposes. **Liber** allows you to search files using regular expressions, glob expressions and **fuzzy matching** on both filenames and custom data. Created data records can be exported and imported to share data with others or for backup purposes. ## Screenshots: @@ -66,7 +66,7 @@ Custom data extractor is a command that can be invoked with a single parameter - ## Technical information -Record in librer is the result of a single scan operation and is shown as one of many top nodes in the main tree window. Contains a directory tree with collected custom data and CRC data. It is stored as a single .dat file in librer database directory. Its internal format is optimized for security, fast initial access and maximum compression (just check :)) Every section is a python data structure serialized by [pickle](https://docs.python.org/3/library/pickle.html) and compressed separately by [Zstandard](https://pypi.org/project/zstandard/) algorithm. The record file, once saved, is never modified afterward. It can only be deleted upon request or exported. All record files are independent of each other. +Record in librer is the result of a single scan operation and is shown as one of many top nodes in the main tree window. Contains a directory tree with collected custom data and CRC data. It is stored as a single .dat file in librer database directory. Its internal format is optimized for security, fast initial access and maximum compression (just check :)) Every section is a python data structure serialized by [pickle](https://docs.python.org/3/library/pickle.html) and compressed separately by [Zstandard](https://pypi.org/project/zstandard/) algorithm. The record file, once saved, is never modified afterward. It can only be deleted upon request or exported. All record files are independent of each other.Fuzzy matching is implemented using the SequenceMatcher function provided by the [difflib](https://docs.python.org/3/library/difflib.html) package. ###### Manual build (linux): ``` diff --git a/src/core.py b/src/core.py index abde2f0..e176b68 100644 --- a/src/core.py +++ b/src/core.py @@ -51,8 +51,6 @@ from re import search from sys import getsizeof -from shutil import copy as shutil_copy - from hashlib import sha1 from collections import defaultdict @@ -60,8 +58,6 @@ from re import compile as re_compile from re import IGNORECASE -from signal import SIGTERM - from time import time from time import strftime from time import localtime @@ -94,6 +90,9 @@ def bytes_to_str(num): return "BIG" +def fnumber(num): + return str(format(num,',d').replace(',',' ')) + def str_to_bytes(string): units = {'kb': 1024,'mb': 1024*1024,'gb': 1024*1024*1024,'tb': 1024*1024*1024*1024, 'b':1} try: @@ -143,7 +142,7 @@ def test_regexp(expr): #print(i, temp_tuple) ####################################################################### -data_format_version='1.0009' +data_format_version='1.0010' class LibrerRecordHeader : def __init__(self,label='',path=''): @@ -165,6 +164,8 @@ def __init__(self,label='',path=''): self.files_cde_size_extracted = 0 self.files_cde_errors_quant = 0 + cde_list = [] + self.creation_os,self.creation_host = f'{platform_system()} {platform_release()}',platform_node() ####################################################################### @@ -174,7 +175,7 @@ def __init__(self,label,path,log): #self.filenames = () self.filestructure = () - self.custom_data = [] + self.customdata = [] self.log = log self.find_results = [] @@ -241,7 +242,7 @@ def calc_crc(self,fullpath,size): #return hasher.hexdigest() return hasher.digest() - def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=None) : + def scan_rec(self, path, scan_like_data,filenames_set,check_dev=True,dev_call=None) : if self.abort_action: return True @@ -252,7 +253,7 @@ def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=N self_scan_rec = self.scan_rec - file_names_set_add = file_names_set.add + filenames_set_add = filenames_set.add try: with scandir(path) as res: local_folder_files_count = 0 @@ -263,7 +264,7 @@ def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=N break entry_name = entry.name - file_names_set_add(entry_name) + filenames_set_add(entry_name) is_dir,is_file,is_symlink = entry.is_dir(),entry.is_file(),entry.is_symlink() @@ -273,7 +274,6 @@ def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=N try: stat_res = stat(entry) - #mtime_ns = stat_res.st_mtime_ns mtime = int(stat_res.st_mtime) dev=stat_res.st_dev except Exception as e: @@ -303,7 +303,7 @@ def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=N has_files = False size = 0 else: - size = self_scan_rec(path_join_loc(path,entry_name),dict_entry,file_names_set,check_dev,dev) + size = self_scan_rec(path_join_loc(path,entry_name),dict_entry,filenames_set,check_dev,dev) has_files = bool(size) local_folder_size_with_subtree += size @@ -338,7 +338,7 @@ def scan_rec(self, path, scan_like_data,file_names_set,check_dev=True,dev_call=N return local_folder_size_with_subtree+local_folder_size def get_file_name(self,nr): - return self.FILE_NAMES[nr] + return self.filenames[nr] def scan(self,cde_list,check_dev=True): self.info_line = 'Scanning filesystem' @@ -350,36 +350,36 @@ def scan(self,cde_list,check_dev=True): self.scan_data={} ######################### - file_names_set=set() - self.scan_rec(self.header.scan_path,self.scan_data,file_names_set) + filenames_set=set() + self.scan_rec(self.header.scan_path,self.scan_data,filenames_set) - self.FILE_NAMES = tuple(sorted(list(file_names_set))) + self.filenames = tuple(sorted(list(filenames_set))) ######################### self.info_line = 'indexing filesystem names' - self.FILE_NAMES_helper = {fsname:fsname_index for fsname_index,fsname in enumerate(self.FILE_NAMES)} + self.filenames_helper = {fsname:fsname_index for fsname_index,fsname in enumerate(self.filenames)} - self.cde_list = cde_list + self.header.cde_list = cde_list self.cd_stat=[0]*len(cde_list) - self.custom_data_pool = {} - self.custom_data_pool_index = 0 + self.customdata_pool = {} + self.customdata_pool_index = 0 - #if cde_list: - self.info_line = f'estimating files pool for custom data extraction' - self.prepare_custom_data_pool_rec(self.scan_data,[]) + if cde_list: + self.info_line = f'estimating files pool for custom data extraction' + self.prepare_customdata_pool_rec(self.scan_data,[]) self.info_line = '' #for ext,stat in sorted(self.ext_statistics.items(),key = lambda x : x[1],reverse=True): # print(ext,stat) - def prepare_custom_data_pool_rec(self,scan_like_data,parent_path): + def prepare_customdata_pool_rec(self,scan_like_data,parent_path): scan_path = self.header.scan_path - self_prepare_custom_data_pool_rec = self.prepare_custom_data_pool_rec + self_prepare_customdata_pool_rec = self.prepare_customdata_pool_rec - self_cde_list = self.cde_list - self_custom_data_pool = self.custom_data_pool + cde_list = self.header.cde_list + self_customdata_pool = self.customdata_pool for entry_name,items_list in scan_like_data.items(): if self.abort_action: @@ -392,7 +392,7 @@ def prepare_custom_data_pool_rec(self,scan_like_data,parent_path): if not is_symlink and not is_bind: if is_dir: if has_files: - self_prepare_custom_data_pool_rec(items_list[7],subpath_list) + self_prepare_customdata_pool_rec(items_list[7],subpath_list) else: subpath=sep.join(subpath_list) ############################ @@ -402,7 +402,7 @@ def prepare_custom_data_pool_rec(self,scan_like_data,parent_path): matched = False rule_nr=-1 - for expressions,use_smin,smin_int,use_smax,smax_int,executable,timeout,crc in self_cde_list: + for expressions,use_smin,smin_int,use_smax,smax_int,executable,timeout,crc in cde_list: if self.abort_action: break if matched: @@ -424,17 +424,17 @@ def prepare_custom_data_pool_rec(self,scan_like_data,parent_path): break if fnmatch(full_file_path,expr): - self_custom_data_pool[self.custom_data_pool_index]=(items_list,subpath,rule_nr) - self.custom_data_pool_index += 1 + self_customdata_pool[self.customdata_pool_index]=(items_list,subpath,rule_nr) + self.customdata_pool_index += 1 self.cd_stat[rule_nr]+=1 self.header.files_cde_size_sum += size matched = True except Exception as e: - self.log.error('prepare_custom_data_pool_rec error::%s',e ) - print('prepare_custom_data_pool_rec',e,entry_name,size,is_dir,is_file,is_symlink,is_bind,has_files,mtime) + self.log.error('prepare_customdata_pool_rec error::%s',e ) + print('prepare_customdata_pool_rec',e,entry_name,size,is_dir,is_file,is_symlink,is_bind,has_files,mtime) - def extract_custom_data(self): + def extract_customdata(self): self_header = self.header scan_path = self_header.scan_path @@ -444,30 +444,30 @@ def extract_custom_data(self): self_header.files_cde_size = 0 self_header.files_cde_size_extracted = 0 self_header.files_cde_errors_quant = 0 - self_header.files_cde_quant_sum = len(self.custom_data_pool) + self_header.files_cde_quant_sum = len(self.customdata_pool) - self_cde_list = self.cde_list + cde_list = self.header.cde_list exe = Executor() - custom_data_helper={} + customdata_helper={} cd_index=0 - self_custom_data_append = self.custom_data.append + self_customdata_append = self.customdata.append self_calc_crc = self.calc_crc exe_run = exe.run - for (scan_like_list,subpath,rule_nr) in self.custom_data_pool.values(): + for (scan_like_list,subpath,rule_nr) in self.customdata_pool.values(): if self.abort_action: break - expressions,use_smin,smin_int,use_smax,smax_int,executable,timeout,crc = self_cde_list[rule_nr] + expressions,use_smin,smin_int,use_smax,smax_int,executable,timeout,crc = cde_list[rule_nr] full_file_path = normpath(abspath(sep.join([scan_path,subpath]))).replace('/',sep) size = scan_like_list[0] - cde_run_list = executable + [full_file_path] + cde_run_list = list(executable) + [full_file_path] if crc: self.info_line_current = f'{subpath} CRC calculation ({bytes_to_str(size)})' @@ -483,14 +483,14 @@ def extract_custom_data(self): new_elem={} new_elem['cd_ok']=cd_ok - if output not in custom_data_helper: - custom_data_helper[output]=cd_index + if output not in customdata_helper: + customdata_helper[output]=cd_index new_elem['cd_index']=cd_index cd_index+=1 - self_custom_data_append(output) + self_customdata_append(output) else: - new_elem['cd_index']=custom_data_helper[output] + new_elem['cd_index']=customdata_helper[output] if crc: new_elem['crc_val']=crc_val @@ -503,8 +503,8 @@ def extract_custom_data(self): self.info_line_current = '' - del self.custom_data_pool - del custom_data_helper + del self.customdata_pool + del customdata_helper exe.end() @@ -516,14 +516,14 @@ def tupelize_rec(self,scan_like_data): self_tupelize_rec = self.tupelize_rec - self_custom_data = self.custom_data + self_customdata = self.customdata sub_list = [] for entry_name,items_list in scan_like_data.items(): try: - entry_name_index = self.FILE_NAMES_helper[entry_name] + entry_name_index = self.filenames_helper[entry_name] except Exception as VE: - print('FILE_NAMES error:',entry_name,VE) + print('filenames error:',entry_name,VE) else: try: (size,is_dir,is_file,is_symlink,is_bind,has_files,mtime) = items_list[0:7] @@ -590,17 +590,17 @@ def pack_data(self): code = entry_LUT_encode[ (is_dir,is_file,is_symlink,is_bind,has_cd,has_files,cd_ok,has_crc) ] self.filestructure = ('',code,size,mtime,self.tupelize_rec(self.scan_data)) - del self.FILE_NAMES_helper + del self.filenames_helper del self.scan_data - def clone_record_rec(self,cd_org,FILE_NAMES_org,tuple_like_data,keep_cd,keep_crc): + def clone_record_rec(self,cd_org,filenames_org,tuple_like_data,keep_cd,keep_crc): entry_LUT_decode_loc = entry_LUT_decode self_get_file_name = self.get_file_name self_clone_record_rec = self.clone_record_rec name_index,code,size,mtime = tuple_like_data[0:4] if name_index: - name = FILE_NAMES_org[name_index] + name = filenames_org[name_index] else: name='' @@ -619,7 +619,7 @@ def clone_record_rec(self,cd_org,FILE_NAMES_org,tuple_like_data,keep_cd,keep_crc if has_files: sub_new_list=[] for sub_structure in tuple_like_data[elem_index]: - sub_new_list.append(self_clone_record_rec(cd_org,FILE_NAMES_org,sub_structure,keep_cd,keep_crc)) + sub_new_list.append(self_clone_record_rec(cd_org,filenames_org,sub_structure,keep_cd,keep_crc)) elem_index+=1 new_list.append(tuple(sorted( sub_new_list,key = lambda x : x[1:4] ))) @@ -638,16 +638,16 @@ def clone_record_rec(self,cd_org,FILE_NAMES_org,tuple_like_data,keep_cd,keep_crc def clone_record(self,file_path,keep_cd=True,keep_crc=True,compression_level=16): self.decompress_filestructure() - self.decompress_custom_data() + self.decompress_customdata() new_record = LibrerRecord(self.header.label,file_path,self.log) new_record.header = self.header - new_record.FILE_NAMES = self.FILE_NAMES + new_record.filenames = self.filenames if keep_cd: - new_record.custom_data = self.custom_data + new_record.customdata = self.customdata - new_record.filestructure = self.clone_record_rec(self.custom_data,self.FILE_NAMES,self.filestructure,keep_cd,keep_crc) + new_record.filestructure = self.clone_record_rec(self.customdata,self.filenames,self.filestructure,keep_cd,keep_crc) new_record.save(file_path,compression_level) def find_items(self, @@ -667,7 +667,7 @@ def find_items(self, find_results = self.find_results = [] find_results_add = find_results.append - file_names_loc = self.FILE_NAMES + filenames_loc = self.filenames filestructure = self.filestructure self.files_search_progress = 0 @@ -676,7 +676,7 @@ def find_items(self, cd_search_kind_code = self.search_kind_code_tab[cd_search_kind] if cd_search_kind_code!=dont_kind_code: - self.decompress_custom_data() + self.decompress_customdata() entry_LUT_decode_loc = entry_LUT_decode @@ -688,7 +688,7 @@ def find_items(self, rgf_group = (regexp_kind_code,glob_kind_code,fuzzy_kind_code) - self_custom_data = self.custom_data + self_customdata = self.customdata while search_list: if self.abort_action: break @@ -711,7 +711,7 @@ def find_items(self, # print('format error:',data_entry_len,data_entry[0]) # continue - name = file_names_loc[name_nr] + name = filenames_loc[name_nr] is_dir,is_file,is_symlink,is_bind,has_cd,has_files,cd_ok,has_crc = entry_LUT_decode_loc[code] @@ -768,7 +768,7 @@ def find_items(self, continue elif cd_search_kind_code_is_rgf: if has_cd and cd_ok: - cd_data = self_custom_data[cd_nr] + cd_data = self_customdata[cd_nr] else: continue @@ -844,6 +844,15 @@ def prepare_info(self): info_list.append(f'file names :{bytes_to_str_mod(zip_file_info["filenames"]).rjust(14)}{bytes_to_str_mod(self.zipinfo["filenames"]).rjust(14)}') info_list.append(f'custom data :{bytes_to_str_mod(zip_file_info["customdata"]).rjust(14)}{bytes_to_str_mod(self.zipinfo["customdata"]).rjust(14)}') + info_list.append(f'\nquant_files:{fnumber(self_header.quant_files)}') + info_list.append(f'quant_folders:{fnumber(self_header.quant_folders)}') + info_list.append(f'sum_size:{bytes_to_str(self_header.sum_size)}') + + if self_header.cde_list: + info_list.append('\nCDE rules (draft):') + for nr,single_cde in enumerate(self_header.cde_list): + info_list.append(str(nr) + ':' + str(single_cde)) + self.txtinfo = '\n'.join(info_list) def save(self,file_path=None,compression_level=16): @@ -870,14 +879,14 @@ def save(self,file_path=None,compression_level=16): zip_file.writestr('filestructure',cctx.compress(filestructure_ser)) self.info_line = f'saving {filename} (File names)' - filenames_ser = dumps(self.FILE_NAMES) + filenames_ser = dumps(self.filenames) self.zipinfo['filenames'] = len(filenames_ser) zip_file.writestr('filenames',cctx.compress(filenames_ser)) self.info_line = f'saving {filename} (Custom Data)' - custom_data_ser = dumps(self.custom_data) - self.zipinfo['customdata'] = len(custom_data_ser) - zip_file.writestr('customdata',cctx.compress(custom_data_ser)) + customdata_ser = dumps(self.customdata) + self.zipinfo['customdata'] = len(customdata_ser) + zip_file.writestr('customdata',cctx.compress(customdata_ser)) self.prepare_info() @@ -926,7 +935,7 @@ def decompress_filestructure(self): self.zipinfo['filestructure'] = len(filestructure_ser) filenames_ser = dctx.decompress(zip_file.read('filenames')) - self.FILE_NAMES = loads(filenames_ser) + self.filenames = loads(filenames_ser) self.zipinfo['filenames'] = len(filenames_ser) self.decompressed_filestructure = True @@ -937,18 +946,18 @@ def decompress_filestructure(self): else: return False - decompressed_custom_data = False - def decompress_custom_data(self): - if not self.decompressed_custom_data: + decompressed_customdata = False + def decompress_customdata(self): + if not self.decompressed_customdata: dctx = ZstdDecompressor() with ZipFile(self.file_path, "r") as zip_file: customdata_ser_comp = zip_file.read('customdata') customdata_ser = dctx.decompress(customdata_ser_comp) - self.custom_data = loads( customdata_ser ) + self.customdata = loads( customdata_ser ) self.zipinfo['customdata'] = len(customdata_ser) - self.decompressed_custom_data = True + self.decompressed_customdata = True self.prepare_info() return True @@ -1023,29 +1032,6 @@ def read_records_pre(self): def abort(self): self.abort_action = True - def import_record(self,file_path): - new_record = self.create() - if new_record.load(file_path): #tylko dla testu - self.log.warning('import failed :%s',file_path) - self.records.remove(new_record) - return None - else: - new_file_tail = 'imported' + str(new_record.header.rid) + '.' + str(time()) + '.dat' - file_path_loc = sep.join([self.db_dir,new_file_tail]) - try: - shutil_copy(file_path,file_path_loc) - except Exception as e: - print(e) - return None - else: - new_record = self.create() - if new_record.load_wrap(self.db_dir,new_file_tail): - return None - else: - return new_record - - #self.records_to_show.append( (new_record,info_curr_quant,info_curr_size) ) - def read_records(self): self.log.info('read_records: %s',self.db_dir) self.records_to_show=[] diff --git a/src/librer.py b/src/librer.py index 24482a6..8279b36 100644 --- a/src/librer.py +++ b/src/librer.py @@ -602,8 +602,8 @@ def get_scan_dialog(self): mask_tooltip = "glob expresions separated by comma ','\ne.g. '*.7z, *.zip, *.gz'" min_tooltip = "Minimum size of file\nto aplly CD extraction or crc\nmay be empty e.g. 10k" max_tooltip = "Maximum size of file\nto aplly CD extraction or crc\nmay be empty e.g. '100MB'" - exec_tooltip = "executable or batch script that will be run\nwith file for extraction\nmay have parameters\nWill be executed with scanned file full path\ne.g. '7z l', 'cat', '~/my_extraction.sh', 'c:\\my_extraction.bat'" - open_tooltip = "set executable file as Custom Data Extractor..." + exec_tooltip = "Executable or batch script that will be run\nwith the file for extraction as last parameter.\nMay have other fixed parameters\nWill be executed with the full path of the scanned file\ne.g. '7z l', 'cat', 'my_extractor.sh', 'my_extractor.bat'" + open_tooltip = "Set executable file as Custom Data Extractor..." timeout_tooltip = "Timeout limit in seconds for single CD extraction.\nAfter timeout executed process will be terminated\n\n'0' or no value means no timeout" test_tooltip = "Test Custom Data Extractor\non single manually selected file ..." crc_tooltip = "Calculate CRC (SHA1) for all\nfiles matching glob and size cryteria\nIt may take a long time." @@ -773,7 +773,7 @@ def get_progress_dialog_on_find(self): return self.progress_dialog_on_find def export_to_file(self): - self.export_dialog_file = asksaveasfilename(parent = self.export_dialog.widget, initialfile = 'record.dat',defaultextension=".dat",filetypes=[("Dat Files","*.dat"),("All Files","*.*")]) + self.export_dialog_file = asksaveasfilename(initialdir=self.last_dir,parent = self.export_dialog.widget, initialfile = 'record.dat',defaultextension=".dat",filetypes=[("Dat Files","*.dat"),("All Files","*.*")]) self.export_dialog.hide() def export_comp_set(self): @@ -826,7 +826,7 @@ def get_export_dialog(self): return self.export_dialog def import_from_file(self): - self.import_dialog_file = askopenfilename(parent = self.import_dialog.widget, initialfile = 'record.dat',defaultextension=".dat",filetypes=[("Dat Files","*.dat"),("All Files","*.*")]) + self.import_dialog_file = askopenfilename(initialdir=self.last_dir,parent = self.import_dialog.widget, initialfile = 'record.dat',defaultextension=".dat",filetypes=[("Dat Files","*.dat"),("All Files","*.*")]) self.import_dialog.hide() def import_comp_set(self): @@ -1160,6 +1160,7 @@ def record_export(self): self.current_record.clone_record(self.export_dialog_file,keep_cd,keep_crc,self.export_compr_var_int.get()) + self.last_dir = dirname(self.export_dialog_file) #self.current_record.save(self.export_dialog_file) @restore_status_line @@ -1189,7 +1190,7 @@ def record_import(self): new_record.load_wrap(DB_DIR,local_file_name) self.single_record_show(new_record) - + self.last_dir = dirname(self.import_dialog_file) def __init__(self,cwd): self.cwd=cwd @@ -1323,6 +1324,8 @@ def __init__(self,cwd): style_configure("Treeview",rowheight=18) style.configure("TScale", background=self.bg_color) + style.configure('TScale.slider', background=self.bg_color) + style.configure('TScale.Horizontal.TScale', background=self.bg_color) bg_focus='#90DD90' bg_focus_off='#90AA90' @@ -1456,13 +1459,18 @@ def file_cascade_post(): if self.actions_processing: self_file_cascade_add_command = self.file_cascade.add_command self_file_cascade_add_separator = self.file_cascade.add_separator + state_on_records = 'normal' if librer_core.records else 'disabled' item_actions_state=('disabled','normal')[self.sel_item is not None] self_file_cascade_add_command(label = 'New Record ...',command = self.scan_dialog_show, accelerator="Ctrl+N",image = self.ico_record,compound='left') - self_file_cascade_add_command(label = 'Export record ...', accelerator='Ctrl+E', command = self.record_export,image = self.ico_empty,compound='left') + + self_file_cascade_add_separator() + self_file_cascade_add_command(label = 'Export record ...', accelerator='Ctrl+E', command = self.record_export,image = self.ico_empty,compound='left',state=state_on_records) self_file_cascade_add_command(label = 'Import record ...', accelerator='Ctrl+I', command = self.record_import,image = self.ico_empty,compound='left') - self_file_cascade_add_command(label = 'Record Info ...', accelerator='Alt+Enter', command = self.record_info,image = self.ico_empty,compound='left') - self_file_cascade_add_command(label = 'Show Custom Data ...', accelerator='Enter', command = self.show_custom_data, image = self.ico_empty,compound='left') + self_file_cascade_add_separator() + self_file_cascade_add_command(label = 'Record Info ...', accelerator='Alt+Enter', command = self.record_info,image = self.ico_empty,compound='left',state=state_on_records) + self_file_cascade_add_separator() + self_file_cascade_add_command(label = 'Show Custom Data ...', accelerator='Enter', command = self.show_customdata, image = self.ico_empty,compound='left',state=state_on_records) self_file_cascade_add_separator() self_file_cascade_add_command(label = 'Find ...',command = self.finder_wrapper_show, accelerator="Ctrl+F",image = self.ico_find,compound='left',state = 'normal' if self.sel_item is not None and self.current_record else 'disabled') self_file_cascade_add_separator() @@ -1682,7 +1690,7 @@ def help_cascade_post(): self_main_bind('', lambda event : self.record_import()) self_main_bind('', lambda event : self.record_info()) - self_main_bind('', lambda event : self.show_custom_data()) + self_main_bind('', lambda event : self.show_customdata()) self_main_bind('', lambda event : self.delete_data_record()) @@ -1787,7 +1795,7 @@ def show_tooltips_tree(self,event): cd_nr = fifth_field node_cd = ' Double click to open custom data for file.' - #if cd_data := record.custom_data[cd_nr]: + #if cd_data := record.customdata[cd_nr]: # #print('a1',cd_nr,cd_data) # cd_txt = cd_data #record.get_cd_text(cd_data) @@ -2609,6 +2617,7 @@ def context_menu_show(self,event): pop_add_cascade = pop.add_cascade pop_add_command = pop.add_command self_ico = self.ico + state_on_records = 'normal' if librer_core.records else 'disabled' c_nav = Menu(self.menubar,tearoff=0,bg=self.bg_color) c_nav_add_command = c_nav.add_command @@ -2621,12 +2630,16 @@ def context_menu_show(self,event): c_nav_add_command(label = 'Go to last record' ,command = lambda : self.goto_first_last_record(-1), accelerator="End",state='normal', image = self.ico_empty,compound='left') pop_add_command(label = 'New record ...', command = self.scan_dialog_show,accelerator='Ctrl+N',image = self_ico['record'],compound='left') - pop_add_command(label = 'Export record ...', accelerator='Ctrl+E', command = self.record_export,image = self.ico_empty,compound='left') + pop_add_separator() + pop_add_command(label = 'Show Custom Data ...', accelerator='Enter', command = self.show_customdata,image = self.ico_empty,compound='left',state=state_on_records) + pop_add_command(label = 'Export record ...', accelerator='Ctrl+E', command = self.record_export,image = self.ico_empty,compound='left',state=state_on_records) pop_add_command(label = 'Import record ...', accelerator='Ctrl+I', command = self.record_import,image = self.ico_empty,compound='left') - pop_add_command(label = 'Record Info ...', accelerator='Alt+Enter', command = self.record_info,image = self.ico_empty,compound='left') - pop_add_command(label = 'Show Custom Data ...', accelerator='Enter', command = self.show_custom_data,image = self.ico_empty,compound='left') pop_add_separator() - pop_add_command(label = 'Delete record ...',command = self.delete_data_record,accelerator="Delete",image = self.ico['delete'],compound='left') + pop_add_command(label = 'Record Info ...', accelerator='Alt+Enter', command = self.record_info,image = self.ico_empty,compound='left',state=state_on_records) + pop_add_separator() + pop_add_command(label = 'Delete record ...',command = self.delete_data_record,accelerator="Delete",image = self.ico['delete'],compound='left',state=state_on_records) + pop_add_separator() + pop_add_command(label = 'Show Custom Data ...', accelerator='Enter', command = self.show_customdata,image = self.ico_empty,compound='left',state=state_on_records) pop_add_separator() pop_add_command(label = 'Copy full path',command = self.clip_copy_full_path_with_file,accelerator='Ctrl+C',state = 'normal' if (self.sel_kind and self.sel_kind!=self.RECORD) else 'disabled', image = self.ico_empty,compound='left') @@ -2766,8 +2779,9 @@ def scan_dialog_hide_wrapper(self): @restore_status_line @logwrapper def scan(self,compression_level): + #self.cfg.write() + #self.status('Scanning...') - self.cfg.write() #librer_core.reset() #self.status_path_configure(text='') @@ -2787,6 +2801,8 @@ def scan(self,compression_level): self.get_info_dialog_on_scan().show('Error. No paths to scan.','Add paths to scan.') return False + self.last_dir = path_to_scan_from_entry + new_record = librer_core.create(self.scan_label_entry_var.get(),path_to_scan_from_entry) self.main_update() @@ -2881,12 +2897,12 @@ def scan(self,compression_level): if self.CDE_use_var_list[e].get(): any_cde_enabled=True cde_list.append( ( - [elem.strip() for elem in mask.split(',')], + tuple([elem.strip() for elem in mask.split(',')]), True if smin_int>=0 else False, smin_int, True if smax_int>=0 else False, smax_int, - exe.split(), + tuple(exe.split()), timeout_int, crc ) ) @@ -2895,7 +2911,7 @@ def scan(self,compression_level): check_dev = self.cfg_get_bool(CFG_KEY_SINGLE_DEVICE) ############################# - scan_thread=Thread(target=lambda : new_record.scan(cde_list,check_dev),daemon=True) + scan_thread=Thread(target=lambda : new_record.scan(tuple(cde_list),check_dev),daemon=True) scan_thread.start() scan_thread_is_alive = scan_thread.is_alive @@ -2968,7 +2984,7 @@ def scan(self,compression_level): self_tooltip_message[str_self_progress_dialog_on_scan_abort_button]='If you abort at this stage,\nCustom data will be incomlplete.' - cd_thread=Thread(target=lambda : new_record.extract_custom_data(),daemon=True) + cd_thread=Thread(target=lambda : new_record.extract_customdata(),daemon=True) cd_thread.start() cd_thread_is_alive = cd_thread.is_alive @@ -3147,7 +3163,7 @@ def exclude_mask_update(self) : def set_path_to_scan(self): initialdir = self.last_dir if self.last_dir else self.cwd if res:=askdirectory(title='Select Directory',initialdir=initialdir,parent=self.scan_dialog.area_main): - self.last_dir=res + self.last_dir = res self.path_to_scan_entry_var.set(normpath(abspath(res))) def cde_test(self,e): @@ -3228,12 +3244,12 @@ def access_filestructure(self,record): @restore_status_line @block_actions_processing @gui_block - def access_custom_data(self,record): + def access_customdata(self,record): self.hide_tooltip() self.popup_unpost() self.status('loading custom data ...') self.main.update() - record.decompress_custom_data() + record.decompress_customdata() @block_actions_processing @gui_block @@ -3446,11 +3462,11 @@ def double_left_button(self,event): tree=event.widget if tree.identify("region", event.x, event.y) != 'heading': if item:=tree.identify('item',event.x,event.y): - self.main.after_idle(self.show_custom_data) + self.main.after_idle(self.show_customdata) #return "break" - def show_custom_data(self): + def show_customdata(self): item=self.tree.focus() if item: try: @@ -3465,9 +3481,9 @@ def show_custom_data(self): if has_cd: #wiec nie has_files cd_index = data_tuple[4] - self.access_custom_data(record) + self.access_customdata(record) - if cd_data := record.custom_data[cd_index]: + if cd_data := record.customdata[cd_index]: cd_txt = cd_data self.get_text_info_dialog().show('Custom Data',cd_txt)