mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 18:30:09 +00:00
340 lines
7.9 KiB
JavaScript
340 lines
7.9 KiB
JavaScript
|
|
/**********************************************************************
|
||
|
|
*
|
||
|
|
*
|
||
|
|
*
|
||
|
|
**********************************************************************/
|
||
|
|
|
||
|
|
define(function(require){ var module = {}
|
||
|
|
console.log('>>> tasks')
|
||
|
|
|
||
|
|
//var DEBUG = DEBUG != null ? DEBUG : true
|
||
|
|
|
||
|
|
var actions = require('lib/actions')
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
/*
|
||
|
|
var Promise = require('promise')
|
||
|
|
|
||
|
|
function PTask(fn) {
|
||
|
|
if (!(this instanceof PTask)){
|
||
|
|
return new PTask(fn)
|
||
|
|
}
|
||
|
|
Promise.call(this, fn)
|
||
|
|
}
|
||
|
|
PTask.prototype = Object.create(Promise.prototype)
|
||
|
|
PTask.prototype.constructor = PTask
|
||
|
|
|
||
|
|
PTask.prototype.spread = function (cb) {
|
||
|
|
return this.then(function (arr) {
|
||
|
|
return cb.apply(this, arr);
|
||
|
|
})
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
// XXX experimental...
|
||
|
|
// Create a task...
|
||
|
|
//
|
||
|
|
// Task(<func>, <arg>[, ...])
|
||
|
|
// -> <task>
|
||
|
|
//
|
||
|
|
// A task is a deferred like object that runs a function returning a
|
||
|
|
// deferred and adds to it the ability to restart.
|
||
|
|
//
|
||
|
|
// Restarting is possible only of the original is rejected.
|
||
|
|
//
|
||
|
|
// Restarting will call the original function with the original set of
|
||
|
|
// arguments and reset the task.
|
||
|
|
//
|
||
|
|
// XXX this depends on that func(..) will return a deferred...
|
||
|
|
// XXX restarting will not transfer the handlers...
|
||
|
|
// ...this might be a deal breaker...
|
||
|
|
module.Task = function(func){
|
||
|
|
var args = [].slice.call(arguments)
|
||
|
|
|
||
|
|
// remove func from args...
|
||
|
|
args.splice(0, 1)
|
||
|
|
|
||
|
|
return ({
|
||
|
|
restart: function(){
|
||
|
|
// XXX jQuery compatible, need promise state check...
|
||
|
|
if(this.isRejected == null || this.isRejected()){
|
||
|
|
this.__proto__ = func.apply(null, args)
|
||
|
|
}
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
}).restart()
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
module.TaskPrototype = {
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
var Queue =
|
||
|
|
module.Queue = actions.Actions({
|
||
|
|
config: {
|
||
|
|
'running-pool-size': 8,
|
||
|
|
// XXX at this point these is ignored...
|
||
|
|
'retry-limit': 5,
|
||
|
|
'default-queue-mode': 'resumable',
|
||
|
|
},
|
||
|
|
|
||
|
|
// NOTE: these are sparse...
|
||
|
|
ready: null,
|
||
|
|
running: null,
|
||
|
|
done: null,
|
||
|
|
failed: null,
|
||
|
|
|
||
|
|
get length(){
|
||
|
|
var lists = ['ready', 'running', 'done', 'failed']
|
||
|
|
var sum = 0
|
||
|
|
while(lists.length > 0){
|
||
|
|
var l = this[lists.pop()]
|
||
|
|
sum += l == null ? 0 : l.len
|
||
|
|
}
|
||
|
|
return sum
|
||
|
|
},
|
||
|
|
set length(val){},
|
||
|
|
|
||
|
|
// can be:
|
||
|
|
// - running
|
||
|
|
// - ready
|
||
|
|
// - done
|
||
|
|
// - ...
|
||
|
|
// XXX should be more informative -- now supports only 'running' and 'stopped'
|
||
|
|
get state(){
|
||
|
|
return this._state || 'stopped'
|
||
|
|
},
|
||
|
|
|
||
|
|
// general events...
|
||
|
|
// NOTE: these are not intended to be called by the user...
|
||
|
|
taskQueued: ['', function(){}],
|
||
|
|
taskUnqueued: ['', function(){}],
|
||
|
|
taskStarted: ['', function(){}],
|
||
|
|
taskDelayed: ['', function(){}],
|
||
|
|
taskFailed: ['', function(){}],
|
||
|
|
taskDone: ['', function(){}],
|
||
|
|
|
||
|
|
// task manipulation actions...
|
||
|
|
// NOTE: these and task events are partly redundent....
|
||
|
|
enqueue: ['',
|
||
|
|
function(a, b, c){
|
||
|
|
// normalize arguments...
|
||
|
|
if(typeof(a) == typeof('str')){
|
||
|
|
var tag = a
|
||
|
|
var task = b
|
||
|
|
var mode = c
|
||
|
|
} else {
|
||
|
|
var tag = null
|
||
|
|
var task = a
|
||
|
|
var mode = b
|
||
|
|
}
|
||
|
|
mode = mode || this.config['default-queue-mode']
|
||
|
|
var ready = this.ready = this.ready || []
|
||
|
|
|
||
|
|
// XXX check if task is a task...
|
||
|
|
// XXX else, wrap in a task...
|
||
|
|
|
||
|
|
ready.push([tag, task, mode])
|
||
|
|
|
||
|
|
// restart in case the queue was depleted...
|
||
|
|
this._run()
|
||
|
|
|
||
|
|
this.taskQueued(tag, task, mode)
|
||
|
|
}],
|
||
|
|
unqueue: ['',
|
||
|
|
function(a, b){
|
||
|
|
var ready = this.ready
|
||
|
|
// empty queue...
|
||
|
|
if(ready == null || ready.length == 0){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// XXX prep args...
|
||
|
|
var tag = typeof(a) == typeof('str') ? a : b
|
||
|
|
var task = typeof(a) == typeof('str') ? b : a
|
||
|
|
|
||
|
|
// no args...
|
||
|
|
if(tag == null && task == null){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove matching tasks from the queue...
|
||
|
|
ready.forEach(function(e, i){
|
||
|
|
// only tag given...
|
||
|
|
if(task == null ? e[0] == tag
|
||
|
|
// only task given...
|
||
|
|
: tag == null ? e[1] === task
|
||
|
|
// both task and tag given...
|
||
|
|
: e[0] != tag && e[1] === task){
|
||
|
|
delete ready[i]
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// XXX
|
||
|
|
this.taskUnqueued(tag, task)
|
||
|
|
}],
|
||
|
|
delay: ['',
|
||
|
|
function(a, b){
|
||
|
|
var ready = this.ready
|
||
|
|
// empty queue...
|
||
|
|
if(ready == null || ready.length == 0){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// XXX prep args...
|
||
|
|
var tag = typeof(a) == typeof('str') ? a : b
|
||
|
|
var task = typeof(a) == typeof('str') ? b : a
|
||
|
|
|
||
|
|
// no args...
|
||
|
|
if(tag == null && task == null){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var delayed = []
|
||
|
|
// remove the matching tasks...
|
||
|
|
ready.forEach(function(e, i){
|
||
|
|
// only tag given...
|
||
|
|
var res = (task == null ? e[0] == tag
|
||
|
|
// only task given...
|
||
|
|
: tag == null ? e[1] === task
|
||
|
|
// both task and tag given...
|
||
|
|
: e[0] != tag && e[1] === task)
|
||
|
|
|
||
|
|
if(res){
|
||
|
|
delete ready[i]
|
||
|
|
|
||
|
|
delayed.push(e)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// push delayed list to the end of the queue...
|
||
|
|
delayed.forEach(function(e){
|
||
|
|
ready.push(e)
|
||
|
|
})
|
||
|
|
|
||
|
|
// restart in case the queue was depleted...
|
||
|
|
this._run()
|
||
|
|
|
||
|
|
// XXX
|
||
|
|
this.taskDelayed(tag, task)
|
||
|
|
}],
|
||
|
|
|
||
|
|
// Run the queue...
|
||
|
|
//
|
||
|
|
// This is not intended for direct use...
|
||
|
|
//
|
||
|
|
// This can run in one of two ways:
|
||
|
|
// 1) run until the .ready queue is completely depleted
|
||
|
|
// This can occur for very fast or sync tasks, essentially
|
||
|
|
// each iteration will replenish the .running pool until there
|
||
|
|
// are not task to run.
|
||
|
|
// 2) load the .running pool and exit
|
||
|
|
// The first task to finish will run this again to replenish
|
||
|
|
// the pool.
|
||
|
|
//
|
||
|
|
// NOTE: there can be no more that one instance running at a time.
|
||
|
|
// NOTE: if .state is not 'running' this will silently exit.
|
||
|
|
//
|
||
|
|
// XXX need to handle retries correctly, at this point all errors just
|
||
|
|
// drop to failed and retry counter is incremented, there is no
|
||
|
|
// flow back to .running
|
||
|
|
_run: ['',
|
||
|
|
function(){
|
||
|
|
if(this._running){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var that = this
|
||
|
|
var size = this.config['running-pool-size']
|
||
|
|
this.running = this.running || []
|
||
|
|
|
||
|
|
// NOTE: the function in the look here is to clock some
|
||
|
|
// values in a closure for reuse in promise state
|
||
|
|
// handlers...
|
||
|
|
while(this.ready && this.ready.len > 0
|
||
|
|
&& this.state == 'running'
|
||
|
|
&& (this.running && this.running.len || 0) < size){ (function(){
|
||
|
|
var elem = ready.shift()
|
||
|
|
var task = elem[0]
|
||
|
|
that._running = true
|
||
|
|
|
||
|
|
that.running.push(elem)
|
||
|
|
|
||
|
|
task()
|
||
|
|
// retry or move to failed...
|
||
|
|
.catch(function(){
|
||
|
|
// pop self of .running
|
||
|
|
delete that.running[that.running.indexOf(elem)]
|
||
|
|
|
||
|
|
// push self to .failed
|
||
|
|
var failed = that.failed = that.failed || []
|
||
|
|
|
||
|
|
// increment retry count...
|
||
|
|
elem[3] = (elem[3] || 0) + 1
|
||
|
|
|
||
|
|
// XXX check task mode and re-queue if needed...
|
||
|
|
// XXX
|
||
|
|
failed.push(elem)
|
||
|
|
})
|
||
|
|
// push to done and ._run some more...
|
||
|
|
.then(function(){
|
||
|
|
// pop self of .running
|
||
|
|
delete that.running[that.running.indexOf(elem)]
|
||
|
|
|
||
|
|
// push self to .done
|
||
|
|
var done = that.done = that.done || []
|
||
|
|
|
||
|
|
done.push(elem)
|
||
|
|
|
||
|
|
// run some more...
|
||
|
|
that._run()
|
||
|
|
})
|
||
|
|
})() }
|
||
|
|
|
||
|
|
that._running = false
|
||
|
|
}],
|
||
|
|
|
||
|
|
// state manipulation actions...
|
||
|
|
// NOTE: we do not need events for these as they are actions...
|
||
|
|
start: ['',
|
||
|
|
function(){
|
||
|
|
this._state = 'running'
|
||
|
|
this._run()
|
||
|
|
}],
|
||
|
|
stop: ['',
|
||
|
|
function(){
|
||
|
|
delete this._state
|
||
|
|
}],
|
||
|
|
clear: ['',
|
||
|
|
function(){
|
||
|
|
this.stop()
|
||
|
|
delete this.ready
|
||
|
|
delete this.running
|
||
|
|
delete this.failed
|
||
|
|
delete this.done
|
||
|
|
}],
|
||
|
|
})
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// XXX need to make the queue usable as an object...
|
||
|
|
// XXX
|
||
|
|
|
||
|
|
|
||
|
|
/**********************************************************************
|
||
|
|
* vim:set ts=4 sw=4 : */
|
||
|
|
return module })
|