Compare commits

...

7 Commits

11 changed files with 269 additions and 198 deletions

9
TODO
View File

@@ -1,12 +1,3 @@
* new viewing model (get ids of query on first load, then paginate only inside that known list)
- BUT, when we finish a delete, what do I do with pageList / entryList???
- start by showing them as deleted (via amend)
- then on success, remove the ids from the *List arrays in js -- but do
this via repagination, invalidate page cache fully, then getPage(currentPage)
(e.g. assume 1, 2, 3 ... 40 in eList). delete 23, 24,
then reset lists to remove 23 and 24, pageList would then
get reset to page with: 21,22,25,26 ... 30, 31
? get rid of style and just use class -- think this should work, so change in
templates/files.html for throbber, etc. and dont set style as much in view_support.js

View File

@@ -32,6 +32,7 @@ class EntryAmendment(PA,db.Model):
job_id = db.Column(db.Integer, db.ForeignKey("job.id"), primary_key=True )
amend_type = db.Column(db.Integer, db.ForeignKey("amendment_type.id"))
type = db.relationship("AmendmentType", backref="entry_amendment")
job = db.relationship("Job", back_populates="amendments")
################################################################################

View File

@@ -5,6 +5,7 @@ from main import db, app, ma
from sqlalchemy import Sequence, text, select, union, or_
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import joinedload
import numbers
import os
import glob
import json
@@ -266,6 +267,22 @@ class EntrySchema(ma.SQLAlchemyAutoSchema):
def get_full_path(self, obj):
return obj.FullPathOnFS()
class JobExtraSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = JobExtra
load_instance = True
name = ma.auto_field()
value = ma.auto_field()
class JobSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Job
load_instance = True
id = ma.auto_field()
name = ma.auto_field()
extra = ma.Nested(JobExtraSchema, many=True)
amendments = ma.Nested(EntryAmendmentSchema, many=True)
# global - this will be use more than once below, so do it once for efficiency
entries_schema = EntrySchema(many=True)
FOT_Schema = FaceOverrideTypeSchema(many=True)
@@ -273,6 +290,8 @@ path_Schema = PathSchema(many=True)
person_Schema = PersonSchema(many=True)
et_schema = AmendmentTypeSchema(many=True)
ea_schema = EntryAmendmentSchema(many=True)
job_schema = JobSchema(many=False)
job_schemas = JobSchema(many=True)
################################################################################
# /get_entries_by_ids -> route where we supply list of entry ids (for next/prev
@@ -312,7 +331,7 @@ def process_ids():
ea = db.session.execute(stmt).unique().scalars().all()
ea_data=ea_schema.dump(ea)
return jsonify(entries=entries_schema.dump(sorted_data), amend=ea_data)
return jsonify(entries=entries_schema.dump(sorted_data), amendments=ea_data)
################################################################################
@@ -629,7 +648,7 @@ def restore_files():
jex.append( JobExtra( name=f"{el}", value=str(request.form[el]) ) )
job=NewJob( name="restore_files", num_files=0, wait_for=None, jex=jex, desc="to restore selected file(s)" )
return redirect("/jobs")
return jsonify( job=job_schema.dump(job) )
################################################################################
# /delete_files -> create a job to delete files for the b/e to process
@@ -642,7 +661,7 @@ def delete_files():
jex.append( JobExtra( name=f"{el}", value=str(request.form[el]) ) )
job=NewJob( name="delete_files", num_files=0, wait_for=None, jex=jex, desc="to delete selected file(s)" )
return redirect("/jobs")
return jsonify( job=job_schema.dump(job) )
################################################################################
# /move_files -> create a job to move files for the b/e to process
@@ -685,7 +704,7 @@ def view():
# route called from front/end - if multiple images are being transformed, each transorm == a separate call
# to this route (and therefore a separate transorm job. Each reponse allows the f/e to check the
# specific transorm job is finished (/check_transform_job) which will be called (say) every 1 sec. from f/e
# specific transorm job is finished (/check_amend_job_status) which will be called (say) every 1 sec. from f/e
# with a spinning wheel, then when pa_job_mgr has finished it will return the transformed thumb
@app.route("/transform", methods=["POST"])
@login_required
@@ -698,27 +717,28 @@ def transform():
jex.append( JobExtra( name=f"{el}", value=str(request.form[el]) ) )
job=NewJob( name="transform_image", num_files=0, wait_for=None, jex=jex, desc="to transform selected file(s)" )
return jsonify( job_id=job.id )
return jsonify( job=job_schema.dump(job) )
################################################################################
# /check_transform_job -> URL that is called repeatedly by front-end waiting for the
# b/e to finish the transform job. Once done, the new / now
# transformed image's thumbnail is returned so the f/e can
# update with it
# /check_amend_job_status -> URL that is called repeatedly by front-end waiting
# for the b/e to finish the amendment job (delete/restore/move file).
# Once done, return "ok"
################################################################################
@app.route("/check_transform_job", methods=["POST"])
@app.route("/check_amend_job_status", methods=["POST"])
@login_required
def check_transform_job():
def check_amend_job_status():
job_id = request.form['job_id']
stmt=select(Job).where(Job.id==job_id)
job=db.session.execute(stmt).scalars().one_or_none()
j=jsonify( finished=False )
if job.pa_job_state == 'Completed':
id=[jex.value for jex in job.extra if jex.name == "id"][0]
stmt=select(Entry).where(Entry.id==id)
stmt = select(Job).options(joinedload(Job.amendments)).where(Job.id == job_id)
job=db.session.execute(stmt).unique().scalars().first()
# FIXME: should validate job_id is real from UI
if job.name == 'transform_image':
eid=[jex.value for jex in job.extra if jex.name == "id"][0]
stmt=select(Entry).where(Entry.id==eid)
ent=db.session.execute(stmt).scalars().all()
ent_data=entries_schema.dump(ent)
j=jsonify( finished=True, entry=ent_data[0] )
j=jsonify(finished=(job.pa_job_state == 'Completed'), job=job_schema.dump(job), entry=ent_data[0] )
else:
j=jsonify(finished=(job.pa_job_state == 'Completed'), job=job_schema.dump(job))
return j
################################################################################

View File

@@ -151,6 +151,37 @@ function MoveDBox(path_details)
$("#suffix").keypress(function (e) { if (e.which == 13) { $("#move_submit").click(); return false; } } )
}
// This function is called anytime we have a job that returns amendments
// (visually we want to show this entry is being amended by a job)
// as we check for a job to end every second, we can call this multiple times
// during the runtime of a job, so only redraw/react to a new amendment
// NOTE: we update all views, as we might go into one via jscript before the job ends
function processAmendments( ams )
{
for (const am of ams)
{
// if we return anything here, we already have this amendment, so continue to next
if( document.amendments.filter(obj => obj.eid === am.eid).length > 0 )
continue
document.amendments.push(am)
if( document.viewing && document.viewing.id == am.eid )
{
im.src=im.src + '?t=' + new Date().getTime();
DrawImg()
}
// find where in the page this image is being viewed
idx = pageList.indexOf(am.eid)
// createFigureHtml uses matching document.amendments to show thobber, etc
html = createFigureHtml( document.entries[idx] )
$('#'+am.eid).replaceWith( html )
}
}
// function to add data for document.amendment based on id and amt
// used when we transform several images in files_*, or single image in viewer
// show the DBox for a delete/restore file, includes all thumbnails of selected files
// with appropriate coloured button to Delete or Restore files`
function DelDBox(del_or_undel)
@@ -159,28 +190,36 @@ function DelDBox(del_or_undel)
$('#dbox-title').html(del_or_undel+' Selected File(s)')
div ='<div class="row col-12"><p class="col">' + del_or_undel + ' the following files?</p></div>'
div+=GetSelnAsDiv()
if( del_or_undel == "Delete" )
{
which="delete"
col="danger"
}
else
{
which="restore"
col="sucess"
}
document.ents_to_del=[]
$('.highlight').each(function( cnt ) { document.ents_to_del[cnt]=parseInt($(this).attr('id')) } )
div+=`<div class="row col-12 mt-3">
<button onClick="$('#dbox').modal('hide')" class="btn btn-outline-secondary col-2">Cancel</button>
`
div+=`
<button onClick="MoveOrDelCleanUpUI(); $.ajax({ type: 'POST', data: to_del, url:
`
if( del_or_undel == "Delete" )
div+=`
'/delete_files',
<button onClick="
$.ajax({ type: 'POST', data: to_del, url: '/${which}_files',
success: function(data) {
if( $(location).attr('pathname').match('search') !== null || document.viewing ) { window.location='/' }; CheckForJobs() } }); return false" class="btn btn-outline-danger col-2">Ok</button>
</div>
`
else
// just force page reload to / for now if restoring files from a search path -- a search (by name)
// would match the deleted/restored file, so it would be complex to clean up the UI (and can't reload, as DB won't be changed yet)
div+=`
'/restore_files',
success: function(data){
if( $(location).attr('pathname').match('search') !== null || document.viewing ) { window.location='/' }; CheckForJobs() } }); return false" class="btn btn-outline-success col-2">Ok</button>
</div>
`
// FIXME: what is the ! search stuff for???
// FIXME: really, also why not show 'delete' throbber, and on success of actual delete go back to /
if( $(location).attr('pathname').match('search') !== null || document.viewing ) { window.location='/' }
processAmendments( data.job.amendments )
checkForAmendmentJobToComplete(data.job.id)
}
});
$('#dbox').modal('hide')
return false"
class="btn btn-outline-${col} col-2">Ok</button>
</div>`
$('#dbox-content').html(div)
$('#dbox').modal('show')
}
@@ -408,7 +447,7 @@ function createFigureHtml( obj )
path_type="${pathType}" size="${size}" hash="${hash}" in_dir="${inDir}"
fname="${fname}" yr="${yr}" date="${date}" pretty_date="${prettyDate}" type="${type}">
${renderMedia(obj,gs,am_html)}
</figure>`;
`
}
// Directory entry
else if (obj.type.name === "Directory" && OPT.folders) {
@@ -422,7 +461,6 @@ function createFigureHtml( obj )
<use xlink:href="/internal/icons.svg#Directory"></use>
</svg>
<figcaption class="svg_cap figure-caption text-center text-wrap text-break">${obj.name}</figcaption>
</figure>
`;
html += `<script>f=$('#${obj.id}'); w=f.find('svg').width(); f.find('figcaption').width(w);</script>`;
}
@@ -434,7 +472,8 @@ function createFigureHtml( obj )
$('#${obj.id}').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
$('#${obj.id}').dblclick( function(e) { startViewing( $(this).attr('id') ) } )
}
</script>`
</script>
</figure>`
return html
}
@@ -621,6 +660,7 @@ function getEntriesByIdSuccessHandler(res,pageNumber,successCallback,viewingIdx)
document.entries=res;
// cache this
document.page[pageNumber]=res
// FIXME: I want to remove successCallback, instead: if viewing, or files_*, or file_list, then call relevant draw routine
successCallback(res,viewingIdx)
resetNextPrevButtons()
// if search, disable folders
@@ -663,11 +703,11 @@ function getPage(pageNumber, successCallback, viewingIdx=0)
data: JSON.stringify(data), contentType: 'application/json',
dataType: 'json',
success: function(res) {
document.amendments=res.amend;
// this is only called when we are viewing a page in files/list view, so check for job(s) ending...
for (const tmp of document.amendments) {
CheckTransformJob(tmp.eid,tmp.job_id,handleTransformFiles)
}
document.amendments=res.amendments;
// only called when an amendment is pending & we are viewing a page in files/list view
// so check for amendment job(s) ending...
for (const tmp of document.amendments)
checkForAmendmentJobToComplete(tmp.job_id)
getEntriesByIdSuccessHandler( res.entries, pageNumber, successCallback, viewingIdx )
},
error: function(xhr, status, error) { console.error("Error:", error); } });
@@ -811,6 +851,64 @@ function changeSize()
$('.svg_cap').width(sz);
}
// when a delete or restore files job has completed successfullly, then get ids
// find the page we are on, remove amendments & ids from entryList and re-get page
// which will reset pageList and the UI of images for that page
function handleDeleteOrRestoreFileJobCompleted(job)
{
// this grabs the values from the object attributes of eid-0, eid-1, etc.
const ids = job.extra.map(item => item.value)
// find page number of first element to delete (this is the page we will return too)
pnum=getPageNumberForId( ids[0] )
// remove amendment data
for (const ent of ids)
{
id=parseInt(ent)
removeAmendment( id )
// remove the item in the entryList
index=entryList.indexOf(id);
if( index != -1 )
entryList.splice(index, 1); // Remove the element
else
{
return; // have to get out of here, or calling getPage() below will loop forever
}
}
// re-create pageList by reloading the page
getPage(pnum,getPageFigures)
}
// POST to a check URL, that will tell us if the amendment job has completed,
// it also calls CheckForJobs() which will fix up the Active Jobs badge,
function checkForAmendmentJobToComplete(job_id)
{
CheckForJobs()
$.ajax( { type: 'POST', data: '&job_id='+job_id, url: '/check_amend_job_status',
success: function(res) { handleCheckAmendmentJobStatus(res); } } )
}
// the status of a Amendment Job has been returned, finished is True/False
// if not finished try again in 1 second... If finished then invalidate page
// cache and based on job type call code correct func to update the UI appropriately
function handleCheckAmendmentJobStatus(data)
{
if( data.finished )
{
// invalidate the cache
document.page.length=0
// transforms contain the single transformed entry data for convenience
if( data.job.name == 'transform_image' )
handleTransformImageJobCompleted(data.job, data.entry)
else if ( data.job.name == 'delete_files' || data.job.name == 'restore_files' )
handleDeleteOrRestoreFileJobCompleted(data.job)
}
else { setTimeout( function() { checkForAmendmentJobToComplete(data.job.id) }, 1000 ); }
}
// different context menu on files
$.contextMenu({
selector: '.entry',

View File

@@ -2,75 +2,26 @@
// can only have 1 ammendment per image, its grayed out for other changes
function removeAmendment( id )
{
console.log( 'removing amendment for: ' + id )
document.amendments=document.amendments.filter(obj => obj.eid !== id)
}
// POST to a check URL, that will tell us if the transformation has completed,
// if not, try again in 1 second... If it has finished then reset the thumbnail
// to full colour, put it back to being an entry and reset the thumbnail to the
// newly created one that was sent back in the response to the POST
function handleTransformFiles(data,id,job_id)
// If Transform job has finished then reset relevant document.entries
// with updated from DB, remove the amendment and redraw image
function handleTransformImageJobCompleted(job, entry)
{
if( data.finished )
{
id=parseInt(id)
idx = entryList.indexOf(id)
// replace data for this entry now its been transformed
document.entries[idx]=data.entry
// update cache too
// document.page[getPageNumberForId(id)][howFarIntoPageCache(id)]=data.entry
// FIXME: for now just invalidate whole cache
document.page.length=0
removeAmendment( id )
// redraw into figure html in dom
last={ 'printed': 'not required' }
html = createFigureHtml( data.entry, last, 9999 )
$('#'+id).replaceWith( html )
return false;
}
else
{
setTimeout( function() { CheckTransformJob(id,job_id,handleTransformFiles) }, 1000,id, job_id );
}
}
// POST to a check URL, that will tell us if the transformation has completed,
// if not, try again in 1 second... If it has finished then reset the image
// to full colour
function handleTransformViewing(data,id,job_id)
{
if( data.finished )
{
// stop throbber, remove grayscale & then force reload with timestamped version of im.src
removeAmendment( entry.id )
// update viewer and files* views, in case we view/go up without a new page load
// force reload with timestamped version of im.src
im.src=im.src + '?t=' + new Date().getTime();
removeAmendment( id )
return false;
}
else
{
setTimeout( function() { CheckTransformJob(id,job_id,handleTransformViewing) }, 1000,id, job_id );
}
}
DrawImg()
// POST to a check URL, that will tell us if the transformation has completed,
// if not, try again in 1 second... If it has finished then reset the thumbnail
// to full colour, put it back to being an entry and reset the thumbnail to the
// newly created one that was sent back in the response to the POST
function CheckTransformJob(id,job_id,successCallback)
{
CheckForJobs()
$.ajax( { type: 'POST', data: '&job_id='+job_id, url: '/check_transform_job',
success: function(res) { successCallback(res,id,job_id); } } )
}
// function to add data for document.amendment based on id and amt
// used when we transform several images in files_*, or single image in viewer
function addTransformAmendment(id,amt)
{
am={}
am.eid=parseInt(id)
am.type = document.amendTypes.filter(obj => obj.job_name === 'transform_image:'+amt )[0]
document.amendments.push(am)
idx = entryList.indexOf(entry.id)
// replace data for this entry now its been transformed
document.entries[idx]=entry
// redraw into figure html in dom
html = createFigureHtml( entry )
$('#'+entry.id).replaceWith( html )
}
// for each highlighted image, POST the transform with amt (90, 180, 270,
@@ -81,15 +32,13 @@ function addTransformAmendment(id,amt)
function Transform(amt)
{
// we are in the viewer with 1 image only...
if( document.viewing )
if( $('#viewer_div').length && ! $('#viewer_div').hasClass('d-none') )
{
post_data = '&amt='+amt+'&id='+document.viewing.id
// POST /transform for image, grayscale the image, add throbber, & start checking for end of job
$.ajax({ type: 'POST', data: post_data, url: '/transform', success: function(data) {
addTransformAmendment(document.viewing.id, amt)
DrawImg();
CheckTransformJob(document.viewing.id,data.job_id,handleTransformViewing);
return false;
processAmendments(data.job.amendments)
checkForAmendmentJobToComplete(data.job.id)
} })
}
else
@@ -98,13 +47,8 @@ function Transform(amt)
post_data = '&amt='+amt+'&id='+e.id
// POST /transform for image, grayscale the thumbnail, add throbber, & start checking for end of job
$.ajax({ type: 'POST', data: post_data, url: '/transform', success: function(data){
addTransformAmendment(e.id, amt)
last={ 'printed': 'not required' }
idx = pageList.indexOf(parseInt(e.id))
html = createFigureHtml( document.entries[idx], last, 9999 )
$('#'+e.id).replaceWith( html )
CheckTransformJob(e.id,data.job_id,handleTransformFiles);
return false;
processAmendments(data.job.amendments)
checkForAmendmentJobToComplete(data.job.id)
} })
} )
}

View File

@@ -71,21 +71,14 @@ function SetActiveJobsBadge(num_jobs)
// after a 1 second timeout
function CheckForJobs()
{
$.ajax(
{
$.ajax( {
type: 'POST', url: '/check_for_jobs',
success: function(data) {
data.sts.forEach(
function(el)
{
StatusMsg(el)
}
)
// for each status, handle it/make toast in UI
data.sts.forEach( function(el) { StatusMsg(el) } )
SetActiveJobsBadge(data.num_active_jobs)
if( data.num_active_jobs > 0 )
{
setTimeout( function() { CheckForJobs() }, 1000 );
}
// still active job(s), keep checking for them to end
if( data.num_active_jobs > 0 ) { setTimeout( function() { CheckForJobs() }, 1000 ); }
},
} )
return false;

View File

@@ -186,7 +186,7 @@ function ViewImageOrVideo()
if( ! document.viewing ) return
if( document.viewing.type.name == 'Image' )
{
im.src='../' + document.viewing.FullPathOnFS
im.src='../' + document.viewing.FullPathOnFS + '?t=' + new Date().getTime();
$('#video_div').hide()
if( $('#fname_toggle').prop('checked' ) )
$('#img-cap').show()
@@ -623,6 +623,8 @@ function goOutOfViewer()
// hide viewer div, then show files_div
$('#viewer_div').addClass('d-none')
$('#files_div').removeClass('d-none')
// no longer viewing an image too
document.viewing=null
}
// change the viewer to the previous entry (handle page change too)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

31
job.py
View File

@@ -4,6 +4,7 @@ from flask import request, render_template, redirect, make_response, jsonify, ur
from settings import Settings
from main import db, app, ma
from sqlalchemy import Sequence, func, select
from sqlalchemy.orm import joinedload
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime, timedelta
import pytz
@@ -58,10 +59,12 @@ class Job(db.Model):
extra = db.relationship( "JobExtra")
logs = db.relationship( "Joblog")
amendments = db.relationship("EntryAmendment", back_populates="job")
def __repr__(self):
return "<id: {}, start_time: {}, last_update: {}, name: {}, state: {}, num_files: {}, current_file_num: {}, current_file: {}, pa_job_state: {}, wait_for: {}, extra: {}, logs: {}>".format(self.id, self.start_time, self.last_update, self.name, self.state, self.num_files, self.current_file_num, self.current_file, self.pa_job_state, self.wait_for, self.extra, self.logs)
################################################################################
# Class describing PA_JobManager_Message and in the DB (via sqlalchemy)
# the job manager can send a message back to the front end (this code) via the
@@ -83,7 +86,7 @@ class PA_JobManager_Message(PA,db.Model):
# Used in main html to show a red badge of # jobs to draw attention there are
# active jobs being processed in the background
################################################################################
def GetNumActiveJobs():
def getNumActiveJobs():
ret=Job.query.filter(Job.pa_job_state != 'Completed').with_entities(func.count(Job.id).label('count') ).first()
return ret[0]
@@ -122,16 +125,16 @@ def NewJob(name, num_files="0", wait_for=None, jex=None, desc="No description pr
if job.name == 'transform_image':
id=[jex.value for jex in job.extra if jex.name == "id"][0]
ea=EntryAmendment( eid=id, job_id=job.id, amend_type=at_id )
print( f"just added an EA for eid={id}, j={job.id}" )
db.session.add(ea)
elif job.name == 'delete_files':
job.amendments.append(ea)
# FIXME: add ea to job.amend
elif job.name == 'delete_files' or job.name == 'restore_files':
for j in jex:
if 'eid-' in j.name:
ea=EntryAmendment( eid=j.value, amend_type=at_id )
ea=EntryAmendment( eid=j.value, job_id=job.id, amend_type=at_id )
db.session.add(ea)
# need to return this to the f/e somehow
# this is for removes, really need to think about this more
#job.amendment=ea
# FIXME: add ea to job.amend
job.amendments.append(ea)
SetFELog( message=f'Created <a class="link-light" href="/job/{job.id}">Job #{job.id}</a> to {desc}', level="success" )
WakePAJobManager(job.id)
@@ -327,14 +330,22 @@ def joblog_search():
@app.route("/check_for_jobs", methods=["POST"])
@login_required
def check_for_jobs():
num=GetNumActiveJobs()
from files import job_schemas
num=getNumActiveJobs()
messages = PA_JobManager_Message.query.all()
sts=[]
for msg in PA_JobManager_Message.query.all():
for msg in messages:
u=''
if 'Job #' not in msg.message and msg.job_id:
u='<a class="link-light" href="' + url_for('joblog', id=msg.job_id) + '">Job #' + str(msg.job_id) + '</a>: '
sts.append( { 'id': msg.id, 'message': u+msg.message, 'level': msg.level, 'job_id': msg.job_id, 'persistent': msg.persistent, 'cant_close': msg.cant_close } )
return make_response( jsonify( num_active_jobs=num, sts=sts ) )
# get jobs mentioned in messages as we may need to process the by client for UI
job_list=[obj.job_id for obj in messages]
stmt = select(Job).options(joinedload(Job.amendments)).where(Job.id.in_(job_list))
jobs=db.session.execute(stmt).unique().scalars().all()
return make_response( jsonify( num_active_jobs=num, sts=sts, jobs=job_schemas.dump(jobs) ) )
###############################################################################
# /clear_msg -> POST -> clears out a F/E message based on passed in <id>

View File

@@ -886,7 +886,6 @@ def RunJob(job):
elif job.name == "run_ai_on_path":
JobRunAIOnPath(job)
elif job.name == "transform_image":
#time.sleep(10)
JobTransformImage(job)
elif job.name == "clean_bin":
JobCleanBin(job)
@@ -1874,6 +1873,20 @@ def JobRunAIOn(job):
FinishJob(job, "Finished Processesing AI")
return
################################################################################
# removeEntryAmendment(): helper routine to remove an Etnry Amendment for a
# given job and eid (called after Transform or Delete/Restore/Move files
################################################################################
def removeEntryAmendment( job, eid ):
# now remove the matching amendment for the transform job
stmt=select(EntryAmendment).where(EntryAmendment.eid==eid)
ea=session.execute(stmt).scalars().one_or_none()
if ea:
session.delete(ea)
else:
AddLogForJob( job, f"ERROR: failed to remove entry amendment in DB for this transformation? (eid={id})" )
PAprint( f"ERROR: failed to remove entry amendment in DB for this transformation? (eid={id}, job={job} )" )
####################################################################################################################################
# JobTransformImage(): transform an image by the amount requested (can also flip horizontal or vertical)
####################################################################################################################################
@@ -1907,14 +1920,7 @@ def JobTransformImage(job):
e.file_details.hash = md5( job, e )
PAprint( f"JobTransformImage DONE thumb: job={job.id}, id={id}, amt={amt}" )
session.add(e)
# now remove the matching amendment for the transform job
stmt=select(EntryAmendment).where(EntryAmendment.eid==id)
ea=session.execute(stmt).scalars().one_or_none()
if ea:
session.delete(ea)
else:
AddLogForJob( job, f"ERROR: failed to remove entry amendment in DB for this transformation? (eid={id})" )
PAprint( f"ERROR: failed to remove entry amendment in DB for this transformation? (eid={id}, job={job} )" )
removeEntryAmendment( job, id )
FinishJob(job, "Finished Processesing image rotation/flip")
return
@@ -2207,6 +2213,7 @@ def JobDeleteFiles(job):
if 'eid-' in jex.name:
del_me=session.query(Entry).join(File).filter(Entry.id==jex.value).first()
MoveFileToRecycleBin(job,del_me)
removeEntryAmendment(job,del_me.id)
NewJob( name="check_dups", num_files=0, wait_for=None, jex=None, parent_job=None, desc="check for duplicate files" )
FinishJob(job, f"Finished deleting selected file(s)")
return
@@ -2221,6 +2228,7 @@ def JobRestoreFiles(job):
if 'eid-' in jex.name:
restore_me=session.query(Entry).join(File).filter(Entry.id==jex.value).first()
RestoreFile(job,restore_me)
removeEntryAmendment(job,restore_me.id)
NewJob( name="check_dups", num_files=0, wait_for=None, jex=None, parent_job=None, desc="check for duplicate files" )
FinishJob(job, f"Finished restoring selected file(s)")
return

View File

@@ -13,6 +13,9 @@ else
sudo -u pauser gunicorn --bind=0.0.0.0:80 --workers=1 --threads=1 main:app --env ENV="development" --error-logfile gunicorn.error.log --access-logfile gunicorn.log --capture-output --enable-stdio-inheritance --reload
fi
# warm the cache to see if this helps with odd restart 404s
curl -sf http://localhost/health
# this should never be invoked unless gunicorn fails -- in that case, at least
# we will keep the container can login by hand and check the issue/error
sleep 99999