resumable
Version:
Let a task queue be resumable.
199 lines (168 loc) • 4.12 kB
JavaScript
/**
* Task Queue Management.
*/
var util = require('util');
function extend(target) {
// Using `extend` from https://github.com/Raynos/xtend
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i]
, keys = Object.keys(source);
for (var j = 0; j < keys.length; j++) {
var name = keys[j];
target[name] = source[name];
}
}
return target
}
function Resumable(options){
this.queue = [];
this.mod = options.mod;
this.key = options.key;
this.store = options.storage;
this.options = options;
this._stringify = options.stringify;
if (options.ensure) {
this.ensure = options.ensure;
}
if (options.autoLoad) {
// load exsiting queue
this.load();
}
}
util.inherits(Resumable, require('events').EventEmitter);
Resumable.prototype.load = function() {
var self = this;
self.store.get(self.key, function(err, r) {
if (err) {
err = new Error(err);
return self.emit('error', err);
}
self.queue = self.parse(r);
self.emit('ready', self.queue);
});
};
Resumable.prototype.ensure = function(queue) {
return queue.filter(function(arg) {
if (!arg[0] || !arg[1]) return false;
return true;
});
};
Resumable.prototype.parse = function(r) {
if (!r) return this.queue;
try {
var ret = JSON.parse(r);
return this.ensure(ret);
} catch (e) {
//this.emit('error', e);
return [];
}
};
Resumable.prototype.stringify = function(r) {
r = r || [];
var mod = this.mod;
r = r.filter(function(item) {
// first argument is not a module method
if (!(item[0] in mod)) return false;
return true;
});
return JSON.stringify(r, this._stringify);
};
/**
* Reset or start a timer to dump
*/
Resumable.prototype._timer = function() {
var self = this;
if (self._t) {
clearTimeout(self._t);
}
self._t = setTimeout(function() {
self.dump();
}, 500);
};
Resumable.prototype.push = function() {
this._changed = true;
this.queue.push([].slice.apply(arguments));
this._timer();
};
Resumable.prototype.shift = function(fn_name) {
this._changed = true;
var args = arguments;
//console.log('========');
//console.log(this.queue);
//console.log(args);
//console.log('======');
this.queue = this.queue.filter(function(item) {
if (!Array.isArray(item)) return false;
item = item.slice();
var max_n = Math.max(args.length, item.length);
var i = 0;
while (i < max_n) {
if (item[i] != args[i]) {
return true;
}
i++;
}
return false;
});
this._timer();
};
Resumable.prototype.resume = function() {
var queue = this.queue;
var mod = this.mod;
var arg, fn;
function doit(arg) {
fn = mod[arg[0]];
setImmediate(function() {
fn && fn.apply(mod, arg.slice(1));
});
}
//console.log(queue);
while(queue.length) {
arg = queue.shift();
doit(arg);
}
};
/**
* To make arguments safe for resume task
*/
Resumable.prototype.safely = function(fn_name, arg) {
if (!this.mod[fn_name]) throw new Error('invalid function name');
var self = this;
var queue_arg = extend({}, arg);
//for (var k in queue_arg) {
//if (queue_arg[k]) {
//var uid = queue_arg[k].uid || queue_arg[k].id;
//if (uid) queue_arg[k] = uid;
//}
//}
// clean vars
queue_arg.error = queue_arg.success = null;
var cb1 = arg.success;
var cb2 = arg.error;
// decorate callbacks
arg.success = function() {
self.shift(fn_name, queue_arg);
cb1 && cb1.apply(this, arguments);
};
arg.error = function() {
self.shift(fn_name, queue_arg);
cb2 && cb2.apply(this, arguments);
};
// special identifier for recovery
queue_arg._from_halt = true;
self.push(fn_name, queue_arg);
return arg;
};
/**
* Dump the whole queue to storage
*/
Resumable.prototype.dump = function() {
var self = this;
if (!self._changed) return;
self.store.set(self.key, self.stringify(self.queue), function(err, r) {
if (err) return self.emit('error', err);
self._changed = false;
self.emit('dumped');
});
};
module.exports = Resumable;