#!/bin/python3 import hashlib,os,random,re,shutil,subprocess,sys from pathlib import Path from uuid import uuid4 import sqlite3 as sql ROOT_DIR=os.getcwd() # The directory where all directories and files of the index are located CONFIG_DIR=ROOT_DIR # The directory where the file 'index.db' is located LINUX_APP_STARTER="xdg-open" # The command which opens the files in the default applications class database(): def __init__(self,filepath = CONFIG_DIR + "/index.db"): 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): # compile the options into a command for the SQLite database colstring=",".join(self.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) def delete_index(self,typ,item): self.crsr.execute("DELETE FROM {} WHERE {}='{}'".format(self.name,typ,item)) self.connection.commit() return 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: m=0 for j in tres: if i in j and n!=m: continue else: res.append(i) m+=1 n+=1 # 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: 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 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): 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 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 metatable(database): def __init__(self,typ,filepath = CONFIG_DIR + "/index.db"): self.name=typ.upper() self.collist=["NAME","ALIAS"] super().__init__(filepath) def add_index(self,val,alias): randhex=get_randhex() 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] 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] name=item[0] return name def search_index(self,args,quiet=True): selection=[] selection=self.sql_compare_list("*", [args], selection,True) 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 = CONFIG_DIR + "/index.db"): 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 exists!") 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) filetype=os.path.splitext(filepath)[1] filename=str(uuid4()) + filetype if not os.path.exists("{}/{}".format(ROOT_DIR,category)): os.makedirs("{}/{}".format(ROOT_DIR,category)) # try to copy the file, return if error. try: shutil.copy(filepath,"{}/{}/{}".format(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=[] for i in self.get_col(): filename=i[0] category=i[4] filehash1=i[1] filepath="{}/{}/{}".format(ROOT_DIR,category,filename) if not os.path.exists(filepath): path_list.append(i) try: filehash2=self.get_hash(filepath) if not filehash1 == filehash2: hash_list.append(i) except Exception: path_list.append(i) return hash_list,path_list def delete_index(self,sel_list): self.get_item("HASH",sel_list[1]) super().delete_index("HASH",sel_list[1]) return True def update_index(self,typ,update,sel_list): typ=typ.upper() if typ in ["FILE","HASH"]: print("This type can't be changed!") return 1 category=sel_list[4] filehash=sel_list[1] filename=sel_list[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)) 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_randhex(count=5): randhex="" for i in range(count): randhex+=random.choice("0123456789abcdef") 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"]: try: print("{}: {}".format(i,args[n])) except Exception: if i == "Tags": extra=" (Separate with ',')" else: extra="" eingabe = input("Enter {}{}: ".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"]: if not args[n]: print("{} must not be empty!".format(i)) return 1 else: if not Path(args[n]).is_file(): print(" The file '{}' doesn't exist or is not a file!".format(args[n])) return 1 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 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] 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