new task manager working...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-11-27 01:25:31 +03:00
parent d59b62c39f
commit 7f086b289b
4 changed files with 107 additions and 244 deletions

View File

@ -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) }],
})

View File

@ -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
abort.cleanup(function(reason, res){
// 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('done')
&& reason == 'aborted'
&& logger.emit(res) })
&& logger.emit('close') }
// cleanup...
delete that.__cache_metadata_reading })
var abort = function(){
that.tasks.stop(ticket.title) }
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
abort.cleanup(function(reason, res){
// 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('done')
&& reason == 'aborted'
&& gid_logger.emit(res)
&& gid_logger.emit('close')
logger
&& logger.emit('done')
&& reason == 'aborted'
&& logger.emit(res) })
&& logger.emit('close') }
// cleanup...
delete that.__cache_metadata_reading })
var abort = function(){
that.tasks.stop(ticket.title) }
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
// XXX this seems to be called prematurely...
abort.cleanup(function(reason, res){
console.log('### ABORT:\n ',
logger && logger.log.length,
reason, res)
// 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')
&& reason == 'aborted'
&& logger.emit(res)
&& 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') })
// 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',

View File

@ -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"

View File

@ -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",