UNPKG

resumable

Version:

Let a task queue be resumable.

199 lines (168 loc) 4.12 kB
/** * 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;