complete rewrite of viewer, videos need to be fixed. Otherwise, viewer now loads entry data for all "how_many" images on the screen, and allows prev/next movement between images without a DB load for the current page of images, then as required, it will retrieve the prev/next "how_many" from the database via /viewlist route, and go back into view/<id> of the appropriate new image of the new list. Also prevents going below 0 and beyond end of DB for the first time.

This commit is contained in:
2021-08-27 23:38:31 +10:00
parent 652a89161d
commit 1d3caf17de
6 changed files with 419 additions and 357 deletions

354
files.py
View File

@@ -78,6 +78,7 @@ class Dir(db.Model):
# {dir|file}_etails are convenience data for the relevant details from the Dir
# or File class - not in DB
# in_dir - is the Dir that this entry is located in (convenience for class only)
# FullPathOnFS(): method to get path on the FS for this Entry
################################################################################
class Entry(db.Model):
__tablename__ = "entry"
@@ -89,6 +90,17 @@ class Entry(db.Model):
file_details = db.relationship( "File", uselist=False )
in_dir = db.relationship ("Dir", secondary="entry_dir_link", uselist=False )
def FullPathOnFS(self):
if self.in_dir:
s=self.in_dir.in_path.path_prefix + '/'
if len(self.in_dir.rel_path) > 0:
s += self.in_dir.rel_path + '/'
# this occurs when we have a dir that is the root of a path
else:
s=self.dir_details.in_path.path_prefix+'/'
s += self.name
return s
def __repr__(self):
return f"<id: {self.id}, name: {self.name}, type={self.type}, dir_details={self.dir_details}, file_details={self.file_details}, in_dir={self.in_dir}"
@@ -159,79 +171,94 @@ def ClearJM_Message(id):
return
################################################################################
# ViewingOptions: defines set of default values for viewing (order/size, etc.)
# SetViewingOptions: defines set of default values for viewing (order/size, etc.)
# and if a request object (from a POST) is passed in, it returns those instead
# it also handles the cwd appropriately
################################################################################
def ViewingOptions( request ):
noo="Oldest"
grouping="None"
how_many="50"
offset="0"
size="128"
if 'files_sp' in request.path:
noo="A to Z"
folders=True
cwd='static/Storage'
elif 'files_rbp' in request.path:
folders=True
cwd='static/Bin'
def SetViewingOptions( request ):
OPT={}
OPT['noo']="Oldest"
OPT['grouping']="None"
OPT['how_many']="50"
OPT['offset']="0"
OPT['size']="128"
settings=Settings.query.first()
if 'orig_url' in request.form:
print("okay, this has come from a viewer.html and needs next/prev how_many, recreate orig critiera...")
url = request.form['orig_url']
else:
folders=False
cwd='static/Import'
root=cwd
url = request.path
if 'files_sp' in url:
OPT['noo']="A to Z"
OPT['folders']=True
OPT['path_type'] = 'Storage'
OPT['cwd']='static/Storage'
OPT['paths'] = settings.storage_path.split("#")
elif 'files_rbp' in url:
OPT['folders']=True
OPT['path_type'] = 'Bin'
OPT['cwd']='static/Bin'
OPT['paths'] = settings.recycle_bin_path.split("#")
else:
OPT['folders']=False
OPT['path_type'] = 'Import'
OPT['cwd']='static/Import'
OPT['paths'] = settings.import_path.split("#")
OPT['root']=OPT['cwd']
# the above are defaults, if we are here, then we have current values, use them instead
if request.method=="POST":
noo=request.form['noo']
how_many=request.form['how_many']
offset=int(request.form['offset'])
grouping=request.form['grouping']
size = request.form['size']
OPT['noo']=request.form['noo']
OPT['how_many']=request.form['how_many']
OPT['offset']=int(request.form['offset'])
OPT['grouping']=request.form['grouping']
OPT['size'] = request.form['size']
# seems html cant do boolean, but uses strings so convert
if request.form['folders'] == "False":
folders=False
OPT['folders']=False
if request.form['folders'] == "True":
folders=True
OPT['folders']=True
# have to force grouping to None if we flick to folders from a flat
# view with grouping (otherwise we print out group headings for
# child content that is not in the CWD)
grouping=None
OPT['grouping']=None
cwd = request.form['cwd']
OPT['cwd'] = request.form['cwd']
if 'fullscreen' in request.form:
OPT['fullscreen']=request.form['fullscreen']
if 'prev' in request.form:
offset -= int(how_many)
if offset < 0:
offset=0
OPT['offset'] -= int(OPT['how_many'])
if OPT['offset'] < 0:
OPT['offset']=0
if 'next' in request.form:
offset += int(how_many)
OPT['offset'] += int(OPT['how_many'])
return noo, grouping, how_many, offset, size, folders, cwd, root
return OPT
################################################################################
# GetEntriesInFlatView: func. to retrieve DB entries appropriate for flat view
################################################################################
def GetEntriesInFlatView( cwd, prefix, noo, offset, how_many ):
def GetEntriesInFlatView( OPT, prefix ):
entries=[]
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()
elif noo == "Newest":
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()
elif noo == "Z to A":
entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name.desc()).offset(offset).limit(how_many).all()
if OPT['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(OPT['offset']).limit(OPT['how_many']).all()
elif OPT['noo'] == "Newest":
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(OPT['offset']).limit(OPT['how_many']).all()
elif OPT['noo'] == "Z to A":
entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name.desc()).offset(OPT['offset']).limit(OPT['how_many']).all()
else:
entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name).offset(offset).limit(how_many).all()
entries+=Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix).order_by(Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
return entries
################################################################################
# GetEntriesInFolderView: func. to retrieve DB entries appropriate for folder view
# read inline comments to deal with variations / ordering...
################################################################################
def GetEntriesInFolderView( cwd, prefix, noo, offset, how_many ):
def GetEntriesInFolderView( OPT, prefix ):
entries=[]
# okay the root cwd is fake, so treat it specially - its Dir can be found by path with dir.rel_path=''
if os.path.dirname(cwd) == 'static':
if os.path.dirname(OPT['cwd']) == 'static':
dir=Entry.query.join(Dir).join(PathDirLink).join(Path).filter(Dir.rel_path=='').filter(Path.path_prefix==prefix).order_by(Entry.name).first()
# this can occur if the path in settings does not exist as it wont be in # the DB
if not dir:
@@ -239,7 +266,7 @@ def GetEntriesInFolderView( cwd, prefix, noo, offset, how_many ):
# although this is 1 entry, needs to come back via all() to be iterable
entries+= Entry.query.filter(Entry.id==dir.id).all()
else:
rp = cwd.replace( prefix, '' )
rp = OPT['cwd'].replace( prefix, '' )
# when in subdirs, replacing prefix will leave the first char as /, get rid of it
if len(rp) and rp[0] == '/':
rp=rp[1:]
@@ -247,22 +274,51 @@ def GetEntriesInFolderView( cwd, prefix, noo, offset, how_many ):
# this can occur if the path in settings does not exist as it wont be in # the DB
if not dir:
return entries
if noo == "Z to A" or "Newest":
if OPT['noo'] == "Z to A" or "Newest":
entries+= Entry.query.join(EntryDirLink).join(FileType).filter(EntryDirLink.dir_eid==dir.id).filter(FileType.name=='Directory').order_by(Entry.name.desc()).all()
# just do A to Z / Oldest by default or if no valid option
else:
entries+= Entry.query.join(EntryDirLink).join(FileType).filter(EntryDirLink.dir_eid==dir.id).filter(FileType.name=='Directory').order_by(Entry.name).all()
# add any files at the current CWD (based on dir_eid in DB)
if noo == "Oldest":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(File.year,File.month,File.day,Entry.name).offset(offset).limit(how_many).all()
elif noo == "Newest":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all()
elif noo == "Z to A":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(Entry.name.desc()).offset(offset).limit(how_many).all()
if OPT['noo'] == "Oldest":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(File.year,File.month,File.day,Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
elif OPT['noo'] == "Newest":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
elif OPT['noo'] == "Z to A":
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(Entry.name.desc()).offset(OPT['offset']).limit(OPT['how_many']).all()
# just do A to Z by default or if no valid option
else:
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(Entry.name).offset(offset).limit(how_many).all()
entries+=Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id).order_by(Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
return entries
################################################################################
# /GetEntries -> helper function that Gets Entries for required files to show
# for several routes (files_ip, files_sp, files_rbp, search, viewlist)
################################################################################
def GetEntries( OPT ):
entries=[]
if 'search_term' in request.form:
search_term=request.form['search_term']
if 'AI:' in search_term:
search_term = search_term.replace('AI:','')
all_entries = Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag.ilike(f"%{search_term}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
else:
file_data=Entry.query.join(File).filter(Entry.name.ilike(f"%{search_term}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
dir_data=Entry.query.join(File).join(EntryDirLink).join(Dir).filter(Dir.rel_path.ilike(f"%{search_term}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
ai_data=Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag.ilike(f"%{search_term}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(OPT['offset']).limit(OPT['how_many']).all()
all_entries = file_data + dir_data + ai_data
return all_entries
for path in OPT['paths']:
if not os.path.exists(path):
continue
prefix = SymlinkName(OPT['path_type'],path,path+'/')
if OPT['folders']:
entries+=GetEntriesInFolderView( OPT, prefix )
else:
entries+=GetEntriesInFlatView( OPT, prefix )
return entries
################################################################################
@@ -271,15 +327,9 @@ def GetEntriesInFolderView( cwd, prefix, noo, offset, how_many ):
@app.route("/file_list_ip", methods=["GET","POST"])
@login_required
def file_list_ip():
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
entries=[]
settings=Settings.query.first()
paths = settings.import_path.split("#")
for path in paths:
prefix = SymlinkName("Import",path,path+'/')
entries+=GetEntriesInFlatView( cwd, prefix, noo, offset, how_many )
return render_template("file_list.html", page_title='View File Details (Import Path)', entry_data=entries, noo=noo, how_many=how_many, offset=offset )
OPT=SetViewingOptions( request )
entries=GetEntries( OPT )
return render_template("file_list.html", page_title='View File Details (Import Path)', entry_data=entries, OPT=OPT )
################################################################################
# /files -> show thumbnail view of files from import_path(s)
@@ -287,26 +337,10 @@ def file_list_ip():
@app.route("/files_ip", methods=["GET", "POST"])
@login_required
def files_ip():
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
entries=[]
OPT=SetViewingOptions( request )
entries=GetEntries( OPT )
people = Person.query.all()
# per import path, add entries to view
settings=Settings.query.first()
paths = settings.import_path.split("#")
for path in paths:
if not os.path.exists(path):
continue
prefix = SymlinkName("Import",path,path+'/')
if folders:
entries+=GetEntriesInFolderView( cwd, prefix, noo, offset, how_many )
else:
entries+=GetEntriesInFlatView( cwd, prefix, noo, offset, how_many )
return render_template("files.html", page_title='View Files (Import Path)', entry_data=entries, noo=noo, grouping=grouping, how_many=how_many, offset=offset, size=size, folders=folders, cwd=cwd, root=root, people=people )
return render_template("files.html", page_title=f"View Files ({OPT['path_type']} Path)", entry_data=entries, OPT=OPT, people=people )
################################################################################
# /files -> show thumbnail view of files from storage_path
@@ -314,24 +348,10 @@ def files_ip():
@app.route("/files_sp", methods=["GET", "POST"])
@login_required
def files_sp():
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
entries=[]
OPT=SetViewingOptions( request )
entries=GetEntries( OPT )
people = Person.query.all()
# per storage path, add entries to view
settings=Settings.query.first()
paths = settings.storage_path.split("#")
for path in paths:
if not os.path.exists(path):
continue
prefix = SymlinkName("Storage",path,path+'/')
if folders:
entries+=GetEntriesInFolderView( cwd, prefix, noo, offset, how_many )
else:
entries+=GetEntriesInFlatView( cwd, prefix, noo, offset, how_many )
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, people=people )
return render_template("files.html", page_title=f"View Files ({OPT['path_type']} Path)", entry_data=entries, OPT=OPT, people=people )
################################################################################
@@ -340,22 +360,10 @@ def files_sp():
@app.route("/files_rbp", methods=["GET", "POST"])
@login_required
def files_rbp():
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
entries=[]
# per recyle bin path, add entries to view
settings=Settings.query.first()
paths = settings.recycle_bin_path.split("#")
for path in paths:
if not os.path.exists(path):
continue
prefix = SymlinkName("Bin",path,path+'/')
if folders:
entries+=GetEntriesInFolderView( cwd, prefix, noo, offset, how_many )
else:
entries+=GetEntriesInFlatView( cwd, prefix, noo, offset, how_many )
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 )
OPT=SetViewingOptions( request )
entries=GetEntries( OPT )
people = Person.query.all()
return render_template("files.html", page_title=f"View Files ({OPT['path_type']} Path)", entry_data=entries, OPT=OPT )
################################################################################
@@ -364,22 +372,11 @@ def files_rbp():
@app.route("/search", methods=["GET","POST"])
@login_required
def search():
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
OPT=SetViewingOptions( request )
# always show flat results for search to start with
folders=False
term=request.form['term']
if 'AI:' in term:
term = term.replace('AI:','')
all_entries = Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag.ilike(f"%{term}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all()
else:
file_data=Entry.query.join(File).filter(Entry.name.ilike(f"%{request.form['term']}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all()
dir_data=Entry.query.join(File).join(EntryDirLink).join(Dir).filter(Dir.rel_path.ilike(f"%{request.form['term']}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all()
ai_data=Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag.ilike(f"%{request.form['term']}%")).order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name).offset(offset).limit(how_many).all()
all_entries = file_data + dir_data + ai_data
return render_template("files.html", page_title='View Files', search_term=request.form['term'], entry_data=all_entries, noo=noo, grouping=grouping, how_many=how_many, offset=offset, size=size, folders=folders, cwd=cwd, root=root )
OPT['folders']=False
entries=GetEntries( OPT )
return render_template("files.html", page_title='View Files', search_term=request.form['search_term'], entry_data=entries, OPT=OPT )
################################################################################
# /files/scannow -> allows us to force a check for new files
@@ -520,73 +517,62 @@ def move_files():
return render_template("base.html")
################################################################################
# /viewnext -> moves to the next entry and grabs data from DB and views it
# /viewlist -> get new set of eids and set current to new img to view
################################################################################
@app.route("/viewnext", methods=["GET","POST"])
@app.route("/viewlist", methods=["POST"])
@login_required
def viewnext():
sels={}
sels['fname']='true'
sels['faces']='true'
sels['distance']='true'
if request.method=="POST":
id = request.form['current']
eids=request.form['eids']
sels['fname']=request.form['fname']
sels['faces']=request.form['faces']
sels['distance']=request.form['distance']
lst = eids.split(',')
new_id = lst[lst.index(id)+1]
obj = Entry.query.join(File).filter(Entry.id==new_id).first()
def viewlist():
OPT=SetViewingOptions( request )
# Get next/prev set of data - e.g. if next set, then it will use orig_url
# to go forward how_many from offset and then use viewer.html to show that
# first obj of the new list of entries
entries=GetEntries( OPT )
# this occurs when we went from the last image on a page (with how_many on
# it) and it just happened to also be the last in the DB...
if not entries:
# undo the skip by how_many and getentries again
OPT['offset'] -= int(OPT['how_many'])
entries=GetEntries( OPT )
# now flag we are at the last in db, to reset current below
OPT['last_entry_in_db']=1
objs = {}
eids=""
for e in entries:
objs[e.id]=e
# get new eids for viewer.html
eids=eids+f"{e.id},"
# put locn data back into array format
for face in obj.file_details.faces:
for face in e.file_details.faces:
face.locn = json.loads(face.locn)
return render_template("viewer.html", obj=obj, eids=eids, sels=sels )
################################################################################
# /viewprev -> moves to the prev entry and grabs data from DB and views it
################################################################################
@app.route("/viewprev", methods=["GET","POST"])
@login_required
def viewprev():
sels={}
sels['fname']='true'
sels['faces']='true'
sels['distance']='true'
if request.method=="POST":
id = request.form['current']
eids=request.form['eids']
sels['fname']=request.form['fname']
sels['faces']=request.form['faces']
sels['distance']=request.form['distance']
lst = eids.split(',')
new_id = lst[lst.index(id)-1]
obj = Entry.query.join(File).filter(Entry.id==new_id).first()
# put locn data back into array format
for face in obj.file_details.faces:
face.locn = json.loads(face.locn)
return render_template("viewer.html", obj=obj, eids=eids, sels=sels )
eids=eids.rstrip(",")
lst = eids.split(',')
if 'next' in request.form:
current = int(lst[0])
if 'prev' in request.form:
current = int(lst[-1])
if 'last_entry_in_db' in OPT:
# force this back to the last image of the last page - its the last in the DB, so set OPT for it
current = int(lst[-1])
OPT['last_entry_in_db']=current
return render_template("viewer.html", current=current, eids=eids, objs=objs, OPT=OPT )
################################################################################
# /view/id -> grabs data from DB and views it
################################################################################
@app.route("/view/<id>", methods=["GET","POST"])
@app.route("/view/<id>", methods=["POST"])
@login_required
def view_img(id):
obj = Entry.query.join(File).filter(Entry.id==id).first()
# put locn data back into array format
for face in obj.file_details.faces:
face.locn = json.loads(face.locn)
if request.method=="POST":
eids=request.form['eids']
else:
eids=''
sels={}
sels['fname']='true'
sels['faces']='true'
sels['distance']='true'
return render_template("viewer.html", obj=obj, eids=eids, sels=sels )
OPT=SetViewingOptions( request )
eids=request.form['eids'].rstrip(',')
objs = {}
lst = eids.split(',')
for e in Entry.query.join(File).filter(Entry.id.in_(lst)).all():
objs[e.id]=e
# put locn data back into array format
for face in e.file_details.faces:
face.locn = json.loads(face.locn)
return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT )
# route called from front/end - if multiple images are being rotated, each rotation == a separate call
# to this route (and therefore a separate rotate job. Each reponse allows the f/e to check the
@@ -652,7 +638,7 @@ def custom_static(filename):
###############################################################################
# This func creates a new filter in jinja2 to test to see if the Dir being
# checked, is a top-level folder of 'cwd'
# checked, is a top-level folder of 'OPT['cwd']'
################################################################################
@app.template_filter('TopLevelFolderOf')
def _jinja2_filter_toplevelfolderof(path, cwd):