diff --git a/TODO b/TODO index 7416dbc..e2f1cc3 100644 --- a/TODO +++ b/TODO @@ -1,20 +1,13 @@ ## GENERAL * on start up, should validate the import/storage/recycle bin paths exist and spit an error if they dont - * dont process duplicates from Bin + * never process duplicates from Bin (allow them to be created on startup so they can be viewed?) - * issue where someone could call IP .../Imp/photos and SP .../Sto/photos and then static/photos is ambiguous: - -- DONE: make path prefix by static// so that files are in: static//in_path.pp/dir.rel_path/ - -- then deleting below would just path_prefix from static/storage to .pa_bin/storage, etc. - -- need to create the subdir if it does not exist in recycle_bin_path - *** THINK: should bin be a normal path/dir name, and in static too -- I think it will be easier in hindsight -- so need to change dst_dir when os.replace is used - -- need to be able to view recycle bin (should be simple when we have path_types) &&& should able to consolidate the files_ip/files_sp/files_rb? route handling functions - -- could also allow undelete per file / show content as another Files->View and more like storage (i.e. show folders) * storage_path viewing needs to be by folder / not a big grab bag of files (by default - DONE) -- BUG: issue with view by Day, etc. we print out day even if the Entry is not in the cwd -- TODO: Need to toggle the view if I want, and when viewing storage area, change single-click to be view file again, and right-click to be my context menu - * * import_path can be folder view and is DONE but needs toggle as above * need a way for search results to show we found something in import_path or storage_path: - - now we can use the in_path, then have a series of icons, e.g. disk for storage, ? for import, and bin for recycling (before the blue path)--maybe even show different colours, e.g. info for import, primary for storage and danger for bin? + - now we can use the in_path, then have a series of icons, e.g. disk for storage, ? for import, and bin for recycling + -- only show these on the thumbs *IF* we search (where it may not be obvious where it came from) * handle thumbs: - need to ignore *thumb* -- but consider how we do this and don't screw up 'dir/job counts' and potentially other stuff like .pa_bin if its in storage/import folder? @@ -22,6 +15,9 @@ * comment your code * more OO goodness :) +## MAYBE? + * can we consolidate the files_ip/files_sp/files_rbp route handling functions??? + * could also allow undelete per file / show content as another Files->View and more like storage (i.e. show folders) ## DB Need to think about... @@ -64,9 +60,6 @@ need to copy into here the jquery/fa files so we don't need internet to function - for that matter run lightspeed against all this - file view should have show folders / flat as a choosable somehow - - sorter... need some way to multiselect images [DONE] and then get them into a new "folder" @@ -89,4 +82,3 @@ * exif processing? * location stuff - test a new photo from my camera out -- image is in dir, need to look at exifread output - diff --git a/files.py b/files.py index 9d4e606..292b402 100644 --- a/files.py +++ b/files.py @@ -134,8 +134,10 @@ def ViewingOptions( request ): size=128 if 'files_sp' in request.path: folders=True - # this is the default as 'Storage' is the path_type in the DB cwd='static/Storage' + elif 'files_rbp' in request.path: + folders=True + cwd='static/Bin' else: folders=False cwd=None @@ -181,7 +183,7 @@ def files_ip(): noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request ) entries=[] - # per import path, add entries to view + # per import path, add entries to view settings=Settings.query.first() paths = settings.import_path.split("#") for path in paths: @@ -206,7 +208,7 @@ def files_sp(): print( f"cwd={cwd}" ) - # per storage path, add entries to view + # per storage path, add entries to view settings=Settings.query.first() paths = settings.storage_path.split("#") for path in paths: @@ -220,6 +222,33 @@ def files_sp(): entries+=Entry.query.join(Dir).join(EntryDirLink).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name).offset(offset).limit(how_many).all() return render_template("files.html", page_title='View Files (Storage Path)', entry_data=entries, noo=noo, grouping=grouping, how_many=how_many, offset=offset, size=size, folders=folders, cwd=cwd, root=root ) + +################################################################################ +# /files -> show thumbnail view of files from storage_path +################################################################################ +@app.route("/files_rbp", methods=["GET", "POST"]) +def files_rbp(): + noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request ) + entries=[] + + print( f"cwd={cwd}" ) + + # per recyle bin path, add entries to view + settings=Settings.query.first() + paths = settings.recycle_bin_path.split("#") + for path in paths: + prefix = SymlinkName("Bin",path,path+'/') + + if noo == "Oldest": + entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(File.year,File.month,File.day,Entry.name).offset(offset).limit(how_many).all() + else: + entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all() + + entries+=Entry.query.join(Dir).join(EntryDirLink).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name).offset(offset).limit(how_many).all() + print( f"dirs={Entry.query.join(Dir).join(EntryDirLink).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name).offset(offset).limit(how_many).all()}" ) + return render_template("files.html", page_title='View Files (Bin Path)', entry_data=entries, noo=noo, grouping=grouping, how_many=how_many, offset=offset, size=size, folders=folders, cwd=cwd, root=root ) + + ################################################################################ # /search -> show thumbnail view of files from import_path(s) ################################################################################ diff --git a/pa_job_manager.py b/pa_job_manager.py index 9252111..d09df63 100644 --- a/pa_job_manager.py +++ b/pa_job_manager.py @@ -495,6 +495,19 @@ def CreateSymlink(job,ptype,path): os.symlink(path, symlink) return symlink +################################################################################################################################################################ +# +# Key function that runs as part of (usually) an import job. The name of the directory (dirname) is checked to see +# if it already is in the database (inside of in_dir in in_path). If it is, +# just return the db entry. If not, then we create a new row in Dir, that has name of dirname, has a parent directory +# of in_dir (DB object), has the rel_path set to the relative fs path from the actual fs path to this entry (including the dirname) +# and the in_path set to the overarching path (one of an Import, Storage or Recycle_bin path in the DB) +# +# e.g. path on FS: /home/ddp/src/photoassistant/images_to_process/ ... ends in DB as path_prefix="static/Import/images_to_process" +# and we have a dir in /home/ddp/src/photoassistant/images_to_process/0000/subtest, then we call: +# AddDir( job, dirname='subtest', in_dir=Dir object for '0000', rel_path='0000/subtest', in_path=Path object for 'static/Import/images_to_process' ) +# +################################################################################################################################################################ def AddDir(job, dirname, in_dir, rel_path, in_path ): dir=session.query(Dir).join(PathDirLink).join(Path).filter(Path.id==in_path.id).filter(Dir.rel_path==rel_path).first() if dir: @@ -573,16 +586,28 @@ def MoveFileToRecycleBin(job,del_me): os.replace( src, dst ) except Exception as e: print( f"Failed to remove file from filesystem - which={src}, err: {e}") - bin=session.query(Path).join(PathType).filter(PathType.name=='Bin').first() - print("bin={bin}") - print("del_me={del_me}") + bin_path=session.query(Path).join(PathType).filter(PathType.name=='Bin').first() + print( f"bin={bin}") + print( f"del_me={del_me}") new_rel_path=del_me.in_dir.in_path.path_prefix.replace('static/','') + # if there is a relative path on this dir, add it to the new_rel_path as there is only ever 1 Bin path if len(del_me.in_dir.rel_path): new_rel_path += '/' + del_me.in_dir.rel_path - print("new_rel_path={new_rel_path}") - new_dir = AddDir(job, new_rel_path, None, new_rel_path, bin ) - print( "new_dir={new_dir}" ) + + print( f"new_rel_path={new_rel_path}" ) + + parent_dir=session.query(Dir).join(PathDirLink).filter(PathDirLink.path_id==bin_path.id).first() + print( f"parent_dir for path={parent_dir}" ) + part_rel_path="" + for dirname in new_rel_path.split("/"): + part_rel_path += f"{dirname}" + print( f"AddDir( {dirname} in {parent_dir} with {part_rel_path} as pfx ) ") + new_dir=AddDir( job, dirname, parent_dir, part_rel_path, bin_path ) + parent_dir=new_dir + part_rel_path += "/" + + print( f"new_dir={new_dir}" ) del_me.in_dir = new_dir return @@ -675,7 +700,7 @@ def JobImportDir(job): path_obj.num_files=overall_file_cnt parent_dir=None - # rel_path is always '' at the top of the path objects path_prefix for the first dir + # rel_path is always '' at the top of the path objects path_prefix for the first dir dir=AddDir(job, os.path.basename(symlink), parent_dir, '', path_obj) # session.add in case we already have imported this dir (as AddDir wont) & now we might have diff num of files to last time, session.add(dir) @@ -688,8 +713,8 @@ def JobImportDir(job): # already create root above to work out num_files for whole os.walk if root != path: pp=SymlinkName( path_obj.type.name, path, root )+'/'+os.path.basename(root) - print( F"pp={pp}, root={root}, symlink={symlink}" ) rel_path=pp.replace(symlink+'/','') + print( f"pp={pp}, root={root}, symlink={symlink}, rel_path={rel_path}" ) dir=AddDir(job, os.path.basename(root), parent_dir, rel_path, path_obj) for basename in files: # commit every 100 files to see progress being made but not hammer the database @@ -1005,8 +1030,8 @@ def RemoveDups(job): del_me_lst = [] for f in files: if os.path.isfile( f.FullPathOnFS() ) == False: - AddLogForJob( job, f"ERROR: (per file del) file (DB id: {f.eid} - {f.FullPathOnFS()}) does not exist? ignorning file") - elif f.file_details.eid == int(keeping): + AddLogForJob( job, f"ERROR: (per file del) file (DB id: {f.id} - {f.FullPathOnFS()}) does not exist? ignorning file") + elif f.id == int(keeping): found = f else: del_me_lst.append(f) @@ -1032,7 +1057,7 @@ def RemoveDups(job): del_me=None for f in files: if os.path.isfile(f.FullPathOnFS()) == False: - AddLogForJob( job, f"ERROR: (per path del) file (DB id: {f.eid} - {f.FullPathOnFS()}) does not exist? ignorning file") + AddLogForJob( job, f"ERROR: (per path del) file (DB id: {f.id} - {f.FullPathOnFS()}) does not exist? ignorning file") if f.in_dir.eid == int(keeping): found=f else: diff --git a/templates/base.html b/templates/base.html index 4f45a90..d0cae8e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -57,6 +57,7 @@ View Photos to Import View Details of Photos to Import View Stored Photos + View Recycle Bin