updated BUGs in general to remove older / fixed BUGs relating to the confusion of current/eids, etc.
update amendments in tables.sql to include job_id in entry_ammendment added amend.py to move amendment-related code into its own file when we create a job (NewJob) and that job matches an amendmentType (via job_name or job_name:amt <- where amt relates to how we do a transform_image), then we enter a new EntryAmendment pa_job_mgr knows when a Transform job ends, and removes relevant EntryAmendment files*.js use EntryAmendment data to render thumbnails with relevant AmendmentType if a normal page load (like /files_ip), and there is an EntryAmendment, mark up the thumb, run the check jobs to look for completion of the job, removeal of the EntryAmendment and update the entry based on 'transformed' image OVERALL: this is a functioning version that uses EntryAmendments and can handle loading a new page with outstanding amendments and 'deals' with it. This is a good base, but does not cater for remove_files or move_files
This commit is contained in:
19
BUGs
19
BUGs
@@ -1,15 +1,7 @@
|
||||
### Next: 141
|
||||
### Next: 143
|
||||
BUG-142: after transforming, the face data is still in the old spots, really should delete it / make it recalc
|
||||
BUG-141: can currently try to flip a video (in a highlighted group)
|
||||
BUG-140: When db is restarted underneath PA, it crashes job mgr... It should just accept timeouts, and keep trying to reconnect every 2? mins
|
||||
BUG-139: using any large entry list and going next a few times, ends say 4 pages of 50 into 4000 matches (entries from DB < 50)...
|
||||
- confirmed this is when person has 2 or more refimgs:
|
||||
- on page "2", we get 49 pulled back in the ORM instead of the 50 expected -- b/c I use that to indicate we must be at the end of the list if not 50 found
|
||||
-- really, need to fix once and for all the eids / re-running query.
|
||||
do GetEntries as we do now, once done however, get all entry ids. Stick those into the DB with a unique query-id and datestamp
|
||||
new func to get all details needed for entries in an eid list (of 1-page) - show this page of entries
|
||||
use current, full eidlist and to work our start/end of list (next/prev), disabling.
|
||||
then client can keep current page of data, if you hit next/prev, use DB unique query id / full list and page of eids, and give full data for new page of entries
|
||||
Implications though, are if a search is invalidated (maybe delete / move a photo), need to remove them from the list on the DB too OR let user know/decide to fix/wait.
|
||||
|
||||
|
||||
BUG-100: I managed to get 2 photos matching mich in the NOT_WORKING photo (probably dif refimgs but same p.tag?)
|
||||
= /photos/2012/20120414-damien/IMG_8467.JPG
|
||||
@@ -31,11 +23,6 @@ BUG-125: when an image is highlighted, then post the contextmenu on a different
|
||||
There is a chance we need to change the document on click to a mouse down (or whatever the context menu
|
||||
uses for default), rather than just fix the highlight
|
||||
|
||||
BUG-130: moving files and then trying to go next page and it got confused...
|
||||
BUG-132: right arrow to go to next photo in viewer ALSO scrolls to the right, needs a return somewhere in the jscript
|
||||
BUG-133: when rebuilding pa[dev], the first run fails to have symlinks to the right paths for Import/Storage, etc. a simple restart fixes - so potentially the intial copy or some other race condition?
|
||||
BUG-134: when moving set of photos on page, then move another set of photos on page, the first set reappears. Could really delete them from the dom?
|
||||
BUG-135: failed to rotate: 2006/20061215-ITS-xmas-KP/DSC00582.JPG - not sure why && not repeatable, so its not the image, timing/race condition maybe?
|
||||
BUG-137: after moving/refiling photos, the next shift-click is out of order (reload fixes it)
|
||||
BUG-138: Placeholder for all the ways we can get the front-end confused:
|
||||
---> JUST fix all these BUGs (relating to confused/lost state) by revisiting the overally complex way I remember state and my position in a list (probably FAR easier, to make an initial sql just save all eids, and then not try to recreate that list ever again and not care how I got into the list). Can attach a "running server-side sequence number", and if old sequence, and the original eid list results in a failure, then just pop up that the saved list is no longer valid, and ask user to re-do their search/list..."
|
||||
|
||||
26
TODO
26
TODO
@@ -1,18 +1,18 @@
|
||||
### major fix - go to everywhere I call GetEntries(), and redo the logic totally...
|
||||
* client side:
|
||||
* instead of removing deleted images from DOM, we should gray them out and put a big Del (red circle with line?) though it as overlay.
|
||||
[DONE] * Create another table of entry_ammendments - note the deletions, rotations, flips of specific eids - then reproduce that on the client side visually as needed
|
||||
[DONE] - at least grayed-out, to indicate a pending action is not complete.
|
||||
- When job that flips, rotates, deletes completes then create an entry_amendment in the DB.
|
||||
- Also hand fudge the jscript amendments for each job / next get_entry_by_id (if needed will also set amendments as needed)
|
||||
- When job finishes, remove amendment from DB
|
||||
- when job finishes, remove amendment from document.amendments
|
||||
need to rework all the throbber stuff, I think it is probably better not to have a div I never use with the throbber in it, just add when I need it...
|
||||
like in code for amendments. Also get rid of style and just use class
|
||||
* 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
|
||||
|
||||
### GENERAL
|
||||
* jobs for AI should show path name
|
||||
* rm dups job should show progress bar
|
||||
* jobs for AI should show path name
|
||||
* rm dups job should show progress bar
|
||||
* in viewer, there is no move button (maybe add one?)
|
||||
* think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
|
||||
- SHOULD JUST get AI to help clean-up and write defensive code here...
|
||||
|
||||
@@ -94,7 +94,7 @@ function MoveOrDelCleanUpUI()
|
||||
// remove the images being moved (so UI immediately 'sees' the move)
|
||||
$("[name^=eid-]").each( function() { $('#'+$(this).attr('value')).remove() } )
|
||||
// reorder the images via ecnt again, so future highlighting can work
|
||||
document.mf_id=0; $('.figure').each( function() { $(this).attr('ecnt', document.mf_id ); document.mf_id++ } )
|
||||
// document.mf_id=0; $('.figure').each( function() { $(this).attr('ecnt', document.mf_id ); document.mf_id++ } )
|
||||
$('#dbox').modal('hide')
|
||||
}
|
||||
|
||||
@@ -354,34 +354,27 @@ function NoSel() {
|
||||
return true
|
||||
}
|
||||
|
||||
// quick wrapper to add a single <figure> to the #figures div
|
||||
function addFigure( obj )
|
||||
{
|
||||
html=createFigureHtml( obj )
|
||||
$('#figures').append( html )
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a group header or entry based on the object and options.
|
||||
* obj - The object containing file/directory details.
|
||||
* last - Tracks the last printed group (e.g., { printed: null }).
|
||||
* ecnt - Entry counter (e.g., { val: 0 }).
|
||||
* returns {string} - Generated HTML string.
|
||||
*/
|
||||
function addFigure( obj, last, ecnt )
|
||||
function createFigureHtml( obj )
|
||||
{
|
||||
let html = "";
|
||||
// if am is null, no amendment for this obj, otherwise we have one
|
||||
var am=null
|
||||
for (const tmp of document.amendments)
|
||||
if( tmp.eid == obj.id )
|
||||
am=tmp
|
||||
|
||||
// Grouping logic
|
||||
if (OPT.grouping === "Day") {
|
||||
if (last.printed !== obj.file_details.day) {
|
||||
html += `<div class="row ps-3"><h6>Day: ${obj.file_details.day} of ${obj.file_details.month}/${obj.file_details.year}</h6></div>`;
|
||||
last.printed = obj.file_details.day;
|
||||
}
|
||||
} else if (OPT.grouping === "Week") {
|
||||
if (last.printed !== obj.file_details.woy) {
|
||||
html += `<div class="row ps-3"><h6>Week #: ${obj.file_details.woy} of ${obj.file_details.year}</h6></div>`;
|
||||
last.printed = obj.file_details.woy;
|
||||
}
|
||||
} else if (OPT.grouping === "Month") {
|
||||
if (last.printed !== obj.file_details.month) {
|
||||
html += `<div class="row ps-3"><h6>Month: ${obj.file_details.month} of ${obj.file_details.year}</h6></div>`;
|
||||
last.printed = obj.file_details.month;
|
||||
}
|
||||
}
|
||||
let html = "";
|
||||
|
||||
// Image/Video/Unknown entry
|
||||
if (obj.type.name === "Image" || obj.type.name === "Video" || obj.type.name === "Unknown") {
|
||||
@@ -395,13 +388,27 @@ function addFigure( obj, last, ecnt )
|
||||
const prettyDate = `${obj.file_details.day}/${obj.file_details.month}/${obj.file_details.year}`;
|
||||
const type = obj.type.name;
|
||||
|
||||
// if amendment for this obj, do not add entry class - prevents highlighting
|
||||
if( am ) {
|
||||
ent=""
|
||||
gs="style='filter: grayscale(100%);'"
|
||||
am_html ='<img class="position-absolute top-50 start-50 translate-middle" height="60" src="/internal/white-circle.png">'
|
||||
am_html +='<img class="position-absolute top-50 start-50 translate-middle" height="64" src="/internal/throbber.gif">'
|
||||
if( am.type.which == 'icon' )
|
||||
am_html+=`<svg class="position-absolute top-50 start-50 translate-middle" height="32" style="color:${am.type.colour}" fill="${am.type.colour}"><use xlink:href="/internal/icons.svg#${am.type.what}"></use></svg>`
|
||||
else
|
||||
am_html+=`<img class="position-absolute top-50 start-50 translate-middle" src="/internal/${am.type.what}?v={{js_vers['r270']}}" height="32">`
|
||||
} else {
|
||||
ent="entry"
|
||||
gs=""
|
||||
am_html=""
|
||||
}
|
||||
html += `
|
||||
<figure id="${obj.id}" ecnt="${ecnt}" class="col col-auto g-0 figure entry m-1"
|
||||
<figure id="${obj.id}" class="col col-auto g-0 figure ${ent} m-1"
|
||||
path_type="${pathType}" size="${size}" hash="${hash}" in_dir="${inDir}"
|
||||
fname="${fname}" yr="${yr}" date="${date}" pretty_date="${prettyDate}" type="${type}">
|
||||
${renderMedia(obj)}
|
||||
</figure>
|
||||
`;
|
||||
${renderMedia(obj,gs,am_html)}
|
||||
</figure>`;
|
||||
}
|
||||
// Directory entry
|
||||
else if (obj.type.name === "Directory" && OPT.folders) {
|
||||
@@ -410,7 +417,7 @@ function addFigure( obj, last, ecnt )
|
||||
: obj.dir_details.in_path.path_prefix;
|
||||
|
||||
html += `
|
||||
<figure class="col col-auto g-0 dir entry m-1" id="${obj.id}" ecnt="${ecnt}" dir="${dirname}" type="Directory">
|
||||
<figure class="col col-auto g-0 dir entry m-1" id="${obj.id}" dir="${dirname}" type="Directory">
|
||||
<svg class="svg" width="${OPT.size - 22}" height="${OPT.size - 22}" fill="currentColor">
|
||||
<use xlink:href="/internal/icons.svg#Directory"></use>
|
||||
</svg>
|
||||
@@ -419,66 +426,42 @@ function addFigure( obj, last, ecnt )
|
||||
`;
|
||||
html += `<script>f=$('#${obj.id}'); w=f.find('svg').width(); f.find('figcaption').width(w);</script>`;
|
||||
}
|
||||
|
||||
$('#figures').append( html )
|
||||
|
||||
// check if there is a pending amendment for this entry, if so mark it up
|
||||
// (e.g. its being deleted, rotated, etc) - details in the am obj
|
||||
for (const am of document.amendments)
|
||||
{
|
||||
if( am.eid == obj.id )
|
||||
{
|
||||
$('#'+obj.id).find('img.thumb').attr('style', 'filter: grayscale(100%);' )
|
||||
$('#'+obj.id).removeClass('entry')
|
||||
html='<img class="position-absolute top-50 start-50 translate-middle" height="60" src="/internal/white-circle.png">'
|
||||
html+='<img class="position-absolute top-50 start-50 translate-middle" height="64" src="/internal/throbber.gif">'
|
||||
if( am.type.which == 'icon' )
|
||||
html+=`<svg class="position-absolute top-50 start-50 translate-middle" height="32" style="color:${am.type.colour}" fill="${am.type.colour}"><use xlink:href="/internal/icons.svg#${am.type.what}"></use></svg>`
|
||||
else
|
||||
html+=`<img class="position-absolute top-50 start-50 translate-middle" src="/internal/${am.type.what}?v={{js_vers['r270']}}" height="32">`
|
||||
$('#'+obj.id).find('a').append(html)
|
||||
// moved the bindings to here as we need to reset them if we recreate this Figure (after a transform job)
|
||||
html += `<script>
|
||||
if( "${obj.type.name}" === "Directory" ) {
|
||||
$("#${obj.id}").click( function(e) { document.back_id=this.id; getDirEntries(this.id,false) } )
|
||||
} else {
|
||||
$('#${obj.id}').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
||||
$('#${obj.id}').dblclick( function(e) { startViewing( $(this).attr('id') ) } )
|
||||
}
|
||||
}
|
||||
return
|
||||
</script>`
|
||||
return html
|
||||
}
|
||||
|
||||
// Helper function to render media (image/video/unknown)
|
||||
function renderMedia(obj) {
|
||||
function renderMedia(obj,gs,am_html) {
|
||||
const isImageOrUnknown = obj.type.name === "Image" || obj.type.name === "Unknown";
|
||||
const isVideo = obj.type.name === "Video";
|
||||
const path = `${obj.in_dir.in_path.path_prefix}/${obj.in_dir.rel_path}/${obj.name}`;
|
||||
const thumb = obj.file_details.thumbnail
|
||||
? `<a href="${path}"><img alt="${obj.name}" class="thumb" height="${OPT.size}" src="data:image/jpeg;base64,${obj.file_details.thumbnail}"></a>`
|
||||
? `<a href="${path}"><img alt="${obj.name}" ${gs} class="thumb" height="${OPT.size}" src="data:image/jpeg;base64,${obj.file_details.thumbnail}"></a>`
|
||||
: `<a href="${path}"><svg width="${OPT.size}" height="${OPT.size}" fill="white"><use xlink:href="/internal/icons.svg#unknown_ftype"/></svg></a>`;
|
||||
|
||||
let mediaHtml = `<div style="position:relative; width:100%">${thumb}`;
|
||||
let mediaHtml = `<div style="position:relative; width:100%">${thumb}${am_html}`;
|
||||
|
||||
if (isImageOrUnknown) {
|
||||
if (OPT.search_term) {
|
||||
mediaHtml += `
|
||||
<div style="position:absolute; bottom: 0px; left: 2px;">
|
||||
<svg width="16" height="16" fill="white"><use xlink:href="/internal/icons.svg#${getLocationIcon(obj)}"/></svg>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
mediaHtml += `
|
||||
<div id="s${obj.id}" style="display:none; position:absolute; top: 50%; left:50%; transform:translate(-50%, -50%);">
|
||||
<img height="64px" src="/internal/throbber.gif">
|
||||
</div>
|
||||
`;
|
||||
} else if (isVideo) {
|
||||
if (isVideo) {
|
||||
mediaHtml += `
|
||||
<div style="position:absolute; top: 0px; left: 2px;">
|
||||
<svg width="16" height="16" fill="white"><use xlink:href="/internal/icons.svg#film"/></svg>
|
||||
</div>
|
||||
`;
|
||||
if (OPT.search_term) {
|
||||
}
|
||||
if (OPT.search_term) {
|
||||
mediaHtml += `
|
||||
<div style="position:absolute; bottom: 0px; left: 2px;">
|
||||
<svg width="16" height="16" fill="white"><use xlink:href="/internal/icons.svg#${getLocationIcon(obj)}"/></svg>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
mediaHtml += `</div>`;
|
||||
@@ -527,7 +510,6 @@ function drawPageOfFigures()
|
||||
{
|
||||
$('#figures').empty()
|
||||
var last = { printed: null }
|
||||
var ecnt=0
|
||||
|
||||
// something is up, let the user know
|
||||
if( document.alert )
|
||||
@@ -557,30 +539,41 @@ function drawPageOfFigures()
|
||||
// with clas "back" this gets a different click handler which flags server to return data by 'going back/up' in dir tree
|
||||
// we give the server the id of the first item on the page so it can work out how to go back
|
||||
html=`<div class="col col-auto g-0 m-1">
|
||||
<figure id="${back_id}" ecnt="0" class="${cl} entry m-1" type="Directory">
|
||||
<figure id="${back_id}" class="${cl} entry m-1" type="Directory">
|
||||
<svg class="svg" width="${OPT.size-22}" height="${OPT.size-22}">
|
||||
<use xlink:href="internal/icons.svg#folder_back${gray}"/>
|
||||
</svg>
|
||||
<figcaption class="figure-caption text-center">${back}</figcaption>
|
||||
</figure>
|
||||
</div>`
|
||||
ecnt++
|
||||
$('#figures').append(html)
|
||||
}
|
||||
for (const obj of document.entries) {
|
||||
addFigure( obj, last, ecnt )
|
||||
ecnt++
|
||||
// Grouping logic
|
||||
if (OPT.grouping === "Day") {
|
||||
if (last.printed !== obj.file_details.day) {
|
||||
$('#figures').append(`<div class="row ps-3"><h6>Day: ${obj.file_details.day} of ${obj.file_details.month}/${obj.file_details.year}</h6></div>` );
|
||||
last.printed = obj.file_details.day;
|
||||
}
|
||||
} else if (OPT.grouping === "Week") {
|
||||
if (last.printed !== obj.file_details.woy) {
|
||||
$('#figures').append(`<div class="row ps-3"><h6>Week #: ${obj.file_details.woy} of ${obj.file_details.year}</h6></div>` );
|
||||
last.printed = obj.file_details.woy;
|
||||
}
|
||||
} else if (OPT.grouping === "Month") {
|
||||
if (last.printed !== obj.file_details.month) {
|
||||
$('#figures').append(`<div class="row ps-3"><h6>Month: ${obj.file_details.month} of ${obj.file_details.year}</h6></div>` );
|
||||
last.printed = obj.file_details.month;
|
||||
}
|
||||
}
|
||||
addFigure( obj )
|
||||
}
|
||||
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
||||
if( document.entries.length == 0 )
|
||||
if( OPT.search_term )
|
||||
$('#figures').append( `<span class="alert alert-danger p-2 col-auto"> No matches for: '${OPT.search_term}'</span>` )
|
||||
else if( OPT.root_eid == 0 )
|
||||
$('#figures').append( `<span class="alert alert-danger p-2 col-auto d-flex align-items-center">No files in Path!</span>` )
|
||||
$('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
||||
$('.figure').dblclick( function(e) { startViewing( $(this).attr('id') ) } )
|
||||
// for dir, getDirEntries 2nd param is back (or "up" a dir)
|
||||
$(".dir").click( function(e) { document.back_id=this.id; getDirEntries(this.id,false) } )
|
||||
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
||||
}
|
||||
|
||||
// emtpy out file_list_div, and repopulate it with new page of content
|
||||
@@ -669,7 +662,14 @@ function getPage(pageNumber, successCallback, viewingIdx=0)
|
||||
type: 'POST', url: '/get_entries_by_ids',
|
||||
data: JSON.stringify(data), contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(res) { document.amendments=res.amend; getEntriesByIdSuccessHandler( res.entries, pageNumber, successCallback, viewingIdx ) },
|
||||
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)
|
||||
}
|
||||
getEntriesByIdSuccessHandler( res.entries, pageNumber, successCallback, viewingIdx )
|
||||
},
|
||||
error: function(xhr, status, error) { console.error("Error:", error); } });
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
// This function will remove the matching amendment for this entry (id)
|
||||
// can only have 1 ammendment per image, its grayed out for other changes
|
||||
function removeAmendment( 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( data.finished )
|
||||
{
|
||||
$('#s'+id).hide()
|
||||
$('#'+id).find('img.thumb').attr('style', 'filter: color(100%);' );
|
||||
$('#'+id).addClass('entry')
|
||||
$('#'+id).find('.thumb').attr('src', 'data:image/jpeg;base64,'+data.thumbnail)
|
||||
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
|
||||
@@ -15,17 +35,15 @@ function handleTransformFiles(data,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 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
|
||||
// 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
|
||||
grayscale=0
|
||||
throbber=0
|
||||
im.src=im.src + '?t=' + new Date().getTime();
|
||||
removeAmendment( id )
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -41,7 +59,18 @@ function handleTransformViewing(data,id,job_id)
|
||||
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); } } )
|
||||
$.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)
|
||||
}
|
||||
|
||||
// for each highlighted image, POST the transform with amt (90, 180, 270,
|
||||
@@ -55,16 +84,28 @@ function Transform(amt)
|
||||
if( document.viewing )
|
||||
{
|
||||
post_data = '&amt='+amt+'&id='+document.viewing.id
|
||||
// send /transform for this image, grayscale the thumbmail, add color spinning wheel overlay, and start checking for job end
|
||||
$.ajax({ type: 'POST', data: post_data, url: '/transform', success: function(data) { grayscale=1; throbber=1; DrawImg(); CheckTransformJob(document.viewing.id,data.job_id,handleTransformViewing); return false; } })
|
||||
// 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;
|
||||
} })
|
||||
}
|
||||
else
|
||||
{
|
||||
$('.highlight').each(function( id, e ) {
|
||||
$('.highlight').each(function( cnt, e ) {
|
||||
post_data = '&amt='+amt+'&id='+e.id
|
||||
// send /transform for this image, grayscale the thumbmail, add color spinning wheel overlay, and start checking for job end
|
||||
$.ajax({ type: 'POST', data: post_data, url: '/transform', success: function(data){ $('#'+e.id).find('img.thumb').attr('style', 'filter: grayscale(100%);' ); $('#'+e.id).removeClass('entry'); $('#s'+e.id).show(); CheckTransformJob(e.id,data.job_id,handleTransformFiles); return false; } })
|
||||
// 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;
|
||||
} })
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
job.py
20
job.py
@@ -9,6 +9,7 @@ from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import socket
|
||||
from shared import PA, PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, NEWEST_LOG_LIMIT, OLDEST_LOG_LIMIT
|
||||
from amend import EntryAmendment, inAmendmentTypes
|
||||
from flask_login import login_required, current_user
|
||||
from sqlalchemy.dialects.postgresql import INTERVAL
|
||||
from sqlalchemy.sql.functions import concat
|
||||
@@ -114,8 +115,25 @@ def NewJob(name, num_files="0", wait_for=None, jex=None, desc="No description pr
|
||||
|
||||
db.session.add(job)
|
||||
db.session.commit()
|
||||
SetFELog( message=f'Created <a class="link-light" href="/job/{job.id}">Job #{job.id}</a> to {desc}', level="success" )
|
||||
|
||||
# if this job changes an eid we store that in DB and client shows until it finishes the job
|
||||
at_id = inAmendmentTypes(job)
|
||||
if at_id:
|
||||
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':
|
||||
for j in jex:
|
||||
if 'eid-' in j.name:
|
||||
ea=EntryAmendment( eid=j.value, 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
|
||||
|
||||
SetFELog( message=f'Created <a class="link-light" href="/job/{job.id}">Job #{job.id}</a> to {desc}', level="success" )
|
||||
WakePAJobManager(job.id)
|
||||
return job
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#
|
||||
# This file controls the 'external' job control manager, that (periodically #
|
||||
# looks / somehow is pushed an event?) picks up new jobs, and processes them.
|
||||
@@ -15,7 +14,7 @@
|
||||
|
||||
### SQLALCHEMY IMPORTS ###
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import Column, Integer, String, Sequence, Float, ForeignKey, DateTime, LargeBinary, Boolean, func, text
|
||||
from sqlalchemy import Column, Integer, String, Sequence, Float, ForeignKey, DateTime, LargeBinary, Boolean, func, text, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import create_engine
|
||||
@@ -23,7 +22,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm import scoped_session
|
||||
|
||||
### LOCAL FILE IMPORTS ###
|
||||
from shared import DB_URL, PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, THUMBSIZE, SymlinkName, GenThumb, SECS_IN_A_DAY, PA_EXIF_ROTATER
|
||||
from shared import DB_URL, PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, THUMBSIZE, SymlinkName, GenThumb, SECS_IN_A_DAY, PA_EXIF_ROTATER, PA
|
||||
from datetime import datetime, timedelta, date
|
||||
|
||||
### PYTHON LIB IMPORTS ###
|
||||
@@ -46,6 +45,8 @@ import re
|
||||
import sys
|
||||
import ffmpeg
|
||||
import subprocess
|
||||
# FIXME: remove this
|
||||
import time
|
||||
|
||||
|
||||
# global debug setting
|
||||
@@ -512,6 +513,15 @@ class PA_JobManager_FE_Message(Base):
|
||||
def __repr__(self):
|
||||
return "<id: {}, job_id: {}, level: {}, message: {}".format(self.id, self.job_id, self.level, self.message)
|
||||
|
||||
################################################################################
|
||||
# Class describing which Entry has a pending Amendment in the DB (via sqlalchemy)
|
||||
################################################################################
|
||||
class EntryAmendment(PA,Base):
|
||||
__tablename__ = "entry_amendment"
|
||||
eid = Column(Integer, ForeignKey("entry.id"), primary_key=True )
|
||||
job_id = Column(Integer, ForeignKey("job.id"), primary_key=True )
|
||||
# don't over think this, we just use eid to delete this entry anyway
|
||||
amend_type = Column(Integer)
|
||||
|
||||
##############################################################################
|
||||
# PAprint(): convenience function to prepend a timestamp to a printed string
|
||||
@@ -876,6 +886,7 @@ 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)
|
||||
@@ -1865,7 +1876,6 @@ def JobRunAIOn(job):
|
||||
|
||||
####################################################################################################################################
|
||||
# JobTransformImage(): transform an image by the amount requested (can also flip horizontal or vertical)
|
||||
# TODO: should be JobTransformImage() ;)
|
||||
####################################################################################################################################
|
||||
def JobTransformImage(job):
|
||||
JobProgressState( job, "In Progress" )
|
||||
@@ -1897,6 +1907,15 @@ 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} )" )
|
||||
|
||||
FinishJob(job, "Finished Processesing image rotation/flip")
|
||||
return
|
||||
|
||||
@@ -2707,7 +2726,6 @@ def ScheduledJobs():
|
||||
created_jobs=True
|
||||
return created_jobs
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
# MAIN - start with validation, then grab any jobs in the DB to process, then
|
||||
# go into waiting on a socket to be woken up (and then if woken, back into HandleJobs()
|
||||
|
||||
@@ -179,9 +179,10 @@ INSERT INTO amendment_type ( id, job_name, which, what, colour ) VALUES ( 6, 'tr
|
||||
INSERT INTO amendment_type ( id, job_name, which, what, colour ) VALUES ( 7, 'transform_image:flipv', 'icon', 'flip_v', '#009EFF' );
|
||||
INSERT INTO amendment_type ( id, job_name, which, what, colour ) VALUES ( 8, 'move_files', 'icon', 'folder_plus', 'var(--bs-primary)' );
|
||||
|
||||
CREATE TABLE entry_amendment ( eid INTEGER, amend_type INTEGER,
|
||||
CONSTRAINT pk_entry_amendment_eid_name PRIMARY KEY(eid,amend_type),
|
||||
CONSTRAINT fk_entry_amendment_amendment_type FOREIGN KEY(amend_type) REFERENCES amendment_type(id) );
|
||||
CREATE TABLE entry_amendment ( amend_type INTEGER, eid INTEGER, job_id INTEGER,
|
||||
CONSTRAINT pk_entry_amendment_eid_job_id PRIMARY KEY(eid,job_id),
|
||||
CONSTRAINT fk_entry_amendment_amendment_type FOREIGN KEY(amend_type) REFERENCES amendment_type(id),
|
||||
CONSTRAINT fk_entry_amendment_job_id FOREIGN KEY(job_id) REFERENCES job(id) );
|
||||
|
||||
-- default data for types of paths
|
||||
INSERT INTO path_type VALUES ( (SELECT NEXTVAL('path_type_id_seq')), 'Import' );
|
||||
|
||||
@@ -137,9 +137,15 @@
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}?v={{js_vers['ic']}}#prev"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<figure class="col col-auto border border-info rounded m-0 p-1" id="figure">
|
||||
<figure style="position: relative;" class="col col-auto border border-info rounded m-0 p-1" id="figure">
|
||||
<canvas id="canvas"></canvas>
|
||||
<!-- next 4 are placeholders and called on during amendments only in viewer code -->
|
||||
<img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}?v={{js_vers[th]}}" style="display:none;">
|
||||
<img id="white-circle" src="{{url_for('internal', filename='white-circle.png')}}?v={{js_vers[th]}}" style="display:none;">
|
||||
<img id="inside-img" style="display:none;">
|
||||
<svg id="inside-icon" style="display:none;" fill="currentColor">
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}?v={{js_vers['ic']}}#flip_v">
|
||||
</use></svg>
|
||||
<script>
|
||||
var im=new Image();
|
||||
im.onload=DrawImg
|
||||
|
||||
Reference in New Issue
Block a user