#!/usr/bin/python3 import ast,hashlib,os,random,re,secrets,shutil,subprocess,sys,tempfile from uuid import uuid4 import sqlite3 as sql from base64 import b64encode,b64decode from Cryptodome.Cipher import AES ROOT_DIR=os.getcwd() # The directory where all directories and files of the index are located INDEX_FILE=ROOT_DIR+"/index.db" # The database file LINUX_APP_STARTER="xdg-open" # The command which opens the files in the default application ENCRYPT=True # True or False; Whether the default is to encrypt the file or to save it as a plain file MAX_ITEMS=10 # Maximal amount of shown entries for selection class database(): def __init__(self,filepath = INDEX_FILE): self.connection=None self.crsr=None if not os.path.exists(filepath) : self.create_database(filepath) else: self.connection = sql.connect(filepath) self.crsr = self.connection.cursor() def add_index(self,vallist,collist=""): collist=self.collist if not collist else collist # compile the options into a command for the SQLite database colstring=",".join(collist) valstring='"{}"'.format('","'.join(vallist)) self.crsr.execute("""INSERT INTO {table} ({cols}) VALUES ({vals}); """.format(table=self.name,cols=colstring,vals=valstring)) self.connection.commit() def create_database(self, filepath): # create the database and tables self.connection = sql.connect(filepath) self.crsr = self.connection.cursor() sqlcommand = """CREATE TABLE FILES( FILE TEXT PRIMARY KEY NOT NULL, HASH TEXT NOT NULL, TITLE TEXT , SOURCE TEXT , CATEGORY TEXT NOT NULL, TAGS TEXT , CONTENT TEXT ); """ self.crsr.execute(sqlcommand) sqlcommand = """CREATE TABLE CATEGORY( NAME TEXT PRIMARY KEY NOT NULL, ALIAS TEXT ); """ self.crsr.execute(sqlcommand) sqlcommand = """CREATE TABLE TAGS( NAME TEXT PRIMARY KEY NOT NULL, ALIAS TEXT ); """ self.crsr.execute(sqlcommand) sqlcommand = """CREATE TABLE ENCRYPTION( NAME TEXT PRIMARY KEY NOT NULL, CIPHER TEXT NOT NULL, PASSWORD TEXT ); """ self.crsr.execute(sqlcommand) def delete_index(self,typ,item): self.crsr.execute("DELETE FROM {} WHERE {}='{}'".format(self.name,typ,item)) self.connection.commit() return True def get_col(self,column = "*"): # get the column of some table. If no options given, return all columns self.crsr.execute("SELECT {} FROM {}".format(column,self.name)) tres=self.crsr.fetchall() res=[] for i in tres: res.append(i) # if the table is empty, return "". if not res: res="" return res def get_item(self,column,where,specific=False): tres=[] if column == "*": for col in self.collist: temp_list=[] if specific: self.crsr.execute("SELECT * FROM {} WHERE {}='{}'".format(self.name,col,where)) else: self.crsr.execute("SELECT * FROM {} WHERE {} GLOB '*{}*'".format(self.name,col,where)) temp_list=self.crsr.fetchall() if temp_list: for i in temp_list: if not i in tres: tres.append(i) else: if specific: self.crsr.execute("SELECT * FROM {} WHERE {}='{}'".format(self.name,column,where)) else: self.crsr.execute("SELECT * FROM {} WHERE {} GLOB '*{}*'".format(self.name,column,where)) tres=self.crsr.fetchall() n=0 res=[] for i in tres: if not i in res: res.append(i) # if the table is empty, return "". if not res: return [""] return res def select_index(self,sel_list,quiet=False): if quiet == "strict": return sel_list if sel_list: res=[] if len(sel_list) > 1: n=0 print("Found several matches:") for tup in sel_list[:MAX_ITEMS]: temp_list=[] for j in tup: temp_list.append(j) print("Match [{}]".format(n)) if self.name == "FILES": print("\tTitle:\t ",temp_list[2]) name=ctb.get_alias(temp_list[4]) if name != "": category=name else: category=temp_list[4] print("\tCategory:",category) tags_list=[] for tag in temp_list[5].split(","): name=ttb.get_alias(tag) if name != "": tag=name tags_list.append(tag) print("\tTags:\t ",",".join(tags_list)) else: print("\tName:\t ",temp_list[0]) print("\tAlias:\t ",temp_list[1]) print("\tDescription: ",temp_list[2]) n+=1 if len(sel_list) > MAX_ITEMS: print(f"The list was too long, so it was reduced to {MAX_ITEMS} entries.") eingabe=input("Enter number(s) (0-{}; '*' for all entries): ".format(n-1)) if not eingabe: return [""] num_list=[] if re.match('[*]',eingabe): for i in range(0,n): num_list.append(i) else: num_list=eingabe.split(' ') nminus=0 for i in num_list: if int(i) >= n: print("The number {} is too big!".format(i)) nminus+=1 continue res.append(sel_list[int(i)]) if not quiet: print("\nFinal match{}:".format("es" if len(num_list)-nminus > 1 else "")) else: if sel_list[0] == "": if not quiet == "strict": print("No matching entry found!") return [""] res=sel_list if not quiet == "strict": print("\nMatch found!") return res return 1 def sql_compare_list(self,typ,firstlist,secondlist,specific=False): if isinstance(firstlist, str): firstlist=firstlist.split(" ") if firstlist: n=0 temp_list=[] if not secondlist: n=0 for i in self.get_col(typ): if len(temp_list) > MAX_ITEMS: break aliases=[] if i == "" and self.name == "FILES": print("NO ENTRIES IN THE INDEX!") return "" # get aliases for the checks, but only for unspecific search! if self.name == "FILES": if typ == "*": for tag in i[5].split(","): aliases.append(ttb.get_alias(tag)) aliases.append(ctb.get_alias(i[4])) elif typ == "TAGS": for tag in i[0].split(","): aliases.append(ttb.get_alias(tag)) elif typ == "CATEGORY": aliases.append(ctb.get_alias(i[0])) istr=" ".join(i).lower() + " " + " ".join(aliases).lower() success=0 for j in firstlist: j=j.lower() if specific: for part in i: part=part.lower() if j == part: if not success == -1: success=1 if success == 0: success=-1 else: if j in istr: if not success == -1: success=1 else: success=-1 if success > 0: if typ == "*": temp_list.append(self.get_item("*",i[0],specific)[0]) else: for k in self.get_item(typ,i[0],specific): if not k in temp_list: temp_list.append(k) n+=1 else: if not secondlist[0] == "": for i in secondlist: for j in firstlist: if j in i: temp_list.append(secondlist[n]) n+=1 else: return secondlist if not temp_list: return [""] return temp_list[:MAX_ITEMS+1] return secondlist def update_index(self,typ,update,where,val): self.crsr.execute("UPDATE {} SET {}='{}' WHERE {}='{}'".format(self.name,typ,update,where,val)) self.connection.commit() return True class enctable(database): # https://www.thesecuritybuddy.com/cryptography-and-python/aes-encryption-and-decryption-using-pycryptodome-module-in-python/ def __init__(self, filepath = INDEX_FILE): self.name="ENCRYPTION" self.collist=["NAME","CIPHER","PASSWORD"] super().__init__(filepath) def derive_key_and_iv(self, password, salt, key_length, iv_length): # derive key and IV from password and salt. d = d_i = b'' while len(d) < key_length + iv_length: d_i = hashlib.md5(d_i + password + salt).digest() # obtain the md5 hash value d += d_i return d[:key_length], d[key_length:key_length+iv_length] def encrypt(self, in_file, out_filepath, password="", key_length=32): print("Encrypting...") in_file=open(in_file,"rb") out_uuid=out_filepath.split("/")[-1].split(".")[0] out_file=open(f"{out_filepath}","wb") bs = AES.block_size # 16 bytes if not password: password = os.urandom(bs*random.randint(1,4)) if self.get_item("NAME", out_uuid)[0] == "": self.add_index([out_uuid.split(".")[0],"AES",b64encode(password).decode()]) else: password_list=self.get_item("NAME", in_uuid.split(".")[0]) if password_list[0] != "": if len(password_list) == 1: password=b64decode(password_list[0][2].encode()) else: print("ERROR: MULTIPLE PASSWORD ENTRIES FOUND!") return False salt = os.urandom(bs) # return a string of random bytes key, iv = self.derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) out_file.write(salt) finished = False while not finished: chunk = in_file.read(1024 * bs) if len(chunk) == 0 or len(chunk) % bs != 0:# final block/chunk is padded before encryption padding_length = (bs - len(chunk) % bs) or bs chunk += str.encode(padding_length * chr(padding_length)) finished = True out_file.write(cipher.encrypt(chunk)) out_file.close() in_file.close() def is_encrypted(self, uuid): if self.get_item("NAME", uuid)[0] != "": return True return False def decrypt(self, in_filepath, out_file=None, password="", key_length=32): print("Decrypting...") in_file=open(f"{in_filepath}","rb") # open the encrypted file in_uuid=in_filepath.split("/")[-1] if not out_file: out_temp=tempfile.mkstemp(prefix="image-index-") filepath=out_temp[1] out_file=open(filepath,"wb") else: filepath=out_file out_file=open(out_file,"wb") if not password: password_list=self.get_item("NAME", in_uuid.split(".")[0]) if password_list[0] != "": if len(password_list) == 1: password=b64decode(password_list[0][2].encode()) elif len(password_list) > 1: print("ERROR: MULTIPLE PASSWORD ENTRIES FOUND!") return False else: print("ERROR: NO PASSWORD FOUND FOR DECRYPTION!") return False else: password=b64decode(password.encode()) bs = AES.block_size salt = in_file.read(bs) key, iv = self.derive_key_and_iv(password, salt, key_length, bs) cipher = AES.new(key, AES.MODE_CBC, iv) next_chunk = '' finished = False while not finished: chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs)) if len(next_chunk) == 0: padding_length = chunk[-1] chunk = chunk[:-padding_length] finished = True out_file.write(bytes(x for x in chunk)) out_file.close() in_file.close() return filepath def delete_index(self, uuid): super().delete_index("NAME", uuid) print("UUID",uuid) return True class metatable(database): def __init__(self,typ,filepath = INDEX_FILE): self.name=typ.upper() self.collist=["NAME","ALIAS"] super().__init__(filepath) def add_index(self,val,alias,randhex=None): if not randhex: randhex=get_randchar() if self.name == "TAGS" or bencrypt: val=get_randchar(8) else: val=re.sub('[ ,?!/\\:!*"<>|]', '', val) super().add_index([val[:8] + "-" + randhex,alias]) def check_index(self,typ): res=[] for i in tb.get_col(typ): success=0 for j in self.get_col("NAME"): if j[0].split(".")[0] in i[0]: success=1 if not i[0] in res and success == 0: res.append(i[0]) return res def get_alias(self,arg): if not arg: return "" selection=self.search_index(arg,"strict") item=selection[0] if item[0] != "": alias=item[1] else: alias = "" return alias def get_name(self,arg): if not arg: return "" selection=self.search_index(arg,"strict") item=selection[0] if not item: name="" else: name=item[0] return name def search_index(self,args,quiet=True): selection=[] selection=self.sql_compare_list("*", [args], selection,True) if selection[0] == "": slist=args.split(" ") selection=self.sql_compare_list("*", slist, [],False) if len(selection) > 1: print("Please enter a more specific search query!") return "" selection=self.select_index(selection,quiet) return selection def update_index(self, typ, update, where, val): selection=self.search_index(update,"strict") if selection[0] != "" and len(selection) >= 1: print("One entry is already called {}!".format(update)) return False else: super().update_index(typ, update, where, val) return True class filestable(database): def __init__(self,filepath = INDEX_FILE): self.name="FILES" self.collist=["FILE","HASH","TITLE","SOURCE","CATEGORY","TAGS","CONTENT"] super().__init__(filepath) def add_index(self,filepath,category="default",title="",source="",tags="",content=""): filehash=self.get_hash(filepath) # make hash of file before copy if filehash in str(self.get_col("HASH")): print("This file already has an entry!") return False n=0 # get the name of the category from the meta table if not category: category="default" name=ctb.get_name(category) if name: category=name else: ctb.add_index(category.lower(),category) category=ctb.get_name(category) # get the name of the tags from the meta table tags_list=[] for tag in tags.split(','): name=ttb.get_name(tag) if name != "": tag=name else: ttb.add_index(tag.lower(),tag) tags_list.append(ttb.get_name(tag)) tags=",".join(tags_list) fileext=os.path.splitext(filepath)[-1] fileuuid=str(uuid4()) filename=fileuuid+fileext if not os.path.exists("{}/{}".format(ROOT_DIR,category)): os.makedirs("{}/{}".format(ROOT_DIR,category)) # try to copy the file, return if error. try: if bencrypt: etb.encrypt(filepath, f"{ROOT_DIR}/{category}/{fileuuid}.enc") else: shutil.copy(filepath,f"{ROOT_DIR}/{category}/{filename}") except Exception as e: print(e) print("COULDN'T COPY FILE TO DESTINATION!") return False vallist=[filename,filehash,title,source,category,tags,content] super().add_index(vallist) return True def check_index(self): hash_list=[] path_list=[] enc_num=0 for i in self.get_col(): filename=i[0] fileuuid=filename.split(".")[0] category=i[4] filehash1=i[1] if etb.is_encrypted(fileuuid): enc_num+=1 filename=f"{fileuuid}.enc" filepath="{}/{}/{}".format(ROOT_DIR,category,filename) if not os.path.exists(filepath): path_list.append(i) if etb.is_encrypted(fileuuid): continue try: filehash2=self.get_hash(filepath) if not filehash1 == filehash2: hash_list.append(i) except Exception: path_list.append(i) print(f"Encrypted files: {enc_num}; hashes not checked.") return hash_list,path_list def delete_index(self,sel_list): item_list=self.get_item(self.collist[1],sel_list[1]) if len(item_list) == 1: item=item_list[0] category=item[4] filename=item[0] fileuuid=os.path.splitext(filename)[0] super().delete_index(self.collist[1],sel_list[1]) etb.delete_index(fileuuid) return True def replace_file(self,in_filepath,item): filehash=self.get_hash(in_filepath) category=item[4] filename=item[0] if filehash in str(self.get_col("HASH")): print("This file already has an entry!") return False fileuuid=os.path.splitext(filename)[-1] out_filepath=f"{ROOT_DIR}/{category}/{filename}" out_encpath=f"{ROOT_DIR}/{category}/{fileuuid}.enc" try: if etb.is_encrypted(fileuuid): etb.encrypt(in_filepath, out_encpath) else: shutil.copy(in_filepath, out_filepath) except Exception as e: print("ERROR:",e) return False super().update_index("HASH", filehash, self.collist[0], filename) return True def update_index(self,typ,update,sel_list,omnipotent=False): typ=typ.upper() if typ in ["FILE","HASH"] and not omnipotent: print("This type can't be changed!") return False category=sel_list[4] filehash=sel_list[1] filename=sel_list[0] fileuuid=sel_list[0].split(".")[0] if typ in ["CATEGORY"]: # get alias of category name=ctb.get_name(update) if name != "": update=name else: ctb.add_index(update.lower(),update) update=ctb.get_name(update) if not os.path.exists("{}/{}".format(ROOT_DIR,update)): os.makedirs("{}/{}".format(ROOT_DIR,update)) if etb.is_encrypted(fileuuid): filename=f"{fileuuid}.enc" shutil.move("{}/{}/{}".format(ROOT_DIR,category,filename), "{}/{}/{}".format(ROOT_DIR,update,filename)) if typ in ["TAGS"]: tags_list=[] if update[0][0] == "+": for tag in sel_list[5].split(","): tags_list.append(tag) for tag in update[1:].split(","): name=ttb.get_name(tag) if name == "": ttb.add_index(tag.lower(), tag) name=ttb.get_name(tag) tags_list.append(name) elif update[0][0] == "-": for tag in sel_list[5].split(","): success=0 for i in update[1:].split(","): name=ttb.get_name(i) if name == "": ttb.add_index(tag.lower(), tag) name=ttb.get_name(tag) if name == tag: success = -1 if success >= 0: tags_list.append(tag) else: for tag in update.split(","): name=ttb.get_name(tag) if name != "": tags_list.append(name) else: ttb.add_index(tag.lower(), tag) tags_list.append(ttb.get_name(tag)) update=",".join(tags_list) super().update_index(typ, update, "HASH", filehash) def get_hash(self,filepath): # https://www.quickprogrammingtips.com/python/how-to-calculate-md5-hash-of-a-file-in-python.html md5_hash = hashlib.md5() # hash selected file in chunks of 4KiB, read the link above if you ask why. with open(filepath,"rb") as f: for byte_block in iter(lambda: f.read(4096),b""): md5_hash.update(byte_block) f.close() return str(md5_hash.hexdigest()) def search_index(self,args,quiet=False): snext="all" shash=[] alle=[] category=[] sfile=[] content=[] source=[] title=[] tags=[] for arg in args: arg=arg.lower() if re.match('^[-]\w{1}$', arg): if arg == "-h": snext="hash" elif arg == "-a": # technically unneeded because of else at the end snext="all" elif arg == "-c": snext="category" elif arg == "-f": snext="file" elif arg == "-i": # Inhalt snext="content" elif arg == "-s": snext="source" elif arg == "-t": snext="title" elif arg == "-g": # Gruppe snext="tags" else: snext="all" continue if snext == "hash": shash.append(arg) elif snext == "all": alle.append(arg) elif snext == "category": name=ctb.get_name(arg) if name != "": arg=name category.append(arg) elif snext == "file": sfile.append(arg) elif snext == "content": content.append(arg) elif snext == "source": source.append(arg) elif snext == "title": title.append(arg) elif snext == "tags": '''if "," in arg: for tag in arg.split(","): name=ttb.get_name(arg) if name != "": arg=name tags.append(arg) else: name=ttb.get_name(arg) print("name",name) if name != "": arg=name''' tags.append(arg) else: alle.append(arg) # search for the right items selection=[] selection=self.sql_compare_list("*",alle,selection) selection=self.sql_compare_list("HASH",shash,selection) selection=self.sql_compare_list("CATEGORY",category,selection) selection=self.sql_compare_list("FILE",sfile,selection) selection=self.sql_compare_list("CONTENT",content,selection) selection=self.sql_compare_list("SOURCE",source,selection) selection=self.sql_compare_list("TITLE",title,selection) selection=self.sql_compare_list("TAGS",tags,selection) return self.select_index(selection,quiet) def get_randchar(count=5): randhex="" for i in range(count): randhex+=random.choice("0123456789abcdefghijklmnopqrstuvwxyz") return randhex def add(args): if len(args) >= 6: tb.add_index(args[0],args[1],args[2],args[3],args[4],args[5]) return n=0 for i in ["Filepath","Category","Title","Source","Tags","Content"]: i=i.title() try: print("{}: {}".format(i,args[n])) except Exception: extra="" if i == "Tags": extra=" (Separate with ',')" eingabe = input("{}{}: ".format(i,extra)) if i in ["Category"] and not eingabe: print("{} set to 'default'".format(i)) eingabe="default" args.append(eingabe) if i in ["Filepath"]: filehash=tb.get_hash(args[n]) if filehash in str(tb.get_col("HASH")): print("This file already exists!") return False if not args[n]: print("{} must not be empty!".format(i)) return False else: if not os.path.isfile(args[n]): print(" The file '{}' doesn't exist or is not a file!".format(args[n])) return False n+=1 success=tb.add_index(args[0],args[1],args[2],args[3],args[4],args[5]) if success: print("Added '{}'!".format(args[2])) def copy(args): if len(args) == 0: out_filepath=input() sel_list=search([]) elif len(args) == 1: out_filepath=args[0] sel_list=search([]) else: out_filepath=args[0] sel_list=search(args[1:]) for item in sel_list: filename=item[0] out_fileext=os.path.splitext(filename)[-1] title=item[2] category=item[4] fileuuid=os.path.splitext(filename)[0] extra="" if os.path.exists(out_filepath): if os.path.isfile(out_filepath): if re.match('[nN].*', input(f"The file on path {out_filepath} already exists!\nDo you want to overwrite it? [Y/n] ")): return False if os.path.isdir(out_filepath): print("found DIRECTORY") extra=f"/{title}{out_fileext}" if etb.is_encrypted(fileuuid): etb.decrypt(f"{ROOT_DIR}/{category}/{fileuuid}.enc",f"{out_filepath}{extra}") else: shutil.copy(f"{ROOT_DIR}/{category}/{filename}", f"{out_filepath}{extra}") print(f"Copied '{title}' to {out_filepath}!") def check(args): success=0 hash_list,temp_list=tb.check_index() path_list=[] hashcheck=pathcheck=True verbose=False for arg in args: if arg == "-v": verbose=True elif arg == "-f": hashcheck=False elif arg == "-h": pathcheck=False if hash_list: print("{} file{} faulty!".format(len(hash_list),"s are" if len(hash_list) > 1 else " is")) success=-1 if verbose: for tup in hash_list: print("Title: ",tup[2]) print("\tCategory:",tup[4]) print("\tFilename:",tup[0]) for i in temp_list: if not i in path_list: path_list.append(i) if path_list: print("{} file{} missing!".format(len(path_list),"s are" if len(path_list) > 1 else " is")) success=-1 if verbose: for tup in path_list: print("Title: ",tup[2]) print("\tCategory:",tup[4]) print("\tFilename:",tup[0]) if success >= 0: print("Everything is good!") if hash_list and hashcheck: eingabe=input("Do you want to remove the faulty files? [y/N]: ") if re.match('[yY]',eingabe): print("Removing faulty files...") repair(hash_list) if path_list and pathcheck: eingabe=input("Do you want to remove the orphaned entries? [Y/n]: ") if not re.match('[nN]',eingabe): print("Removing orphaned entries...") repair(path_list) def delete(args): selection=search(args,True) for sel in selection: if sel[0] != "": try: category=sel[4] filename=sel[0] fileuuid=os.path.splitext(filename)[0] if etb.is_encrypted(fileuuid): filename=f"{fileuuid}.enc" os.remove("{}/{}/{}".format(ROOT_DIR,category,filename)) except Exception as e: print(e) print("Couldn't delete a file!") return 1 tb.delete_index(sel) print("Deleted '{}'!".format(sel[2])) def help(args): syntax=False for arg in args: if not syntax and not re.match('[mM]',arg): print("SYNTAX: image-index <option> [args]") syntax=True if re.match('[aA].*',arg): print("add:\tadds a new entry;\n\tInstant: image-index add <filepath> <category> <title> <source> <tags> <content>") print("\tPrompt: image-index add") print("Encryption: -e: Encrypt the added file\n\t -p: Do not encrypt the added file (plain)") print('EXAMPLES:\nimage-index add ~/Pictures/example.jpg "A Category" "Example file" "https://example.org" "Tag,Example,Some thing" "This is an example for the add option."') print("image-index add ~/Videos/movie.mp4 (This will ask for the other options in a prompt)\n") elif re.match('[mM].*',arg): meta_help() elif re.match('[cC].*', arg): print("copy:\tcopies a file to a custom filepath based on a search query;\n\tInstant: image-index copy <filepath> <words/filters>") print("\tPrompt: image-index copy") print("EXAMPLE:\nimage-index copy ~/Pictures/Example.jpg -t example") print("image-index copy ~/Pictures/ -t example (creates a file with the title and extension of the entry)\n") elif re.match('[cC][hH].*',arg) or re.match('[cC].*[eE].*',arg): print("check:\tchecks the existence and correctness of all files in the index;\n\tSyntax: image-index check [options]") print("\tOptions: -v: show every faulty/orphaned entry") print("\t\t -f: check only if files exist (disables the other check)") print("\t\t -h: check only if hashes are correct (disables the other check)\n") elif re.match('[dD].*',arg): print("delete:\tdeletes a file and entry based on a search query;\n\tInstant: image-index delete <words/filters>") print("\tPrompt: image-index delete") print("EXAMPLE:\nimage-index delete -t Example -g Tag Example -a .mp4 add\n") elif re.match('[iI].*',arg): print("\timport:\tshows an add prompt for every file in a directory;\n\t\tSyntax: image-index import <directory path>") print("EXAMPLE:\nimage-index import ~/Pictures/Vacation\n") elif re.match('[oO].*',arg): print("open:\topens a file based on a search query in the standard app;\n\tInstant: image-index open <words/filters>") print("\tPrompt: image-index open") print("EXAMPLE:\nimage-index open example -s example.org -i an example\n") elif re.match('[rR].*',arg): print("replaces a file of an entry based on a search query;\n\tInstant: image-index replace <replacement_file> <words/filters>") print("\tPrompt: image-index replace") print("EXAMPLE:\nimage-index replace ~/Pictures/example_new.jpg -f .jpg\n") elif re.match('[sS][tT].*',arg) or re.match('[sS].*[aA].*',arg): print("stats:\tshows some statistics about the index;\n\tSyntax: image-index stats [-v]") print("EXAMPLE:\nimage-index stats -v (also shows all tags and categories)\n") elif re.match('[sS].*',arg): print("show:\tsearches through the index and shows the matches;\n\tInstant: image-index show <words/filters>") print("\tPrompt: image-index show") print("FILTERS: -a: All types\n\t -c: Category\n\t -f: Filename\n\t -g: Tags") print("\t -h: Hash\n\t -i: Content\n\t -s: Source\n\t -t: Title") print("EXAMPLE:\nimage-index show example -c category\n") elif re.match('[uU].*',arg): print("update:\tchanges specific column based on a search query;\n\tInstant: image-index update <column> <updated_value> <words/filters>") print("\tPrompt: image-index update") print('EXAMPLES:\nimage-index update category "New Category" -t example') print('image-index update Tags "Tag,Example,Test" -s https:// -a example') print('image-index update title "Some new title" -g "some thing" -a an example') print('SPECIAL EXAMPLES TAGS:\nimage-index update tags +New_tag,hello -g "some thing" (adds the tags "New_tag" and "hello")') print('image-index update tags -Tag -t Example (removes the tag "Tag")\n') if not args: print("SYNTAX: image-index <option> [args]") print("OPTIONS:\n\thelp:\tdisplays helpful text\}n\t\tSyntax: image-index help [commands]") print("\tmeta:\tdisplays help for the metadata tables") print("\tadd:\tadds a new entry;\n\t\tSyntax: image-index add <filepath> <category> <title> <source> <tags> <content>") print("\tcheck:\tchecks the existence and correctness of all files in the index;\n\t\tSyntax: image-index check [options]") print("\tcopy:\tcopies a file to a custom filepath based on a search query;\n\t\tSyntax: image-index copy <filepath> <words/filters>") print("\tdelete:\tdeletes a file and entry based on a search query;\n\t\tSyntax: image-index delete <words/filters>") print("\timport:\tshows an add prompt for every file in a directory;\n\t\tSyntax: image-index import <directory path>") print("\topen:\topens a file based on a search query in the standard app;\n\t\tSyntax: image-index open <words/filters>") print("\treplace:replaces a file of an entry based on a search query;\n\t\tSyntax: image-index replace <file> <words/filters>") print("\tshow:\tsearches through the index and shows the matches;\n\t\tSyntax: image-index show <words/filters>") print("\tstats:\tshows some stats about the index;\n\t\tSyntax: image-index stats [-v]") print("\tupdate:\tchanges specific column based on a search query;\n\t\tSyntax: image-index update <column> <updated_value> <words/filters>") def imports(args): for arg in args: if arg[-1] != "/": arg+="/" if os.path.exists(arg): if not os.path.isfile(arg): for sfile in os.listdir(arg): if os.path.isfile(arg+sfile): add(["{}{}".format(arg,sfile)]) else: print("Path '{}' is a file!".format(arg)) else: print("Path '{}' doesn't exist!".format(arg)) def meta(args): if len(args) == 0 or re.match('[hH].*',args[0]): meta_help() else: if len(args) <= 1: print("Options: Category, Tags") args.append(input("Input one type to check from the list above: ")) command=args[0] typ=args[1] args=args[2:] if re.match('[cC].*',command): meta_check(typ,args) elif re.match('[uU].*',command): meta_update(typ,args) def meta_check(typ,args): if re.match('[cC].*',typ): tres=ctb.check_index("CATEGORY") elif re.match('[tT].*',typ): tres=ttb.check_index("TAGS") else: print("No valid type specified!") return False if tres: yas=True print("Missing items:",','.join(tres)) eingabe=input("Do you want to add aliases to them?[Y/n]: ") if re.match('[nN]',eingabe): yas=False print("yo") for val in tres: if yas: eingabe=input("Enter Alias for {}: ".format(val.upper())) else: eingabe="" if re.match('[cC].*',typ): ctb.add_index(val,eingabe) elif re.match('[tT].*',typ): ttb.add_index(val,eingabe) else: print("Everything is good!") def meta_update(typ,args): if len(args) == 0: args.append(input("Enter the alias to update: ")) args.append(input("Enter the updated alias: ")) elif len(args) == 1: args.append(input("Enter the updated alias: ")) search=args[0] update=args[1] if re.match('[cC].*',typ): sel_list=ctb.search_index(search) for item in sel_list: alias=ctb.get_alias(item[0]) success=ctb.update_index("ALIAS", update, "NAME", item[0]) elif re.match('[tT].*',typ): sel_list=ttb.search_index(search) for item in sel_list: alias=ttb.get_alias(item[0]) success=ttb.update_index("ALIAS", update, "NAME", item[0]) else: print("The first argument needs to be either 'Category' or 'Tags'!") return False if item[0] != "" and success: print("Updated {} to {}".format(alias,update)) def meta_help(): print("SYNTAX: image-index meta <option> [args]") print("OPTIONS:\n\thelp:\tdisplays this text") print("\tcheck:\tcheck which items don't have an entry yet;\n\t\tSyntax: image-index meta check <Category/Tags>") print("\tupdate:\tchange an alias of one entry based on a search query;\n\t\tSyntax: image-index update <Category/Tags> [entry] [alias]") print('\t\tExamples: image-index meta update tags Example "New alias"') print('\t\t\t image-index meta update category "A Category" "Example Category"\n') def opens(args): plat=sys.platform selection=search(args,True) for sel in selection: if not sel[0] == "": fileuuid=os.path.splitext(sel[0])[0] if etb.is_encrypted(fileuuid): filename=f"{fileuuid}.enc" else: filename=sel[0] category=sel[4] filepath="{}/{}/{}".format(ROOT_DIR,category,filename) else: continue if etb.is_encrypted(fileuuid): filepath=etb.decrypt(filepath) # temporary file in RAM if plat.startswith('linux'): subprocess.Popen([LINUX_APP_STARTER, filepath]) else: os.startfile(filepath) def update(args): n=0 for i in ["column","updated string"]: try: trash=args[n] except Exception as e: eingabe=input("Enter {}: ".format(i)) args.append(eingabe) n+=1 selection=search(args[n:],True) typ=args[0] update=args[1] for sel in selection: if sel[0] != "": n=0 for i in tb.collist: if i.lower() == typ.lower(): old=sel[n] else: n+=1 tb.update_index(typ,update,sel) if re.match('[tT][aA].*', typ) and re.match('[-+]', update[0]): tags_list=[] for tag in update[1:].split(","): tags_list.append(tag) tagstr=", ".join(tags_list) if update[0] == "-": print("Removed {}!".format(tagstr)) elif update[0] == "+": print("Added {}!".format(tagstr)) else: print("Updated {} to {}!".format(old,update)) def repair(err_list): sel_list=[] for i in err_list: if not i in sel_list: sel_list.append(i) for tup in sel_list: filename=tup[0] category=tup[4] filepath="{}/{}/{}".format(ROOT_DIR,category,filename) if os.path.exists(filepath): os.remove(filepath) tb.delete_index(tup) def replace(args): if len(args) == 0: filepath=input("Enter filepath of the replacement: ") sel_list=search([]) elif len(args) == 1: filepath=args[0] sel_list=search([]) else: filepath=args[0] sel_list=search(args[1:]) if len (sel_list) != 1: print("Please select one entry for replacement!") return False item=sel_list[0] if os.path.exists(filepath) and os.path.isfile(filepath): title=item[2] if tb.replace_file(filepath, item): print(f"Replaced '{title}'!") else: print(f"Failed to replace '{title}'!") else: print("ERROR: Replacement doesn't exist or is not a file!") return False def search(args,quiet=False): if len(args) == 0: print("Separate the items with spaces.") print("FILTERS: -a: All types\n\t -c: Category\n\t -f: Filename\n\t -g: Tags") print("\t -h: Hash\n\t -i: Content\n\t -s: Source\n\t -t: Title") args=input("Query: ") if len(args) > 0: res=tb.search_index(args.split(' '),quiet) else: print("\nQuery empty!") return [""] else: res=tb.search_index(args,quiet) return res def show(args): tres=search(args,False) if not tres[0] == "": for res in tres: print("Title: ",res[2]) print("\tSource:\t ",res[3]) alias=ctb.get_alias(res[4]) if alias != "": print("\tCategory: {} ({})".format(alias,res[4])) else: print("\tCategory:",res[4]) print("\tFilename:",res[0],"(Encrypted)" if etb.is_encrypted(os.path.splitext(res[0])[0]) else "") print("\tHash:\t ",res[1]) tags_list=[] for tag in res[5].split(","): alias=ttb.get_alias(tag) if alias != "": tags_list.append(alias) print("\tTags:\t ",",".join(tags_list)) print("\tContent: ",res[6]) def stats(args): verbose=False for arg in args: if re.match('[-]+[vV].*', arg): verbose=True entrynum=len(tb.get_col("HASH")) encnum=len(etb.get_col("NAME")) print(f"Entry count: {entrynum} ({encnum} encrypted)") tags=ttb.get_col("NAME") tagnum=len(tags) print(f"Tags count: {tagnum}") if verbose: for item in tags: name=item[0] taguse=tb.get_item("TAGS", name) if taguse[0] == "": taguse=0 else: taguse=len(taguse) print(f"\t{ttb.get_alias(name)} ({name}): Used by {taguse} entr%s"% ("y" if taguse == 1 else "ies")) cats=ctb.get_col("NAME") catnum=len(cats) print(f"Categories count: {catnum}") if verbose: for item in cats: name=item[0] catuse=len(tb.get_item("CATEGORY", name)) print(f"\t{ctb.get_alias(name)} ({name}): Used by {catuse} entr%s"% "y" if taguse == 1 else "ies") return True def main(): if len(sys.argv) <= 1 or re.match('[hH].*',sys.argv[1]): help(sys.argv[2:]) else: command=sys.argv[1] args=[] for arg in sys.argv[2:]: global bencrypt if re.match("[-]{1,2}e.*",arg): # -e --encrypted bencrypt=True if re.match("[-]{1,2}p.*",arg): # -p --plain bencrypt=False else: args.append(arg) if re.match('[aA].*',command): add(args) elif re.match('[mM].*',command): meta(args) elif re.match('[cC][hH].*',command) or re.match('[cC].*[eE].*',command): check(args) elif re.match('[cC].*',command): copy(args) elif re.match('[dD].*',command): delete(args) elif re.match('[iI].*',command): imports(args) elif re.match('[oO].*',command): opens(args) elif re.match('[rR].*',command): replace(args) elif re.match('[sS][tT].*',command) or re.match('[sS].*[aA].*',command): stats(args) elif re.match('[sS].*',command): show(args) elif re.match('[uU].*',command): update(args) else: print("No such option!") tb.connection.close() ctb.connection.close() etb.connection.close() ttb.connection.close() #input("Press return...") if __name__ == "__main__": bencrypt=ENCRYPT # boolean for encryption in session filepath=INDEX_FILE etb = enctable(filepath) tb = filestable(filepath) ctb = metatable("CATEGORY",filepath) ttb = metatable("TAGS",filepath) main()