diff --git a/README.md b/README.md index 70c4ea5..f6b2820 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ Portable executable packages created with [PyInstaller](https://pyinstaller.org/ https://github.com/PJDude/librer/releases +## MAJORGEEKS review: +https://www.majorgeeks.com/files/details/librer.html + +## SOFTPEDIA review: +https://www.softpedia.com/get/Others/File-CD-DVD-Catalog/Librer.shtml + ## [Tutorial](./info/tutorial.md) ## ## Guidelines for crafting custom data extractors @@ -40,6 +46,8 @@ Custom data extractor is a command that can be invoked with a single parameter - ## Portability **librer** writes log files, configuration and record files in runtime. Default location for these files is **logs** and **data** subfolders of **librer** main directory. +## [Importing data from "Where Is It?"](./info/wii_import.md) ## + ## 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. 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) module. Searching records is performed as a separate subprocess for each record. The number of parallel searches is limited by the CPU cores. diff --git a/info/wii_01.png b/info/wii_01.png new file mode 100644 index 0000000..a6e7252 Binary files /dev/null and b/info/wii_01.png differ diff --git a/info/wii_02.png b/info/wii_02.png new file mode 100644 index 0000000..ac7d8c8 Binary files /dev/null and b/info/wii_02.png differ diff --git a/info/wii_import.md b/info/wii_import.md new file mode 100644 index 0000000..eed85fb --- /dev/null +++ b/info/wii_import.md @@ -0,0 +1,22 @@ +Librer allows to import data from "Where Is It?" software indirectly by reading it's xml report file. + +### This feature is under development + +How to import data ? You need to save information about all files catalogued by "Where Is It?" + +in "Where Is It?": +- Start Report Generator Wizard +- on "Select report type" page select the Pre-defined report "List of all cataloged files and folders on selected disks" +![image info](./wii_01.png) + +- In the Report Generator window, select export to "XML file". All options set as below: +![image info](./wii_02.png) + +- clik Export and save XML file + +in Librer use File menu action: 'Import "Where Is It?" xml ...' + +## unregistered Where Is It? version issue +Unregistered version of WII seems to pollute its own report by replacing some data with ```*** DEMO ***``` string. Imported data from such report will be obviously incomplete, however the ```*** DEMO ***``` string seems to be inserted randomly, so in sequential writes it appears in different sections. After couple of exports, with a little luck pool of report files will contain all data scattered in different files. Import all that reports at once in single shot (!) to merge the data into complete dataset. To do that use multiselection on import dialog. Incomplete data will be ignored and will not apear in librer records. + + diff --git a/src/core.py b/src/core.py index 4b27319..b3a9899 100644 --- a/src/core.py +++ b/src/core.py @@ -55,6 +55,8 @@ if windows: from signal import CTRL_C_EVENT +from re import compile as re_compile + from pickle import dumps,loads from zstandard import ZstdCompressor,ZstdDecompressor from pympler.asizeof import asizeof @@ -288,6 +290,8 @@ def __init__(self,label='',scan_path=''): self.zipinfo = {} + self.scanning_time=0 + self.compression_level=0 self.creation_os,self.creation_host = f'{platform_system()} {platform_release()}',platform_node() @@ -375,6 +379,22 @@ def compress_with_header_update_wrapp(data,datalabel): compress_with_header_update_wrapp(self.header,'header') + if False: + with open(file_path+'.debug.txt', "w") as txt_file: + #txt_file.write(str(self.header) + '\n') + + txt_file.write('-------------------------------\nheader:\n\n') + for k,v in vars(self.header).items(): + txt_file.write(f' {k}:{v}\n') + + txt_file.write('\n\n-------------------------------\nfilestructure:\n\n') + txt_file.write(str(self.filestructure)) + txt_file.write('\n\n-------------------------------\nfilenames:\n\n') + txt_file.write(str(self.filenames)) + if self.customdata: + txt_file.write('\n-------------------------------\ncustomdata\n') + txt_file.write(self.customdata) + self.prepare_info() print_func(['save','finished'],True) @@ -765,10 +785,14 @@ def tupelize_rec(self,scan_like_data,results_queue_put): try: (size,is_dir,is_file,is_symlink,is_bind,has_files,mtime) = items_list[0:7] - elem_index = 7 - if has_files: - sub_dict = items_list[elem_index] - elem_index+=1 + try: + elem_index = 7 + if has_files: + sub_dict = items_list[elem_index] + elem_index+=1 + except: + #print('f1 error') + sub_dict={} try: info_dict = items_list[elem_index] @@ -1278,6 +1302,266 @@ def read_records_pre(self): self.log.error('list read error:%s' % e ) return (0,0) + def get_wii_files_dict(self,import_filenames): + re_obj_item = re_compile(r'') + re_obj_item_end = re_compile(r'/ITEM>') + + re_obj_name = re_compile(r'(.+)') + re_obj_ext = re_compile(r'(.+)') + re_obj_size = re_compile(r'(.+)') + re_obj_date = re_compile(r'(.+)') + re_obj_disk_name = re_compile(r'(.+)') + re_obj_disk_type = re_compile(r'(.+)') + re_obj_disk_num = re_compile(r'(.+)') + re_obj_disk_location = re_compile(r'(.+)') + re_obj_path = re_compile(r'(.+)') + re_obj_time = re_compile(r'') + re_obj_crc = re_compile(r'(.+)') + re_obj_category = re_compile(r'(.+)') + re_obj_flag = re_compile(r'(.+)') + re_obj_desc = re_compile(r'(.+)') + re_obj_desc_begin = re_compile(r'(.*)') + re_obj_desc_end = re_compile(r'(.*)') + + l=0 + in_item=False + in_description=False + + demo_str = '*** DEMO ***' + + wii_paths_dict = {} + wii_path_tuple_to_data = {} + + try: + filenames_set=set() + for import_filename in import_filenames: + with open(import_filename,"rt", encoding='utf-8', errors='ignore') as f: + for line in f: + if match := re_obj_item.search(line): + item={} + item['type']=match.group(1) + item['ext'] = '' + item['description'] = [] + + in_item=True + in_description=False + + + elif match := re_obj_item_end.search(line): + in_item=False + in_description=False + + if item['disk_name']!=demo_str and item['path']!=demo_str and item['name']!=demo_str and item['ext']!=demo_str: + #path_splitted = [item['disk_name'] + ':'] + item['path'].strip('\\').split('\\') + [item['name']] + path_splitted = [item['disk_name']] + item['path'].strip('\\').split('\\') + [item['name'] + item['ext']] + path_splitted = [path_elem for path_elem in path_splitted if path_elem] + + path_splitted_len = len(path_splitted) + path_splitted_tuple = tuple(path_splitted) + + #print(f'{path_splitted=}') + + next_dict = wii_paths_dict + for ps_i in range(path_splitted_len): + filename = path_splitted[ps_i] + filenames_set.add(filename) + + if filename not in next_dict: + next_dict[filename] = {} + + next_dict = next_dict[filename] + + size = item['size'] + is_dir = bool(item['type']=="Folder") + is_file = bool(item['type']=="File") + is_symlink=False + is_bind=False + has_files=False + mtime=1234 + + #scan_like_data tuple + sld_tuple = tuple([size,is_dir,is_file,is_symlink,is_bind,has_files,mtime]) + #print(f'{path_splitted=} : {sld_tuple=}') + + wii_path_tuple_to_data[path_splitted_tuple] = sld_tuple + + elif match := re_obj_name.search(line): + item['name']=match.group(1) + elif match := re_obj_ext.search(line): + ext = match.group(1) + if ext!=demo_str: + item['ext']='.' + ext + elif match := re_obj_size.search(line): + try: + item['size']=int(match.group(1)) + except: + #print(f'wrong size: {match.group(1)}') + item['size']=333222 + elif match := re_obj_date.search(line): + item['date']=match.group(1) + elif match := re_obj_disk_name.search(line): + item['disk_name']=match.group(1) + elif match := re_obj_disk_type.search(line): + item['disk_type']=match.group(1) + elif match := re_obj_disk_num.search(line): + item['disk_num']=match.group(1) + elif match := re_obj_disk_location.search(line): + item['disk_loc']=match.group(1) + elif match := re_obj_path.search(line): + item['path']=match.group(1) + elif match := re_obj_time.search(line): + item['time']=match.group(1) + elif match := re_obj_crc.search(line): + item['crc']=match.group(1) + elif match := re_obj_category.search(line): + item['category']=match.group(1) + elif match := re_obj_flag.search(line): + item['flag']=match.group(1) + elif match := re_obj_desc.search(line): + item['desc']=match.group(1) + elif match := re_obj_desc_begin.search(line): + in_description=True + item['description'].append(match.group(1)) + elif match := re_obj_desc_end.search(line): + in_description=False + item['description'].append(match.group(1)) + item['description'] = tuple(item['description']) + elif in_description: + item['description'].append(line) + elif lstrip:=line.strip(): + #pass + print('IGNORING:',lstrip) + + l+=1 + + # + # $Recycle.Bin + # 1918697428 + # 2023-04-27 + # c + # Hard disk + # \ + # + # 1 + # + # 0 + # + + return filenames_set,wii_path_tuple_to_data,wii_paths_dict + except: + return {{},{},{}} + + def wii_data_to_scan_like_data(self,path_list,curr_dict_ref,scan_like_data): + path_list_tuple = tuple(path_list) + + anything = False + try: + for name,val in curr_dict_ref.items(): + + anything = True + + dict_entry={} + + sub_path_list = path_list + [name] + sub_path_list_tuple = tuple(sub_path_list) + + try: + size,is_dir,is_file,is_symlink,is_bind,has_files,mtime = self.wii_path_tuple_to_data[sub_path_list_tuple] + except Exception as e1: + print(f'{e1=}') + #tylko topowy ? + size=1 + is_dir = True + is_file = False + is_symlink = False + is_bind = False + mtime = 4568 + + if is_dir: + self.wii_data_to_scan_like_data(sub_path_list,val,dict_entry) + + if dict_entry: + has_files = True + else: + has_files = False + + temp_list_ref = scan_like_data[name] = [size,is_dir,is_file,is_symlink,is_bind,has_files,mtime] + + if dict_entry: + temp_list_ref.append(dict_entry) + + except Exception as e: + print('wii_data_to_scan_like_data error:',e) + pass + + return anything + + def import_records_wii_scan(self,import_filenames): + self.log.info(f'import_records_wii:{",".join(import_filenames)}') + + demo_str = '*** DEMO ***' + + quant = len(import_filenames) + + filenames_set,wii_path_tuple_to_data,wii_paths_dict = self.get_wii_files_dict(import_filenames) + + quant_files,quant_folders = 0,0 + for k,v in wii_path_tuple_to_data.items(): + size,is_dir,is_file,is_symlink,is_bind,has_files,mtime = v + if is_dir: + quant_folders+=1 + elif is_file: + quant_files+=1 + + return quant_files,quant_folders,filenames_set,wii_path_tuple_to_data,wii_paths_dict + + def import_records_wii_do(self,quant_files,quant_folders,filenames_set,wii_path_tuple_to_data,wii_paths_dict,update_callback): + import_res=[] + + self.wii_path_tuple_to_data = wii_path_tuple_to_data + self.wii_paths_dict=wii_paths_dict + + scan_like_data={} + self.wii_data_to_scan_like_data([],self.wii_paths_dict,scan_like_data) + + new_record = self.create() + new_record.filenames = tuple(sorted(list(filenames_set))) + new_record.header.label = 'WII import' + new_record.header.scan_path = 'WII import' + new_record.filenames_helper = {fsname:fsname_index for fsname_index,fsname in enumerate(new_record.filenames)} + + new_record.header.quant_files = quant_files + new_record.header.quant_folders = quant_folders + + ################################## + size,mtime = 0,0 + is_dir = True + is_file = False + is_symlink = False + is_bind = False + has_cd = False + has_files = True + cd_ok = False + has_crc = False + + new_record.header.references_names=0 + new_record.header.references_cd=0 + + code = LUT_encode[ (is_dir,is_file,is_symlink,is_bind,has_cd,has_files,cd_ok,has_crc,False,False) ] + new_record.filestructure = ('',code,size,mtime,new_record.tupelize_rec(scan_like_data,print)) + + new_file_path = sep.join([self.db_dir,f'wii.{int(time())}.dat']) + print(f'{new_file_path=}') + + new_record.save(print,file_path=new_file_path) + + update_callback(new_record) + + if import_res: + return '\n'.join(import_res) + else: + return None + def import_records(self,import_filenames,update_callback): self.log.info(f"import {','.join(import_filenames)}") @@ -1510,6 +1794,7 @@ def find_results_clean(self): stdout_files_cde_size_sum=0 ######################################################################################################################## + def create_new_record(self,temp_dir,update_callback): self.log.info(f'create_new_record') self_log_info = self.log.info diff --git a/src/librer.py b/src/librer.py index 3d15a19..1a0d91f 100644 --- a/src/librer.py +++ b/src/librer.py @@ -1388,6 +1388,9 @@ def repack_to_local(self): def repack_comp_set(self): self.repack_compr_var_int.set(int(self.repack_compr_var.get())) + def wii_import_comp_set(self): + self.wii_import_compr_var_int.set(int(self.wii_import_compr_var.get())) + repack_dialog_created = False @restore_status_line @block_actions_processing @@ -1439,6 +1442,64 @@ def get_repack_dialog(self): self.repack_dialog_created = True return self.repack_dialog + wii_import_dialog_created = False + @restore_status_line + @block_actions_processing + @gui_block + def get_wii_import_dialog(self): + self.wii_import_dialog_do_it=False + + if not self.wii_import_dialog_created: + self.wii_import_dialog=GenericDialog(self.main,(self.ico_librer,self.ico_librer_small),self.bg_color,'Where Is It ? import records',pre_show=self.pre_show,post_close=self.post_close,min_width=400,min_height=200) + self.wii_import_separate = BooleanVar() + self.wii_import_separate.set(False) + self.wii_import_compr_var = IntVar() + self.wii_import_compr_var_int = IntVar() + self.wii_import_label_var = StringVar() + + self.wii_import_compr_var.set(9) + self.wii_import_compr_var_int.set(9) + self.wii_import_label_var.set('WII-imported') + + self.wii_import_brief_label=Label(self.wii_import_dialog.area_main,text='',bd=2,bg=self.bg_color,takefocus=False,relief='groove',anchor='w',justify='left') + self.wii_import_brief_label.grid(row=0,column=0,sticky='news',padx=4,pady=4,columnspan=2) + try: + self.wii_import_brief_label.configure(font=('Courier', 10)) + except: + try: + self.wii_import_brief_label.configure(font=('TkFixedFont', 10)) + except: + pass + + (label_frame := LabelFrame(self.wii_import_dialog.area_main,text='Record Label',bd=2,bg=self.bg_color,takefocus=False)).grid(row=1,column=0,sticky='news',padx=4,pady=4,columnspan=2) + Entry(label_frame,textvariable=self.wii_import_label_var).pack(expand='yes',fill='x',padx=2,pady=2) + + (wii_import_frame := LabelFrame(self.wii_import_dialog.area_main,text='Options',bd=2,bg=self.bg_color,takefocus=False)).grid(row=2,column=0,sticky='news',padx=4,pady=4,columnspan=2) + self.wii_import_dialog.area_main.grid_columnconfigure( 0, weight=1) + self.wii_import_dialog.area_main.grid_columnconfigure( 1, weight=1) + + self.wii_import_dialog.area_main.grid_rowconfigure( 2, weight=1) + + self.wii_import_separate_cb = Checkbutton(wii_import_frame,text='create separate records',variable=self.wii_import_separate) + #self.wii_import_crc_cb = Checkbutton(wii_import_frame,text='Include CRC values',variable=self.wii_import_crc_var) + + self.wii_import_separate_cb.grid(row=0, column=0, sticky='wens',padx=4,pady=4) + #self.wii_import_crc_cb.grid(row=1, column=0, sticky='wens',padx=4,pady=4) + + wii_import_frame.grid_columnconfigure( 0, weight=1) + + (wii_import_frame_compr := LabelFrame(self.wii_import_dialog.area_main,text='Compression (0-22)',bd=2,bg=self.bg_color,takefocus=False)).grid(row=3,column=0,sticky='news',padx=4,pady=4,columnspan=2) + + Scale(wii_import_frame_compr, variable=self.wii_import_compr_var, orient='horizontal',from_=0, to=22,command=lambda x : self.wii_import_comp_set(),style="TScale").pack(fill='x',side='left',expand=1,padx=2) + Label(wii_import_frame_compr, textvariable=self.wii_import_compr_var_int,width=3,bg=self.bg_color,relief='ridge').pack(side='right',padx=2,pady=2) + + Button(self.wii_import_dialog.area_buttons, text='Proceed', width=14 , command= self.wii_import_to_local).pack(side='left', anchor='n',padx=5,pady=5) + Button(self.wii_import_dialog.area_buttons, text='Close', width=14, command=self.wii_import_dialog.hide ).pack(side='right', anchor='n',padx=5,pady=5) + + self.wii_import_dialog_created = True + + return self.wii_import_dialog + find_dialog_created = False @restore_status_line @block_actions_processing @@ -1788,162 +1849,44 @@ def record_repack(self): self.find_clear() self.info_dialog_on_main.show('Repacking finished.','Check repacked record\nDelete original record manually if you want.') + def wii_import_to_local(self): + self.wii_import_dialog_do_it=True + self.wii_import_dialog.hide() + @restore_status_line @block_actions_processing @gui_block def record_import_wii(self): initialdir = self.last_dir if self.last_dir else self.cwd - - if import_filenames := askopenfilenames(initialdir=self.last_dir,parent = self.main,title='Choose "Where Is It?" Report xml file to import', defaultextension=".xml",filetypes=[("XML Files","*.xml"),("All Files","*.*")]): + self.wii_import_dialog_do_it= False + if import_filenames := askopenfilenames(initialdir=self.last_dir,parent = self.main,title='Choose "Where Is It?" Report xml files to import', defaultextension=".xml",filetypes=[("XML Files","*.xml"),("All Files","*.*")]): self.last_dir = dirname(import_filenames[0]) - try: - re_obj_item = re_compile(r'') - re_obj_item_end = re_compile(r'/ITEM>') - - re_obj_name = re_compile(r'(.+)') - re_obj_ext = re_compile(r'(.+)') - re_obj_size = re_compile(r'(.+)') - re_obj_date = re_compile(r'(.+)') - re_obj_disk_name = re_compile(r'(.+)') - re_obj_disk_type = re_compile(r'(.+)') - re_obj_disk_num = re_compile(r'(.+)') - re_obj_disk_location = re_compile(r'(.+)') - re_obj_path = re_compile(r'(.+)') - re_obj_time = re_compile(r'') - re_obj_crc = re_compile(r'(.+)') - re_obj_category = re_compile(r'(.+)') - re_obj_flag = re_compile(r'(.+)') - re_obj_desc = re_compile(r'(.+)') - re_obj_desc_begin = re_compile(r'(.*)') - re_obj_desc_end = re_compile(r'(.*)') - - l=0 - in_item=False - in_description=False - - demo_str = '*** DEMO ***' - - #known_discs = set() - #known_items = set() - - wii_paths_dict = {} - - def print_dir_structure(file_handle,curr_dict_ref,indent=''): - try: - for key,val in curr_dict_ref.items(): - if not indent: - file_handle.write('\n==================================\n') - file_handle.write(indent + str(key) + '\n') - print_dir_structure(file_handle,val,indent + ' ') - except Exception as e: - pass - - def wii_paths_dict_add(path_elem,curr_dict_ref): - if path_elem not in curr_dict_ref: - curr_dict_ref[path_elem] = {} - - return curr_dict_ref[path_elem] - - for import_filename in import_filenames: - with open(import_filename,"rt", encoding='utf-8', errors='ignore') as f: - for line in f: - if match := re_obj_item.search(line): - in_item=True - in_description=False - item={} - item['description'] = [] - - item['type']=match.group(1) - elif match := re_obj_item_end.search(line): - in_item=False - in_description=False - - if item['disk_name']!=demo_str and item['path']!=demo_str: - path_splitted = [item['disk_name'] + ':'] + item['path'].strip('\\').split('\\') - path_splitted_len = len(path_splitted) - #print(f'{path_splitted=}') - - next_dict = wii_paths_dict - for ps_i in range(path_splitted_len): - next_dict = wii_paths_dict_add(path_splitted[ps_i],next_dict) - - elif match := re_obj_name.search(line): - item['name']=match.group(1) - elif match := re_obj_ext.search(line): - item['ext']=match.group(1) - elif match := re_obj_size.search(line): - item['size']=match.group(1) - elif match := re_obj_date.search(line): - item['date']=match.group(1) - elif match := re_obj_disk_name.search(line): - item['disk_name']=match.group(1) - elif match := re_obj_disk_type.search(line): - item['disk_type']=match.group(1) - elif match := re_obj_disk_num.search(line): - item['disk_num']=match.group(1) - elif match := re_obj_disk_location.search(line): - item['disk_loc']=match.group(1) - elif match := re_obj_path.search(line): - item['path']=match.group(1) - elif match := re_obj_time.search(line): - item['time']=match.group(1) - elif match := re_obj_crc.search(line): - item['crc']=match.group(1) - elif match := re_obj_category.search(line): - item['category']=match.group(1) - elif match := re_obj_flag.search(line): - item['flag']=match.group(1) - elif match := re_obj_desc.search(line): - item['desc']=match.group(1) - elif match := re_obj_desc_begin.search(line): - in_description=True - item['description'].append(match.group(1)) - elif match := re_obj_desc_end.search(line): - in_description=False - item['description'].append(match.group(1)) - item['description'] = tuple(item['description']) - elif in_description: - item['description'].append(line) - elif lstrip:=line.strip(): - #pass - print('IGNORING:',lstrip) - - l+=1 - - - #xml_content = fb.read().decode('utf-8', errors='replace') - - #print(f'{xml_content=}') - - #root = ET.fromstring(xml_content) - - # - # $Recycle.Bin - # 1918697428 - # 2023-04-27 - # c - # Hard disk - # \ - # - # 1 - # - # 0 - # - - - #librer_core.wii_xml_import(root,self.single_record_show) - #print('done.',l,'known_items_len:',len(known_items)) - - #proof of concept - if poc_file_path := asksaveasfilename(initialdir=self.last_dir,parent = self.main, initialfile = 'decoded_wii.txt',defaultextension=".txt",filetypes=[("Text Files","*.txt"),("All Files","*.*")]): - self.last_dir = dirname(poc_file_path) - with open(poc_file_path,'w') as fw: - fw.write('Where Is It? - report decoding - proof of concept\n') - fw.write('=================================================\n\n') - print_dir_structure(fw,wii_paths_dict) - - except Exception as ie: - print(f'WII Import error:{ie}') + + quant_files,quant_folders,filenames_set,wii_path_tuple_to_data,wii_paths_dict = librer_core.import_records_wii_scan(import_filenames) + + if quant_files==0 or quant_folders==0: + self.info_dialog_on_main.show('Where Is It? Import failed',"No files / No folders") + else: + ########################### + dialog = self.get_wii_import_dialog() + + #self.wii_import_label_var.set(self.current_record.header.label) + self.wii_import_compr_var.set(9) + self.wii_import_compr_var_int.set(9) + self.wii_import_brief_label.configure(text=f'GATHERED DATA:\nfiles : {fnumber(quant_files)}\nfolders : {fnumber(quant_folders)}') + + dialog.show() + + if self.wii_import_dialog_do_it: + res = librer_core.import_records_wii_do(quant_files,quant_folders,filenames_set,wii_path_tuple_to_data,wii_paths_dict,self.single_record_show) + + if not res: + ########################### + self.info_dialog_on_main.show('Where Is It? Import','Successful.') + self.find_clear() + else: + self.info_dialog_on_main.show('Where Is It? Import failed',res) + @restore_status_line @block_actions_processing @@ -2528,6 +2471,7 @@ def find_items(self): files_search_quant = sum([record.header.quant_files+record.header.quant_folders for record in sel_range]) if files_search_quant==0: + self.info_dialog_on_find.show('Search aborted.','No files in records.') return 1 if find_filename_search_kind == 'regexp': @@ -2692,6 +2636,7 @@ def find_items(self): records_len = len(librer_core.records) if records_len==0: + self.info_dialog_on_find.show('Search aborted.','No records.') return self_progress_dialog_on_find_progr1var_set = self.progress_dialog_on_find.progr1var.set @@ -2805,6 +2750,8 @@ def find_items(self): if find_results_quant_sum_format: self.find_result_index=-1 self.find_next() + else: + self.info_dialog_on_find.show('Search aborted.','Same params') def get_child_of_name(self,record,item,child_name): self_tree = self.tree