From 7f086b289bea1d078f4f074ad7ba37cd2e5d968d Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Fri, 27 Nov 2020 01:25:31 +0300 Subject: [PATCH] new task manager working... Signed-off-by: Alex A. Naanou --- Viewer/features/core.js | 181 +-------------------------------------- Viewer/features/sharp.js | 160 +++++++++++++++++++++------------- Viewer/package-lock.json | 8 +- Viewer/package.json | 2 +- 4 files changed, 107 insertions(+), 244 deletions(-) diff --git a/Viewer/features/core.js b/Viewer/features/core.js index 814de054..3ae84b38 100755 --- a/Viewer/features/core.js +++ b/Viewer/features/core.js @@ -2358,89 +2358,6 @@ function(func){ return func } -// -// action: ['Path/To/Action', -// abortablePromise('abort-id', function(abort, ...args){ -// -// abort.cleanup(function(reason, res){ -// if(reason == 'done'){ -// // ... -// } -// if(reason == 'aborted'){ -// // ... -// } -// }) -// -// return new Promise(function(resolve, reject){ -// // ... -// -// if(abort.isAborted){ -// // handle abort... -// } -// -// // ... -// }) })], -// -// -// NOTE: if the returned promise is not resolved .cleanup(..) will not -// be called even if the appropriate .abort(..) as called... -// -// XXX is the abort api an overkill?? -// ...can this be solved/integrated with tasks??? -// essentially this creates an abortable task, for full blown tasks -// it would be nice to also be able to: -// - pause/resume (abort is done) -// - serialize/restore -// - list/sort/prioritize -// - remote (peer/worker) -// XXX docs... -// XXX LEGACY... -var abortablePromise = -module.abortablePromise = -function(title, func){ - return Object.assign( - Task(function(...args){ - var that = this - - var abort = object.mixinFlat( - this.abortable(title, function(){ - that.clearAbortable(title, abort) - return abort }), - { - get isAborted(){ - return !((that.__abortable || new Map()) - .get(title) || new Set()) - .has(this) }, - - __cleanup: null, - cleanup: function(func){ - var args = [...arguments] - var reason = this.isAborted ? - 'aborted' - : 'done' - typeof(func) == 'function' ? - // register handler... - (this.__cleanup = this.__cleanup - || new Set()).add(func) - // call cleanup handlers... - : [...(this.__cleanup || [])] - .forEach(function(f){ - f.call(that, reason, ...args) }) - return this }, - }) - - return func.call(this, abort, ...args) - .then(function(res){ - abort.cleanup(res)() - return res }) - .catch(function(res){ - abort.cleanup(res)() }) }), - { - toString: function(){ - return `core.abortablePromise('${ title }', \n${ func.toString() })` }, - }) } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Task action action helpers... // @@ -2473,6 +2390,7 @@ function(title, func){ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// XXX add a task manager UI... var TaskActions = actions.Actions({ config: { }, @@ -2504,103 +2422,6 @@ var TaskActions = actions.Actions({ // session tasks are stopped when the index is cleared... get sessionTasks(){ return this.tasks.titled(...this.sessionTaskActions) }, - - - - // XXX LEGACY -- remove after migrating sharp.js and abortablePromise(..) - // - // Abortable... - // - // Format: - // Map({ - // title: Set([ func, ... ]), - // ... - // }) - // - // XXX rename... - // XXX extend to support other task operations... - __abortable: null, - - abortable: ['- System/Register abort handler', - doc`Register abortable action - - .abortable(title, func) - -> func - - `, - function(title, callback){ - // reserved titles... - if(title == 'all' || title == '*'){ - throw new Error('.abortable(..): can not set reserved title: "'+ title +'".') } - - var abortable = this.__abortable = this.__abortable || new Map() - var set = abortable.get(title) || new Set() - abortable.set(title, set) - set.add(callback) - - return actions.ASIS(callback) }], - clearAbortable: ['- System/Clear abort handler(s)', - doc`Clear abort handler(s) - - Clear abort handler... - .clearAbortable(title, callback) - - Clear all abort handlers for title... - .clearAbortable(title) - .clearAbortable(title, 'all') - - Clear all abort handlers... - .clearAbortable('all') - - `, - function(title, callback){ - callback = callback || '*' - - // clear all... - if(title == '*' || title == 'all'){ - delete this.__abortable } - - var set = ((this.__abortable || new Map()).get(title) || new Set()) - // clear specific handler... - callback != '*' - && callback != 'all' - && set.delete(callback) - // cleanup / clear title... - ;(set.size == 0 - || callback == '*' - || callback == 'all') - && (this.__abortable || new Set()).delete(title) - // cleanup... - this.__abortable - && this.__abortable.size == 0 - && (delete this.__abortable) }], - abort: ['- System/Abort task(s)', - doc` - - .abort(title) - .abort([title, .. ]) - - .abort('all') - - `, - function(title, task='all'){ - title = title == '*' || title == 'all' ? - [...(this.__abortable || new Map()).keys()] - : title instanceof Array ? - title - : [title] - - this.__abortable - && title - .forEach(function(title){ - [...(this.__abortable || new Map()).get(title) || []] - .forEach(function(f){ f() }) - this.__abortable - && this.__abortable.delete(title) }.bind(this)) - // cleanup... - this.__abortable - && this.__abortable.size == 0 - && (delete this.__abortable) }], }) diff --git a/Viewer/features/sharp.js b/Viewer/features/sharp.js index 9b9b0105..3ae08093 100755 --- a/Viewer/features/sharp.js +++ b/Viewer/features/sharp.js @@ -7,8 +7,6 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ -var array = require('lib/types/Array') - var actions = require('lib/actions') var features = require('lib/features') @@ -248,21 +246,36 @@ var SharpActions = actions.Actions({ NOTE: all options are optional. NOTE: this will not overwrite existing images. `, - core.abortablePromise('makeResizedImage', function(abort, images, size, path, options={}){ + core.taskAction('makeResizedImage', function(ticket, images, size, path, options={}){ var that = this // sanity check... - if(arguments.length < 3){ + if(arguments.length < 4){ + ticket.reject() throw new Error('.makeResizedImage(..): ' +'need at least images, size and path.') } - var CHUNK_SIZE = 4 + // setup runtime interactions... + // + // NOTE: we will resolve the ticket when we are fully done + // and not on stop... + var STOP = false + ticket + .onmessage('stop', function(){ + STOP = true }) + .then(function(){ + // close progress bar... + // NOTE: if we have multiple tasks let the last one + // close the progress bar... + if(that.tasks.titled(ticket.title).length == 0){ + logger + && logger.emit('close') } + // cleanup... + delete that.__cache_metadata_reading }) + var abort = function(){ + that.tasks.stop(ticket.title) } - abort.cleanup(function(reason, res){ - logger - && logger.emit('done') - && reason == 'aborted' - && logger.emit(res) }) + var CHUNK_SIZE = 4 // get/normalize images... //images = images || this.current @@ -316,7 +329,8 @@ var SharpActions = actions.Actions({ logger = logger !== false ? (logger || this.logger) : false - logger = logger && logger.push('Resize', {onclose: abort}) + logger = logger + && logger.push('Resize', {onclose: abort}) // backup... // XXX make backup name pattern configurable... @@ -328,8 +342,8 @@ var SharpActions = actions.Actions({ return images .mapChunks(CHUNK_SIZE, function(gid){ - if(abort.isAborted){ - throw array.STOP('aborted') } + if(STOP){ + throw Array.STOP('aborted') } // skip non-images... if(!['image', null, undefined] @@ -413,13 +427,13 @@ var SharpActions = actions.Actions({ && logger.emit('done', to) return img }) }) }) }) .then(function(res){ + ticket.resolve(res) return res == 'aborted' ? Promise.reject('aborted') : res }) })], // XXX this does not update image.base_path -- is this correct??? // XXX add support for offloading the processing to a thread/worker... - // XXX should we use task.Queue()??? makePreviews: ['Sharp|File/Make image $previews', core.doc`Make image previews @@ -447,27 +461,43 @@ var SharpActions = actions.Actions({ NOTE: if base_path is given .images will not be updated with new preview paths... `, - core.abortablePromise('makePreviews', function(abort, images, sizes, base_path, logger){ + core.taskAction('makePreviews', function(ticket, images, sizes, base_path, logger){ var that = this - var CHUNK_SIZE = 4 + // setup runtime interactions... + // + // NOTE: we will resolve the ticket when we are fully done + // and not on stop... + var STOP = false + ticket + .onmessage('stop', function(){ + STOP = true }) + .then(function(){ + // close progress bar... + // NOTE: if we have multiple tasks let the last one + // close the progress bar... + if(that.tasks.titled(ticket.title).length == 0){ + gid_logger + && gid_logger.emit('close') + logger + && logger.emit('close') } + // cleanup... + delete that.__cache_metadata_reading }) + var abort = function(){ + that.tasks.stop(ticket.title) } - abort.cleanup(function(reason, res){ - gid_logger - && gid_logger.emit('done') - && reason == 'aborted' - && gid_logger.emit(res) - logger - && logger.emit('done') - && reason == 'aborted' - && logger.emit(res) }) + var CHUNK_SIZE = 4 var logger_mode = this.config['preview-progress-mode'] || 'gids' logger = logger !== false ? (logger || this.logger) : false - var gid_logger = logger && logger.push('Images', {onclose: abort}) - logger = logger && logger.push('Previews', {onclose: abort}) + var gid_logger = logger + && logger.push('Images', + {onclose: abort}) + logger = logger + && logger.push('Previews', + {onclose: abort}) // get/normalize images... images = images @@ -506,8 +536,8 @@ var SharpActions = actions.Actions({ return images .mapChunks(CHUNK_SIZE, function(gid){ - if(abort.isAborted){ - throw array.STOP('aborted') } + if(STOP){ + throw Array.STOP('aborted') } var img = that.images[gid] var base = base_path @@ -516,8 +546,8 @@ var SharpActions = actions.Actions({ return sizes .map(function(size, i){ - if(abort.isAborted){ - throw array.STOP('aborted') } + if(STOP){ + throw Array.STOP('aborted') } var name = path = path_tpl .replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size)) @@ -548,12 +578,12 @@ var SharpActions = actions.Actions({ return [gid, size, name] }) }) }) .then(function(res){ + ticket.resolve(res) return res == 'aborted' ? Promise.reject('aborted') : res.flat() }) })], // XXX add support for offloading the processing to a thread/worker... - // XXX should we use task.Queue()??? __cache_metadata_reading: null, cacheMetadata: ['- Sharp|Image/', core.doc`Cache metadata @@ -602,21 +632,38 @@ var SharpActions = actions.Actions({ NOTE: this will effectively update metadata format to the new spec... NOTE: for info on full metadata format see: .readMetadata(..) `, - core.abortablePromise('cacheMetadata', function(abort, images, logger){ + core.sessionTaskAction('cacheMetadata', function(ticket, images, logger){ var that = this - var CHUNK_SIZE = 4 + // setup runtime interactions... + // + // NOTE: we will resolve the ticket when we are fully done + // and not on stop... + var STOP = false + ticket + .onmessage('stop', function(){ + STOP = true }) + .then(function(){ + // close progress bar... + // NOTE: if we have multiple tasks let the last one + // close the progress bar... + if(that.tasks.titled(ticket.title).length == 0){ + logger + && logger.emit('close') } + that.off('clear', on_close) + // cleanup... + delete that.__cache_metadata_reading }) + // clear the progress bar for the next session... + var on_close + this.one('clear', on_close = function(){ + logger && logger.emit('close') }) - // XXX this seems to be called prematurely... - abort.cleanup(function(reason, res){ - console.log('### ABORT:\n ', - logger && logger.log.length, - reason, res) - logger - && logger.emit('close') - && reason == 'aborted' - && logger.emit(res) - delete that.__cache_metadata_reading }) + // universal task abort... + // NOTE: this will abort all the tasks of this type... + var abort = function(){ + that.tasks.stop(ticket.title) } + + var CHUNK_SIZE = 4 // handle logging and processing list... // NOTE: these will maintain .__cache_metadata_reading helping @@ -688,8 +735,8 @@ var SharpActions = actions.Actions({ return images .mapChunks(CHUNK_SIZE, function(gid){ // abort... - if(abort.isAborted){ - throw array.STOP('aborted') } + if(STOP){ + throw Array.STOP('aborted') } var img = cached_images[gid] var path = img && that.getImagePath(gid) @@ -770,9 +817,12 @@ var SharpActions = actions.Actions({ return done(gid) }) }) .then(function(res){ + ticket.resolve(res) + // XXX do we need this??? return res == 'aborted' ? Promise.reject('aborted') - : res }) })], + : res + }) })], cacheAllMetadata: ['- Sharp|Image/', core.doc`Cache all metadata NOTE: this is a shorthand to .cacheMetadata('all', ..)`, @@ -781,12 +831,13 @@ var SharpActions = actions.Actions({ // shorthands... // XXX do we need these??? + // ...better have a task manager UI... abortMakeResizedImage: ['- Sharp/', - 'abort: "makeResizedImage"'], + 'tasks.stop: "makeResizedImage"'], abortMakePreviews: ['- Sharp/', - 'abort: "makePreviews"'], + 'tasks.stop: "makePreviews"'], abortCacheMetadata: ['- Sharp/', - 'abort: "cacheMetadata"'], + 'tasks.stop: "cacheMetadata"'], }) @@ -806,15 +857,6 @@ module.Sharp = core.ImageGridFeatures.Feature({ isApplicable: function(){ return !!sharp }, handlers: [ - // XXX - ['load.pre', - function(){ - this.abort([ - 'makeResizedImage', - 'makePreviews', - 'cacheMetadata', - ]) }], - //* XXX this needs to be run in the background... // XXX this is best done in a thread + needs to be abortable (on .load(..))... [['loadImages', diff --git a/Viewer/package-lock.json b/Viewer/package-lock.json index fdfeb074..7795db28 100755 --- a/Viewer/package-lock.json +++ b/Viewer/package-lock.json @@ -1,6 +1,6 @@ { "name": "ImageGrid.Viewer.g4", - "version": "4.0.0-a", + "version": "4.0.0a", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1110,9 +1110,9 @@ "integrity": "sha512-9kZM80Js9/eTwXN9VXwLDC1wDJ7gIAdYU9GIzb5KJmNcLAMaW+zhgFrwFFMrcSfggUuadgnqSrS41E4XLe8JZw==" }, "ig-types": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/ig-types/-/ig-types-5.0.14.tgz", - "integrity": "sha512-j4oyqZP+xasNYMyWllcZz5kT8vscB58P6smehoBEHxNRPlKMqfZTP4MlxQVZpyAgPH8HCCwGjEYZ7c63FokWFA==", + "version": "5.0.17", + "resolved": "https://registry.npmjs.org/ig-types/-/ig-types-5.0.17.tgz", + "integrity": "sha512-A4qzL3t+usOnPx7tu+ieUDQIDUBEocouv7O6aBbLTcleDM5VNDOrdswfThNk3PSoP1bTSLmQV9LWsBRG1qVXiA==", "requires": { "ig-object": "^5.4.12", "object-run": "^1.0.1" diff --git a/Viewer/package.json b/Viewer/package.json index a66e05eb..50cdb31a 100755 --- a/Viewer/package.json +++ b/Viewer/package.json @@ -32,7 +32,7 @@ "ig-argv": "^2.15.0", "ig-features": "^3.4.2", "ig-object": "^5.4.12", - "ig-types": "^5.0.14", + "ig-types": "^5.0.17", "moment": "^2.29.1", "object-run": "^1.0.1", "requirejs": "^2.3.6",