Commit baedd341 authored by Tristan Cavelier's avatar Tristan Cavelier

JIO source code replaced by new one

parent d9db77a0
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localstorage: true, setInterval: true, clearInterval: true */
var activityUpdater = (function (spec, my) {
var that = {}, priv = {};
spec = spec || {};
my = my || {};
priv.id = spec.id || 0;
priv.interval = 400;
priv.interval_id = null;
// Methods //
/**
* Update the last activity date in the localStorage.
* @method touch
*/
priv.touch = function () {
localstorage.setItem('jio/id/' + priv.id, Date.now());
};
/**
* Sets the jio id into the activity.
* @method setId
* @param {number} id The jio id.
*/
that.setId = function (id) {
priv.id = id;
};
/**
* Sets the interval delay between two updates.
* @method setIntervalDelay
* @param {number} ms In milliseconds
*/
that.setIntervalDelay = function (ms) {
priv.interval = ms;
};
/**
* Gets the interval delay.
* @method getIntervalDelay
* @return {number} The interval delay.
*/
that.getIntervalDelay = function () {
return priv.interval;
};
/**
* Starts the activity updater. It will update regulary the last activity
* date in the localStorage to show to other jio instance that this instance
* is active.
* @method start
*/
that.start = function () {
if (!priv.interval_id) {
priv.touch();
priv.interval_id = setInterval(function () {
priv.touch();
}, priv.interval);
}
};
/**
* Stops the activity updater.
* @method stop
*/
that.stop = function () {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
}
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global announcement: true */
var announcement = function (spec, my) {
var that = {},
callback_a = [],
announcer = spec.announcer || {};
spec = spec || {};
my = my || {};
// Methods //
that.add = function (callback) {
callback_a.push(callback);
};
that.remove = function (callback) {
var i, tmp_callback_a = [];
for (i = 0; i < callback_a.length; i += 1) {
if (callback_a[i] !== callback) {
tmp_callback_a.push(callback_a[i]);
}
}
callback_a = tmp_callback_a;
};
that.register = function () {
announcer.register(that);
};
that.unregister = function () {
announcer.unregister(that);
};
that.trigger = function (args) {
var i;
for (i = 0; i < callback_a.length; i += 1) {
callback_a[i].apply(null, args);
}
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global announcement: true */
var announcer = (function (spec, my) {
var that = {},
announcement_o = {};
spec = spec || {};
my = my || {};
// Methods //
that.register = function (name) {
if (!announcement_o[name]) {
announcement_o[name] = announcement();
}
};
that.unregister = function (name) {
if (announcement_o[name]) {
delete announcement_o[name];
}
};
that.at = function (name) {
return announcement_o[name];
};
that.on = function (name, callback) {
that.register(name);
that.at(name).add(callback);
};
that.trigger = function (name, args) {
that.at(name).trigger(args);
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var allDocsCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'allDocs';
};
that.executeOn = function (storage) {
storage.allDocs(that);
};
that.canBeRestored = function () {
return false;
};
that.validateState = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var checkCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'check';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.check(that);
};
that.canBeRestored = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global postCommand: true, putCommand: true, getCommand: true,
removeCommand: true, allDocsCommand: true,
getAttachmentCommand: true, removeAttachmentCommand: true,
putAttachmentCommand: true, failStatus: true, doneStatus: true,
checkCommand: true, repairCommand: true,
hex_md5: true */
var command = function (spec, my) {
var that = {},
priv = {};
spec = spec || {};
my = my || {};
priv.commandlist = {
'post': postCommand,
'put': putCommand,
'get': getCommand,
'remove': removeCommand,
'allDocs': allDocsCommand,
'getAttachment': getAttachmentCommand,
'putAttachment': putAttachmentCommand,
'removeAttachment': removeAttachmentCommand,
'check': checkCommand,
'repair': repairCommand
};
// creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) {
priv.label = spec.label;
delete spec.label;
return priv.commandlist[priv.label](spec, my);
}
priv.tried = 0;
priv.doc = spec.doc || {};
if (typeof priv.doc !== "object") {
priv.doc = {
"_id": priv.doc.toString()
};
}
priv.option = spec.options || {};
priv.callbacks = spec.callbacks || {};
priv.success = [priv.callbacks.success || function () {}];
priv.error = [priv.callbacks.error || function () {}];
priv.retry = function () {
that.error({
status: 13,
statusText: 'Fail Retry',
error: 'fail_retry',
message: 'Impossible to retry.',
reason: 'Impossible to retry.'
});
};
priv.end = function () {};
priv.on_going = false;
// Methods //
/**
* Returns a serialized version of this command.
* @method serialized
* @return {object} The serialized command.
*/
that.serialized = function () {
var o = {};
o.label = that.getLabel();
o.tried = priv.tried;
o.doc = that.cloneDoc();
o.option = that.cloneOption();
return o;
};
/**
* Returns the label of the command.
* @method getLabel
* @return {string} The label.
*/
that.getLabel = function () {
return 'command';
};
/**
* Gets the document id
* @method getDocId
* @return {string} The document id
*/
that.getDocId = function () {
return priv.doc._id;
};
/**
* Gets the attachment id
* @method getAttachmentId
* @return {string} The attachment id
*/
that.getAttachmentId = function () {
return priv.doc._attachment;
};
/**
* Returns the data of the attachment
* @method getAttachmentData
* @return {string} The data
*/
that.getAttachmentData = function () {
return priv.doc._data || "";
};
/**
* Returns the data length of the attachment
* @method getAttachmentLength
* @return {number} The length
*/
that.getAttachmentLength = function () {
return (priv.doc._data || "").length;
};
/**
* Returns the mimetype of the attachment
* @method getAttachmentMimeType
* @return {string} The mimetype
*/
that.getAttachmentMimeType = function () {
return priv.doc._mimetype;
};
/**
* Generate the md5sum of the attachment data
* @method md5SumAttachmentData
* @return {string} The md5sum
*/
that.md5SumAttachmentData = function () {
return hex_md5(priv.doc._data || "");
};
/**
* Returns an information about the document.
* @method getDocInfo
* @param {string} infoname The info name.
* @return The info value.
*/
that.getDocInfo = function (infoname) {
return priv.doc[infoname];
};
/**
* Returns the value of an option.
* @method getOption
* @param {string} optionname The option name.
* @return The option value.
*/
that.getOption = function (optionname) {
return priv.option[optionname];
};
/**
* Validates the storage.
* @param {object} storage The storage.
*/
that.validate = function (storage) {
if (typeof priv.doc._id === "string" && priv.doc._id === "") {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id is invalid",
"reason": "empty"
});
return false;
}
if (!that.validateState()) {
return false;
}
return storage.validate();
};
/*
* Extend this function
*/
that.validateState = function () {
return true;
};
/**
* Check if the command can be retried.
* @method canBeRetried
* @return {boolean} The result
*/
that.canBeRetried = function () {
return (priv.option.max_retry === undefined ||
priv.option.max_retry === 0 ||
priv.tried < priv.option.max_retry);
};
/**
* Gets the number time the command has been tried.
* @method getTried
* @return {number} The number of time the command has been tried
*/
that.getTried = function () {
return priv.tried;
};
/**
* Delegate actual excecution the storage.
* @param {object} storage The storage.
*/
that.execute = function (storage) {
if (!priv.on_going) {
if (that.validate(storage)) {
priv.tried += 1;
priv.on_going = true;
storage.execute(that);
}
}
};
/**
* Execute the good method from the storage.
* Override this function.
* @method executeOn
* @param {object} storage The storage.
*/
that.executeOn = function (storage) {};
that.success = function (return_value) {
var i;
priv.on_going = false;
for (i = 0; i < priv.success.length; i += 1) {
priv.success[i](return_value);
}
priv.end(doneStatus());
priv.success = [];
priv.error = [];
};
that.retry = function (return_error) {
priv.on_going = false;
if (that.canBeRetried()) {
priv.retry();
} else {
that.error(return_error);
}
};
that.error = function (return_error) {
var i;
priv.on_going = false;
for (i = 0; i < priv.error.length; i += 1) {
priv.error[i](return_error);
}
priv.end(failStatus());
priv.success = [];
priv.error = [];
};
that.end = function () {
priv.end(doneStatus());
};
that.addCallbacks = function (success, error) {
if (arguments.length > 1) {
priv.success.push(success || function () {});
priv.error.push(error || function () {});
} else {
priv.success.push(function (response) {
(success || function () {})(undefined, response);
});
priv.error.push(function (err) {
(success || function () {})(err, undefined);
});
}
};
that.onSuccessDo = function (fun) {
if (fun) {
priv.success = fun;
} else {
return priv.success;
}
};
that.onErrorDo = function (fun) {
if (fun) {
priv.error = fun;
} else {
return priv.error;
}
};
that.onEndDo = function (fun) {
priv.end = fun;
};
that.onRetryDo = function (fun) {
priv.retry = fun;
};
/**
* Is the command can be restored by another JIO : yes.
* @method canBeRestored
* @return {boolean} true
*/
that.canBeRestored = function () {
return true;
};
/**
* Clones the command and returns it.
* @method clone
* @return {object} The cloned command.
*/
that.clone = function () {
return command(that.serialized(), my);
};
/**
* Clones the command options and returns the clone version.
* @method cloneOption
* @return {object} The clone of the command options.
*/
that.cloneOption = function () {
return JSON.parse(JSON.stringify(priv.option));
};
/**
* Clones the document and returns the clone version.
* @method cloneDoc
* @return {object} The clone of the document.
*/
that.cloneDoc = function () {
return JSON.parse(JSON.stringify(priv.doc));
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var getAttachmentCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'getAttachment';
};
that.executeOn = function (storage) {
storage.getAttachment(that);
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var getCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'get';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" &&
that.getDocId() !== "")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.get(that);
};
that.canBeRestored = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var postCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'post';
};
that.validateState = function () {
return true;
};
that.executeOn = function (storage) {
storage.post(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var putAttachmentCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'putAttachment';
};
that.executeOn = function (storage) {
storage.putAttachment(that);
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var putCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'put';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.put(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var removeAttachmentCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'removeAttachment';
};
that.executeOn = function (storage) {
storage.removeAttachment(that);
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var removeCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'remove';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.remove(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var repairCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'repair';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.repair(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global secureMethods, exports, defineConstant, console */
/**
* Inspired by nodejs EventEmitter class
* http://nodejs.org/api/events.html
*
* When an EventEmitter instance experiences an error, the typical action is
* to emit an 'error' event. Error events are treated as a special case in
* node. If there is no listener for it, then the default action throws the
* exception again.
*
* All EventEmitters emit the event 'newListener' when new listeners are added
* and 'removeListener' when a listener is removed.
*
* @class EventEmitter
* @constructor
*/
function EventEmitter() {
this._events = {};
this._maxListeners = 10;
}
/**
* Adds a listener to the end of the listeners array for the specified
* event.
*
* @method addListener
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.addListener = function (event, listener) {
var listener_list;
if (typeof listener !== "function") {
return this;
}
this.emit("newListener", event, listener);
listener_list = this._events[event];
if (listener_list === undefined) {
this._events[event] = listener;
listener_list = listener;
} else if (typeof listener_list === "function") {
this._events[event] = [listener_list, listener];
listener_list = this._events[event];
} else {
listener_list[listener_list.length] = listener;
}
if (this._maxListeners > 0 &&
typeof listener_list !== "function" &&
listener_list.length > this._maxListeners &&
listener_list.warned !== true) {
console.warn("warning: possible EventEmitter memory leak detected. " +
listener_list.length + " listeners added. " +
"Use emitter.setMaxListeners() to increase limit.");
listener_list.warned = true;
}
return this;
};
/**
* #crossLink "EventEmitter/addListener:method"
*
* @method on
*/
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/**
* Adds a one time listener for the event. This listener is invoked only the
* next time the event is fired, after which it is removed.
*
* @method once
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.once = function (event, listener) {
var that = this, wrapper = function () {
that.removeListener(event, wrapper);
listener.apply(that, arguments);
};
wrapper.original = listener;
return that.on(event, wrapper);
};
/**
* Remove a listener from the listener array for the specified event.
* Caution: changes array indices in the listener array behind the listener
*
* @method removeListener
* @param {String} event The event name
* @param {Function} listener The listener callback
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.removeListener = function (event, listener) {
var listener_list = this._events[event], i;
if (listener_list) {
if (typeof listener_list === "function") {
if (listener_list === listener || listener_list.original === listener) {
delete this._events[event];
}
return this;
}
for (i = 0; i < listener_list.length; i += 1) {
if (listener_list[i] === listener ||
listener_list[i].original === listener) {
listener_list.splice(i, 1);
this.emit("removeListener", event, listener);
break;
}
}
if (listener_list.length === 1) {
this._events[event] = listener_list[0];
}
if (listener_list.length === 0) {
this._events[event] = undefined;
}
}
return this;
};
/**
* Removes all listeners, or those of the specified event.
*
* @method removeAllListeners
* @param {String} event The event name (optional)
* @return {EventEmitter} This emitter
*/
EventEmitter.prototype.removeAllListeners = function (event) {
var key;
if (event === undefined) {
for (key in this._events) {
if (this._events.hasOwnProperty(key)) {
delete this._events[key];
}
}
return this;
}
delete this._events[event];
return this;
};
/**
* By default EventEmitters will print a warning if more than 10 listeners
* are added for a particular event. This is a useful default which helps
* finding memory leaks. Obviously not all Emitters should be limited to 10.
* This function allows that to be increased. Set to zero for unlimited.
*
* @method setMaxListeners
* @param {Number} max_listeners The maximum of listeners
*/
EventEmitter.prototype.setMaxListeners = function (max_listeners) {
this._maxListeners = max_listeners;
};
/**
* Execute each of the listeners in order with the supplied arguments.
*
* @method emit
* @param {String} event The event name
* @param {Any} [args]* The listener argument to give
* @return {Boolean} true if event had listeners, false otherwise.
*/
EventEmitter.prototype.emit = function (event) {
var i, argument_list, listener_list;
listener_list = this._events[event];
if (typeof listener_list === 'function') {
listener_list = [listener_list];
} else if (Array.isArray(listener_list)) {
listener_list = listener_list.slice();
} else {
return false;
}
argument_list = Array.prototype.slice.call(arguments, 1);
for (i = 0; i < listener_list.length; i += 1) {
try {
listener_list[i].apply(this, argument_list);
} catch (e) {
if (this.listeners("error").length > 0) {
this.emit("error", e);
break;
}
throw e;
}
}
return true;
};
/**
* Returns an array of listeners for the specified event.
*
* @method listeners
* @param {String} event The event name
* @return {Array} The array of listeners
*/
EventEmitter.prototype.listeners = function (event) {
return (typeof this._events[event] === 'function' ?
[this._events[event]] : (this._events[event] || []).slice());
};
/**
* Static method; Return the number of listeners for a given event.
*
* @method listenerCount
* @static
* @param {EventEmitter} emitter The event emitter
* @param {String} event The event name
* @return {Number} The number of listener
*/
EventEmitter.listenerCount = function (emitter, event) {
return emitter.listeners(event).length;
};
exports.EventEmitter = EventEmitter;
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true, regexp: true */
/*global Deferred, inherits, constants, dictUpdate, deepClone, Blob */
function IODeferred(method, info) {
IODeferred.super_.call(this);
this._info = info || {};
this._method = method;
// this._options = options;
}
inherits(IODeferred, Deferred);
IODeferred.prototype.resolve = function (a, b) {
if (this._method === 'getAttachment') {
if (typeof a === 'string') {
return IODeferred.super_.prototype.resolve.call(this, new Blob([a]));
}
if (a instanceof Blob) {
return IODeferred.super_.prototype.resolve.call(this, a);
}
}
// resolve('ok', {"custom": "value"});
// resolve(200, {...});
// resolve({...});
var weak = {"ok": true}, strong = {};
if (this._method === 'post') {
weak.status = constants.http_status.created;
weak.statusText = constants.http_status_text.created;
} else {
weak.status = constants.http_status.ok;
weak.statusText = constants.http_status_text.ok;
weak.id = this._info._id;
if (/Attachment$/.test(this._method)) {
weak.attachment = this._info._attachment;
}
}
if (a !== undefined && (typeof a !== 'object' || Array.isArray(a))) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return this.reject(
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
);
}
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
}
dictUpdate(weak, strong);
strong = undefined; // free memory
if (typeof weak.id !== 'string' || !weak.id) {
return this.reject(
'internal_storage_error',
'invalid response',
'New document id have to be specified'
);
}
//return super_resolve(deepClone(weak));
return IODeferred.super_.prototype.resolve.call(this, deepClone(weak));
};
IODeferred.prototype.reject = function (a, b, c, d) {
// reject(status, reason, message, {"custom": "value"});
// reject(status, reason, {..});
// reject(status, {..});
var weak = {}, strong = {};
weak.status = constants.http_status.unknown;
weak.statusText = constants.http_status_text.unknown;
weak.message = 'Command failed';
weak.reason = 'fail';
if (typeof a !== 'object' || Array.isArray(a)) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return this.reject(
// can create infernal loop if 'internal_storage_error' is not defined
// in the constants
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
);
}
a = b;
b = c;
c = d;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.reason = a;
a = b;
b = c;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.message = a;
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
}
dictUpdate(weak, strong);
strong = undefined;
if (weak.error === undefined) {
weak.error = weak.statusText.toLowerCase().replace(/ /g, '_').
replace(/[^_a-z]/g, '');
}
if (typeof weak.message !== 'string') {
weak.message = "";
}
if (typeof weak.reason !== 'string') {
weak.reason = "unknown";
}
//return super_reject(deepClone(weak));
return IODeferred.super_.prototype.reject.call(this, deepClone(weak));
};
IODeferred.createFromDeferred = function (method, info, options, deferred) {
var iodeferred = new IODeferred(method, info, options);
// iodeferred.promise().done(deferred.resolve.bind(deferred)).
// fail(deferred.reject.bind(deferred)).
// progress(deferred.notify.bind(deferred));
// // phantomjs doesn't like 'bind'...
iodeferred.promise().done(function () {
deferred.resolve.apply(deferred, arguments);
}).fail(function () {
deferred.reject.apply(deferred, arguments);
}).progress(function () {
deferred.notify.apply(deferred, arguments);
});
return iodeferred;
};
IODeferred.createFromParam = function (param) {
return IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
};
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true, regexp: true */
/*global EventEmitter, deepClone, inherits, secureMethods, defineConstant,
exports */
/*global addBlobStorageUtilities, addClassesToStorageUtilities, enableRestAPI,
enableRestParamChecker, enableCommandMaker, enableCommandExecuter,
enableCommandTimeout, enableJobMaker, enableJobRetry, enableJobChecker,
enableJobQueue, enableJobRecoverer, enableJobExecuter, enableJobTimeout */
function JIO(storage_spec, options) {
JIO.super_.call(this);
var that = this, shared = new EventEmitter();
shared.storage_spec = deepClone(storage_spec);
if (options === undefined) {
options = {};
} else if (typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError("JIO(): Optional argument 2 is not of type 'object'");
}
enableRestAPI(that, shared, options);
enableRestParamChecker(that, shared, options);
enableJobMaker(that, shared, options);
enableJobRetry(that, shared, options);
// enableJobChecker(that, shared, options);
enableJobQueue(that, shared, options);
// enableJobRecoverer(that, shared, options);
enableJobTimeout(that, shared, options);
enableJobExecuter(that, shared, options);
shared.emit('load');
}
inherits(JIO, EventEmitter);
JIO.createInstance = function (storage_spec, options) {
return new JIO(storage_spec, options);
};
exports.JIO = JIO;
exports.createJIO = JIO.createInstance;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global deepClone */
/**
* Tool to manipulate a list of object containing at least one property: 'id'.
* Id must be a number > 0.
*
* @class JobQueue
* @constructor
* @param {Array} An array of object
*/
function JobQueue(array) {
this._array = array;
}
/**
* Removes elements which are not objects containing at least 'id' property.
*
* @method repair
*/
JobQueue.prototype.repair = function () {
var i, job;
for (i = 0; i < this._array.length; i += 1) {
job = this._array[i];
if (typeof job !== 'object' || Array.isArray(job) ||
typeof job.id !== 'number' || job.id <= 0) {
this._array.splice(i, 1);
i -= 1;
}
}
};
/**
* Post an object and generate an id
*
* @method post
* @param {Object} job The job object
* @return {Number} The generated id
*/
JobQueue.prototype.post = function (job) {
var i, next = 1;
// get next id
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id >= next) {
next = this._array[i].id + 1;
}
}
job.id = next;
this._array[this._array.length] = deepClone(job);
return this;
};
/**
* Put an object to the list. If an object contains the same id, it is replaced
* by the new one.
*
* @method put
* @param {Object} job The job object with an id
*/
JobQueue.prototype.put = function (job) {
var i;
if (typeof job.id !== 'number' || job.id <= 0) {
throw new TypeError("JobQueue().put(): Job id should be a positive number");
}
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === job.id) {
break;
}
}
this._array[i] = deepClone(job);
return this;
};
/**
* Puts some object into the list. Update object with the same id, and add
* unreferenced one.
*
* @method update
* @param {Array} job_list A list of new jobs
*/
JobQueue.prototype.update = function (job_list) {
var i, j = 0, j_max, index = {}, next = 1, job, post_list = [];
j_max = this._array.length;
for (i = 0; i < job_list.length; i += 1) {
if (typeof job_list[i].id !== 'number' || job_list[i].id <= 0) {
// this job has no id, it has to be post
post_list[post_list.length] = job_list[i];
} else {
job = deepClone(job_list[i]);
if (index[job.id] !== undefined) {
// this job is on the list, update
this._array[index[job.id]] = job;
} else if (j === j_max) {
// this job is not on the list, update
this._array[this._array.length] = job;
} else {
// don't if the job is there or not
// searching same job in the original list
while (j < j_max) {
// references visited job
index[this._array[j].id] = j;
if (this._array[j].id >= next) {
next = this._array[j].id + 1;
}
if (this._array[j].id === job.id) {
// found on the list, just update
this._array[j] = job;
break;
}
j += 1;
}
if (j === j_max) {
// not found on the list, add to the end
this._array[this._array.length] = job;
} else {
// found on the list, already updated
j += 1;
}
}
if (job.id >= next) {
next = job.id + 1;
}
}
}
for (i = 0; i < post_list.length; i += 1) {
// adding job without id
post_list[i].id = next;
next += 1;
this._array[this._array.length] = deepClone(post_list[i]);
}
return this;
};
/**
* Get an object from an id. Returns undefined if not found
*
* @method get
* @param {Number} id The job id
* @return {Object} The job or undefined
*/
JobQueue.prototype.get = function (id) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === id) {
return deepClone(this._array[i]);
}
}
};
/**
* Removes an object from an id
*
* @method remove
* @param {Number} id The job id
*/
JobQueue.prototype.remove = function (id) {
var i;
for (i = 0; i < this._array.length; i += 1) {
if (this._array[i].id === id) {
this._array.splice(i, 1);
return true;
}
}
return false;
};
/**
* Clears the list.
*
* @method clear
*/
JobQueue.prototype.clear = function () {
this._array.length = 0;
return this;
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global JobQueue, uniqueJSONStringify, deepClone, dictFilter */
/**
* Manipulates a job queue and is also able to store it in a specific place.
*
* @class JobWorkspace
* @constructor
* @param {Workspace} workspace The workspace where to store
* @param {JobQueue} job_queue The job queue to use
* @param {String} namespace The namespace to use in the workspace
* @param {Array} job_keys An array of job keys to store
*/
function JobWorkspace(workspace, job_queue, namespace, job_keys) {
this._workspace = workspace;
this._job_queue = job_queue;
this._namespace = namespace;
this._job_keys = job_keys;
}
/**
* Store the job queue into the workspace.
*
* @method save
*/
JobWorkspace.prototype.save = function () {
var i, job_queue = deepClone(this._job_queue._array);
for (i = 0; i < job_queue.length; i += 1) {
dictFilter(job_queue[i], this._job_keys);
}
if (this._job_queue._array.length === 0) {
this._workspace.removeItem(this._namespace);
} else {
this._workspace.setItem(
this._namespace,
uniqueJSONStringify(job_queue)
);
}
return this;
};
/**
* Loads the job queue from the workspace.
*
* @method load
*/
JobWorkspace.prototype.load = function () {
var job_list;
try {
job_list = JSON.parse(this._workspace.getItem(this._namespace));
} catch (ignore) {}
if (!Array.isArray(job_list)) {
job_list = [];
}
this._job_queue.clear();
new JobQueue(job_list).repair();
this._job_queue.update(job_list);
return this;
};
/**
* Post a job in the job queue
*
* @method post
* @param {Object} job The job object
* @return {Number} The generated id
*/
JobWorkspace.prototype.post = function (job) {
return this._job_queue.post(job);
};
/**
* Put a job to the job queue
*
* @method put
* @param {Object} job The job object with an id
*/
JobWorkspace.prototype.put = function (job) {
return this._job_queue.put(job);
};
/**
* Get a job from an id. Returns undefined if not found
*
* @method get
* @param {Number} id The job id
* @return {Object} The job or undefined
*/
JobWorkspace.prototype.get = function (id) {
return this._job_queue.get(id);
};
/**
* Removes a job from an id
*
* @method remove
* @param {Number} id The job id
*/
JobWorkspace.prototype.remove = function (id) {
return this._job_queue.remove(id);
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localStorage */
// keywords: js, javascript, store on local storage as array
function LocalStorageArray(namespace) {
var index, next;
function nextId() {
var i = next;
next += 1;
return i;
}
this.length = function () {
return index.length;
};
this.truncate = function (length) {
var i;
if (length === index.length) {
return this;
}
if (length > index.length) {
index.length = length;
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
}
while (length < index.length) {
i = index.pop();
if (i !== undefined && i !== null) {
delete localStorage[namespace + '.' + i];
}
}
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.get = function (i) {
return JSON.parse(localStorage[namespace + '.' + index[i]] || 'null');
};
this.set = function (i, value) {
if (index[i] === undefined || index[i] === null) {
index[i] = nextId();
localStorage[namespace + '.' + index[i]] = JSON.stringify(value);
localStorage[namespace + '.index'] = JSON.stringify(index);
} else {
localStorage[namespace + '.' + index[i]] = JSON.stringify(value);
}
return this;
};
this.append = function (value) {
index[index.length] = nextId();
localStorage[namespace + '.' + index[index.length - 1]] =
JSON.stringify(value);
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.pop = function (i) {
var value, key;
if (i === undefined || i === null) {
key = namespace + '.' + index[index.length - 1];
index.pop();
} else {
if (i < 0 || i >= index.length) {
return null;
}
key = namespace + '.' + i;
index.splice(i, 1);
}
value = localStorage[key];
if (index.length === 0) {
delete localStorage[namespace + '.index'];
} else {
localStorage[namespace + '.index'] = JSON.stringify(index);
}
delete localStorage[key];
return JSON.parse(value || 'null');
};
this.clear = function () {
var i;
for (i = 0; i < index.length; i += 1) {
delete localStorage[namespace + '.' + index[i]];
}
index = [];
delete localStorage[namespace + '.index'];
return this;
};
this.reload = function () {
var i;
index = JSON.parse(localStorage[namespace + '.index'] || '[]');
next = 0;
for (i = 0; i < index.length; i += 1) {
if (next < index[i]) {
next = index[i];
}
}
return this;
};
this.toArray = function () {
var i, list = [];
for (i = 0; i < index.length; i += 1) {
list[list.length] = this.get(i);
}
return list;
};
this.update = function (list) {
if (!Array.isArray(list)) {
throw new TypeError("LocalStorageArray().saveArray(): " +
"Argument 1 is not of type 'array'");
}
var i, location;
// update previous values
for (i = 0; i < list.length; i += 1) {
location = index[i];
if (location === undefined || location === null) {
location = nextId();
index[i] = location;
}
localStorage[namespace + '.' + location] =
JSON.stringify(list[i]);
}
// remove last ones
while (list.length < index.length) {
location = index.pop();
if (location !== undefined && location !== null) {
delete localStorage[namespace + '.' + location];
}
}
// store index
localStorage[namespace + '.index'] = JSON.stringify(index);
return this;
};
this.reload();
}
LocalStorageArray.saveArray = function (namespace, list) {
if (!Array.isArray(list)) {
throw new TypeError("LocalStorageArray.saveArray(): " +
"Argument 2 is not of type 'array'");
}
var local_storage_array = new LocalStorageArray(namespace).clear(), i;
for (i = 0; i < list.length; i += 1) {
local_storage_array.append(list[i]);
}
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global exports, deepClone, jsonDeepClone */
/**
* A class to manipulate metadata
*
* @class Metadata
* @constructor
*/
function Metadata(metadata) {
if (arguments.length > 0) {
if (typeof metadata !== 'object' ||
Object.getPrototypeOf(metadata || []) !== Object.prototype) {
throw new TypeError("Metadata(): Optional argument 1 is not an object");
}
this._dict = metadata;
} else {
this._dict = {};
}
}
Metadata.prototype.format = function () {
return this.update(this._dict);
};
Metadata.prototype.update = function (metadata) {
var k;
for (k in metadata) {
if (metadata.hasOwnProperty(k)) {
if (k[0] === '_') {
this._dict[k] = jsonDeepClone(metadata[k]);
} else {
this._dict[k] = Metadata.normalizeValue(metadata[k]);
}
if (this._dict[k] === undefined) {
delete this._dict[k];
}
}
}
return this;
};
Metadata.prototype.get = function (key) {
return this._dict[key];
};
Metadata.prototype.add = function (key, value) {
var i;
if (key[0] === '_') {
return this;
}
if (this._dict[key] === undefined) {
this._dict[key] = Metadata.normalizeValue(value);
if (this._dict[key] === undefined) {
delete this._dict[key];
}
return this;
}
if (!Array.isArray(this._dict[key])) {
this._dict[key] = [this._dict[key]];
}
value = Metadata.normalizeValue(value);
if (value === undefined) {
return this;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
this._dict[key][this._dict[key].length] = value[i];
}
return this;
};
Metadata.prototype.set = function (key, value) {
if (key[0] === '_') {
this._dict[key] = JSON.parse(JSON.stringify(value));
} else {
this._dict[key] = Metadata.normalizeValue(value);
}
if (this._dict[key] === undefined) {
delete this._dict[key];
}
return this;
};
Metadata.prototype.remove = function (key) {
delete this._dict[key];
return this;
};
Metadata.prototype.forEach = function (key, fun) {
var k, i, value, that = this;
if (typeof key === 'function') {
fun = key;
key = undefined;
}
function forEach(key, fun) {
value = that._dict[key];
if (!Array.isArray(that._dict[key])) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
fun.call(that, key, deepClone(value[i]), i);
} else {
fun.call(that, key, {'content': value[i]}, i);
}
}
}
if (key === undefined) {
for (k in this._dict) {
if (this._dict.hasOwnProperty(k)) {
forEach(k, fun);
}
}
} else {
forEach(key, fun);
}
return this;
};
Metadata.prototype.toFullDict = function () {
var dict = {};
this.forEach(function (key, value, index) {
dict[key] = dict[key] || [];
dict[key][index] = value;
});
return dict;
};
Metadata.asJsonableValue = function (value) {
switch (typeof value) {
case 'string':
case 'boolean':
return value;
case 'number':
if (isFinite(value)) {
return value;
}
return null;
case 'object':
if (value === null) {
return null;
}
if (value instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is a invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. Here, the
// result will always be `null`.
if (isNaN(value.getTime())) {
return null;
}
}
if (typeof value.toJSON === 'function') {
return Metadata.asJsonableValue(value.toJSON());
}
return value; // dict, array
// case 'undefined':
default:
return null;
}
};
Metadata.isDict = function (o) {
return typeof o === 'object' &&
Object.getPrototypeOf(o || []) === Object.prototype;
};
Metadata.isContent = function (c) {
return typeof c === 'string' ||
(typeof c === 'number' && isFinite(c)) ||
typeof c === 'boolean';
};
Metadata.contentValue = function (value) {
if (Array.isArray(value)) {
return Metadata.contentValue(value[0]);
}
if (Metadata.isDict(value)) {
return value.content;
}
return value;
};
Metadata.normalizeArray = function (value) {
var i;
value = value.slice();
i = 0;
while (i < value.length) {
value[i] = Metadata.asJsonableValue(value[i]);
if (Metadata.isDict(value[i])) {
value[i] = Metadata.normalizeObject(value[i]);
if (value[i] === undefined) {
value.splice(i, 1);
} else {
i += 1;
}
} else if (Metadata.isContent(value[i])) {
i += 1;
} else {
value.splice(i, 1);
}
}
if (value.length === 0) {
return;
}
if (value.length === 1) {
return value[0];
}
return value;
};
Metadata.normalizeObject = function (value) {
var i, count = 0, ok = false, new_value = {};
for (i in value) {
if (value.hasOwnProperty(i)) {
value[i] = Metadata.asJsonableValue(value[i]);
if (Metadata.isContent(value[i])) {
new_value[i] = value[i];
if (new_value[i] === undefined) {
delete new_value[i];
}
count += 1;
if (i === 'content') {
ok = true;
}
}
}
}
if (ok === false) {
return;
}
if (count === 1) {
return new_value.content;
}
return new_value;
};
Metadata.normalizeValue = function (value) {
value = Metadata.asJsonableValue(value);
if (Metadata.isContent(value)) {
return value;
}
if (Array.isArray(value)) {
return Metadata.normalizeArray(value);
}
if (Metadata.isDict(value)) {
return Metadata.normalizeObject(value);
}
};
exports.Metadata = Metadata;
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Deferred, exports, setInterval, setTimeout, clearInterval,
clearTimeout */
/**
* Promise()
*
* @class Promise
* @constructor
*/
function Promise() {
this._onReject = [];
this._onResolve = [];
this._onProgress = [];
this._state = "";
this._answers = undefined;
}
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// when(value, callback, errback_opt)
/**
* when(item, [onSuccess], [onError]): Promise
*
* Return an item as first parameter of the promise answer. If item is of
* type Promise, the method will just return the promise. If item is of type
* Deferred, the method will return the deferred promise.
*
* Promise.when('a').then(console.log); // shows 'a'
*
* @method when
* @static
* @param {Any} item The item to use
* @param {Function} [onSuccess] The callback called on success
* @param {Function} [onError] the callback called on error
* @return {Promise} The promise
*/
Promise.when = function (item, onSuccess, onError) {
if (item instanceof Promise) {
return item;
}
if (typeof Deferred === 'function' && item instanceof Deferred) {
return item.promise();
}
var p = new Promise().done(onSuccess).fail(onError);
p.defer().resolve(item);
return p;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// get(object, name)
/**
* get(dict, property): Promise
*
* Return the dict property as first parameter of the promise answer.
*
* Promise.get({'a': 'b'}, 'a').then(console.log); // shows 'b'
*
* @method get
* @static
* @param {Object} dict The object to use
* @param {String} property The object property name
* @return {Promise} The promise
*/
Promise.get = function (dict, property) {
var p = new Promise(), solver = p.defer();
try {
solver.resolve(dict[property]);
} catch (e) {
solver.reject(e);
}
return p;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// put(object, name, value)
/**
* put(dict, property, value): Promise
*
* Set and return the dict property as first parameter of the promise answer.
*
* Promise.put({'a': 'b'}, 'a', 'c').then(console.log); // shows 'c'
*
* @method put
* @static
* @param {Object} dict The object to use
* @param {String} property The object property name
* @param {Any} value The value
* @return {Promise} The promise
*/
Promise.put = function (dict, property, value) {
var p = new Promise(), solver = p.defer();
try {
dict[property] = value;
solver.resolve(dict[property]);
} catch (e) {
solver.reject(e);
}
return p;
};
/**
* execute(callback): Promise
*
* Execute the callback and use the returned value as promise answer.
*
* Promise.execute(function () {
* return 'a';
* }).then(console.log); // shows 'a'
*
* @method execute
* @static
* @param {Function} callback The callback to execute
* @return {Promise} The promise
*/
Promise.execute = function (callback) {
return new Promise().defer(function (solver) {
try {
solver.resolve(callback());
} catch (e) {
solver.reject(e);
}
});
};
/**
* all(items): Promise
*
* Resolve the promise. The item type must be like the item parameter of the
* `when` static method.
*
* Promise.all([promisedError, 'b']).
* then(console.log); // shows [Error, 'b']
*
* @method all
* @static
* @param {Array} items The items to use
* @return {Promise} The promise
*/
Promise.all = function (items) {
var array = [], count = 0, next = new Promise(), solver;
solver = next.defer();
function succeed(i) {
return function (answer) {
array[i] = answer;
count += 1;
if (count !== items.length) {
return;
}
return solver.resolve(array);
};
}
items.forEach(function (item, i) {
Promise.when(item).done(succeed(i)).fail(succeed(i));
});
return next;
};
/**
* allOrNone(items): Promise
*
* Resolve the promise only when all items are resolved. If one item fails, then
* reject. The item type must be like the item parameter of the `when` static
* method.
*
* Promise.allOrNone([Promise.when('a'), 'b']).
* then(console.log); // shows ['a', 'b']
*
* @method allOrNone
* @static
* @param {Array} items The items to use
* @return {Promise} The promise
*/
Promise.allOrNone = function (items) {
var array = [], count = 0, next = new Promise(), solver;
solver = next.defer();
items.forEach(function (item, i) {
Promise.when(item).done(function (answer) {
array[i] = answer;
count += 1;
if (count !== items.length) {
return;
}
return solver.resolve(array);
}).fail(function (answer) {
return solver.reject(answer);
});
});
return next;
};
/**
* any(items): Promise
*
* Resolve the promise only when one of the items is resolved. The item type
* must be like the item parameter of the `when` static method.
*
* Promise.any([promisedError, Promise.delay(10)]).
* then(console.log); // shows 10
*
* @method any
* @static
* @param {Array} items The items to use
* @return {Promise} The promise
*/
Promise.any = function (items) {
var count = 0, next = new Promise(), solver;
solver = next.defer();
items.forEach(function (item) {
Promise.when(item).done(function (answer) {
return solver.resolve(answer);
}).fail(function (answer) {
count += 1;
if (count === items.length) {
return solver.reject(answer);
}
});
});
return next;
};
/**
* first(*items): Promise
*
* Resolve the promise only when one item is resolved. The item type must be
* like the item parameter of the `when` static method.
*
* Promise.first(Promise.delay(100), 'b').then(console.log); // shows 'b'
*
* @method first
* @static
* @param {Any} *items The items to use
* @return {Promise} The promise
*/
Promise.first = function () { // *promises
var next = new Promise(), solver = next.defer();
Array.prototype.forEach.call(arguments, function (item) {
Promise.when(item).done(solver.resolve).fail(solver.reject);
});
return next;
};
/**
* delay(timeout[, every]): Promise
*
* Resolve the promise after `timeout` milliseconds and notfies us every `every`
* milliseconds.
*
* Promise.delay(50, 10).then(console.log, console.error, console.log);
* // // shows
* // 10 // from progress
* // 20 // from progress
* // 30 // from progress
* // 40 // from progress
* // 50 // from success
*
* @method delay
* @static
* @param {Number} timeout In milliseconds
* @param {Number} [every] In milliseconds
* @return {Promise} The promise
*/
Promise.delay = function (timeout, every) { // *promises
var next = new Promise(), solver, ident, now = 0;
solver = next.defer();
if (typeof every === 'number' && !isNaN(every)) {
ident = setInterval(function () {
now += every;
solver.notify(now);
}, every);
}
setTimeout(function () {
clearInterval(ident);
solver.resolve(timeout);
}, timeout);
return next;
};
/**
* timeout(item, timeout): Promise
*
* If the promise is not resolved after `timeout` milliseconds, it returns a
* timeout error.
*
* Promise.timeout('a', 100).then(console.log); // shows 'a'
*
* Promise.timeout(Promise.delay(100), 10).then(console.log, console.error);
* // shows Error Timeout
*
* @method timeout
* @static
* @param {Any} Item The item to use
* @param {Number} timeout In milliseconds
* @return {Promise} The promise
*/
Promise.timeout = function (item, timeout) {
var next = new Promise(), solver, i;
solver = next.defer();
i = setTimeout(function () {
solver.reject.apply(next, [new Error("Timeout")]);
}, timeout);
Promise.when(item).done(function () {
clearTimeout(i);
solver.resolve.apply(next, arguments);
}).fail(function () {
clearTimeout(i);
solver.reject.apply(next, arguments);
});
return next;
};
/**
* defer([callback]): Promise
*
* Set the promise to the 'running' state. If `callback` is a function, then it
* will be executed with a solver as first parameter and returns the promise.
* Else it returns the promise solver.
*
* @method defer
* @param {Function} [callback] The callback to execute
* @return {Promise,Object} The promise or the promise solver
*/
Promise.prototype.defer = function (callback) {
var that = this;
switch (this._state) {
case "running":
case "resolved":
case "rejected":
throw new Error("Promise().defer(): Already " + this._state);
default:
break;
}
function createSolver() {
return {
"resolve": function () {
if (that._state !== "resolved" && that._state !== "rejected") {
that._state = "resolved";
that._answers = arguments;
that._onResolve.forEach(function (callback) {
setTimeout(function () {
callback.apply(that, that._answers);
});
});
// free the memory
that._onResolve = undefined;
that._onReject = undefined;
that._onProgress = undefined;
}
},
"reject": function () {
if (that._state !== "resolved" && that._state !== "rejected") {
that._state = "rejected";
that._answers = arguments;
that._onReject.forEach(function (callback) {
setTimeout(function () {
callback.apply(that, that._answers);
});
});
// free the memory
that._onResolve = undefined;
that._onReject = undefined;
that._onProgress = undefined;
}
},
"notify": function () {
if (that._onProgress) {
var answers = arguments;
that._onProgress.forEach(function (callback) {
callback.apply(that, answers);
});
}
}
};
}
this._state = "running";
if (typeof callback === 'function') {
setTimeout(function () {
callback(createSolver());
});
return this;
}
return createSolver();
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// then(fulfilledHandler, errorHandler, progressHandler)
/**
* then([onSuccess], [onError], [onProgress]): Promise
*
* Returns a new Promise with the return value of the `onSuccess` or `onError`
* callback as first parameter. If the pervious promise is resolved, the
* `onSuccess` callback is called. If rejected, the `onError` callback is
* called. If notified, `onProgress` is called.
*
* Promise.when(1).
* then(function (one) { return one + 1; }).
* then(console.log); // shows 2
*
* @method then
* @param {Function} [onSuccess] The callback to call on resolve
* @param {Function} [onError] The callback to call on reject
* @param {Function} [onProgress] The callback to call on notify
* @return {Promise} The new promise
*/
Promise.prototype.then = function (onSuccess, onError, onProgress) {
var next = new Promise(), that = this;
switch (this._state) {
case "resolved":
if (typeof onSuccess === 'function') {
next.defer(function (resolver) {
try {
resolver.resolve(onSuccess.apply(that, that._answers));
} catch (e) {
resolver.reject(e);
}
});
}
break;
case "rejected":
if (typeof onError === 'function') {
next.defer(function (resolver) {
try {
resolver.resolve(onError.apply(that, that._answers));
} catch (e) {
resolver.reject(e);
}
});
}
break;
default:
if (typeof onSuccess === 'function') {
this._onResolve.push(function () {
var answers = arguments;
next.defer(function (resolver) {
try {
resolver.resolve(onSuccess.apply(that, answers));
} catch (e) {
resolver.reject(e);
}
});
});
}
if (typeof onError === 'function') {
this._onReject.push(function () {
var answers = arguments;
next.defer(function (resolver) {
try {
resolver.resolve(onError.apply(that, answers));
} catch (e) {
resolver.reject(e);
}
});
});
}
if (typeof onProgress === 'function') {
this._onProgress.push(onProgress);
}
break;
}
return next;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// get(propertyName)
/**
* get(property): Promise
*
* Get the property of the promise response as first parameter of the new
* Promise.
*
* Promise.when({'a': 'b'}).get('a').then(console.log); // shows 'b'
*
* @method get
* @param {String} property The object property name
* @return {Promise} The promise
*/
Promise.prototype.get = function (property) {
return this.then(function (dict) {
return dict[property];
});
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// call(functionName, arg1, arg2, ...)
Promise.prototype.call = function (function_name) {
var args = Array.prototype.slice.call(arguments, 1);
return this.then(function (dict) {
return dict[function_name].apply(dict, args);
});
};
/**
* done(callback): Promise
*
* Call the callback on resolve.
*
* Promise.when(1).
* done(function (one) { return one + 1; }).
* done(console.log); // shows 1
*
* @method done
* @param {Function} callback The callback to call on resolve
* @return {Promise} This promise
*/
Promise.prototype.done = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case "resolved":
setTimeout(function () {
callback.apply(that, that._answers);
});
break;
case "rejected":
break;
default:
this._onResolve.push(callback);
break;
}
return this;
};
/**
* fail(callback): Promise
*
* Call the callback on reject.
*
* promisedTypeError().
* fail(function (e) { name_error(); }).
* fail(console.log); // shows TypeError
*
* @method fail
* @param {Function} callback The callback to call on reject
* @return {Promise} This promise
*/
Promise.prototype.fail = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case "rejected":
setTimeout(function () {
callback.apply(that, that._answers);
});
break;
case "resolved":
break;
default:
this._onReject.push(callback);
break;
}
return this;
};
/**
* progress(callback): Promise
*
* Call the callback on notify.
*
* Promise.delay(100, 10).
* progress(function () { return null; }).
* progress(console.log); // does not show null
*
* @method progress
* @param {Function} callback The callback to call on notify
* @return {Promise} This promise
*/
Promise.prototype.progress = function (callback) {
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case "rejected":
case "resolved":
break;
default:
this._onProgress.push(callback);
break;
}
return this;
};
/**
* always(callback): Promise
*
* Call the callback on resolve or on reject.
*
* sayHello().
* done(iAnswer).
* fail(iHeardNothing).
* always(iKeepWalkingAnyway);
*
* @method always
* @param {Function} callback The callback to call on resolve or on reject
* @return {Promise} This promise
*/
Promise.prototype.always = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case "resolved":
case "rejected":
setTimeout(function () {
callback.apply(that, that._answers);
});
break;
default:
that._onReject.push(callback);
that._onResolve.push(callback);
break;
}
return this;
};
function Deferred() {
this._promise = new Promise();
this._solver = this._promise.defer();
}
Deferred.prototype.resolve = function () {
this._solver.resolve.apply(this._solver, arguments);
};
Deferred.prototype.reject = function () {
this._solver.reject.apply(this._solver, arguments);
};
Deferred.prototype.notify = function () {
this._solver.notify.apply(this._solver, arguments);
};
Deferred.prototype.promise = function () {
return this._promise;
};
exports.Promise = Promise;
exports.Deferred = Deferred;
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global exports, defaults, defineConstant */
function Storage() { // (storage_spec, util)
return undefined; // this is a constructor
}
// end Storage
function createStorage(storage_spec, util) {
if (typeof storage_spec.type !== 'string') {
throw new TypeError("Invalid storage description");
}
if (!defaults.storage_types[storage_spec.type]) {
throw new TypeError("Unknown storage '" + storage_spec.type + "'");
}
return new defaults.storage_types[storage_spec.type](storage_spec, util);
}
function addStorage(type, Constructor) {
// var proto = {};
if (typeof type !== 'string') {
throw new TypeError("jIO.addStorage(): Argument 1 is not of type 'string'");
}
if (typeof Constructor !== 'function') {
throw new TypeError("jIO.addStorage(): " +
"Argument 2 is not of type 'function'");
}
if (defaults.storage_types[type]) {
throw new TypeError("jIO.addStorage(): Storage type already exists");
}
// dictUpdate(proto, Constructor.prototype);
// inherits(Constructor, Storage);
// dictUpdate(Constructor.prototype, proto);
defaults.storage_types[type] = Constructor;
}
defineConstant(exports, 'addStorage', addStorage);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global */
/**
* A class that acts like localStorage on a simple object.
*
* Like localStorage, the object will contain only strings.
*
* @class Workspace
* @constructor
*/
function Workspace(object) {
this._object = object;
}
// // Too dangerous, never use it
// /**
// * Empty the entire space.
// *
// * @method clear
// */
// Workspace.prototype.clear = function () {
// var k;
// for (k in this._object) {
// if (this._object.hasOwnProperty(k)) {
// delete this._object;
// }
// }
// return undefined;
// };
/**
* Get an item from the space. If the value does not exists, it returns
* null. Else, it returns the string value.
*
* @method getItem
* @param {String} key The location where to get the item
* @return {String} The item
*/
Workspace.prototype.getItem = function (key) {
return this._object[key] === undefined ? null : this._object[key];
};
/**
* Set an item into the space. The value to store is converted to string before.
*
* @method setItem
* @param {String} key The location where to set the item
* @param {Any} value The value to store
*/
Workspace.prototype.setItem = function (key, value) {
if (value === undefined) {
this._object[key] = 'undefined';
} else if (value === null) {
this._object[key] = 'null';
} else {
this._object[key] = value.toString();
}
return undefined;
};
/**
* Removes an item from the space.
*
* @method removeItem
* @param {String} key The location where to remove the item
*/
Workspace.prototype.removeItem = function (key) {
delete this._object[key];
return undefined;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
var defaults = {}, constants = {};
defaults.storage_types = {};
constants.dcmi_types = {
'Collection': 'Collection',
'Dataset': 'Dataset',
'Event': 'Event',
'Image': 'Image',
'InteractiveResource': 'InteractiveResource',
'MovingImage': 'MovingImage',
'PhysicalObject': 'PhysicalObject',
'Service': 'Service',
'Software': 'Software',
'Sound': 'Sound',
'StillImage': 'StillImage',
'Text': 'Text'
};
// if (dcmi_types.Collection === 'Collection') { is a DCMI type }
// if (typeof dcmi_types[name] === 'string') { is a DCMI type }
constants.http_status_text = {
"550": "Internal JIO Error",
"551": "Internal Storage Error",
"Internal JIO Error": "Internal JIO Error",
"Internal Storage Error": "Internal Storage Error",
"internal_jio_error": "Internal JIO Error",
"internal_storage_error": "Internal Storage Error",
"200": "Ok",
"201": "Created",
"204": "No Content",
"205": "Reset Content",
"400": "Bad Request",
"401": "Unauthorized",
"402": "Payment Required",
"403": "Forbidden",
"404": "Not Found",
"405": "Method Not Allowed",
"406": "Not Acceptable",
"407": "Proxy Authentication Required",
"408": "Request Timeout",
"409": "Conflict",
"410": "Gone",
"411": "Length Required",
"412": "Precondition Failed",
"413": "Request Entity Too Large",
"414": "Request-URI Too Long",
"415": "Unsupported Media Type",
"416": "Requested Range Not Satisfiable",
"417": "Expectation Failed",
"418": "I'm a teapot",
"419": "Authentication Timeout",
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"507": "Insufficient Storage",
"Ok": "Ok",
"Created": "Created",
"No Content": "No Content",
"Reset Content": "Reset Content",
"Bad Request": "Bad Request",
"Unauthorized": "Unauthorized",
"Payment Required": "Payment Required",
"Forbidden": "Forbidden",
"Not Found": "Not Found",
"Method Not Allowed": "Method Not Allowed",
"Not Acceptable": "Not Acceptable",
"Proxy Authentication Required": "Proxy Authentication Required",
"Request Timeout": "Request Timeout",
"Conflict": "Conflict",
"Gone": "Gone",
"Length Required": "Length Required",
"Precondition Failed": "Precondition Failed",
"Request Entity Too Large": "Request Entity Too Large",
"Request-URI Too Long": "Request-URI Too Long",
"Unsupported Media Type": "Unsupported Media Type",
"Requested Range Not Satisfiable": "Requested Range Not Satisfiable",
"Expectation Failed": "Expectation Failed",
"I'm a teapot": "I'm a teapot",
"Authentication Timeout": "Authentication Timeout",
"Internal Server Error": "Internal Server Error",
"Not Implemented": "Not Implemented",
"Bad Gateway": "Bad Gateway",
"Service Unavailable": "Service Unavailable",
"Gateway Timeout": "Gateway Timeout",
"Insufficient Storage": "Insufficient Storage",
"ok": "Ok",
"created": "Created",
"no_content": "No Content",
"reset_content": "Reset Content",
"bad_request": "Bad Request",
"unauthorized": "Unauthorized",
"payment_required": "Payment Required",
"forbidden": "Forbidden",
"not_found": "Not Found",
"method_not_allowed": "Method Not Allowed",
"not_acceptable": "Not Acceptable",
"proxy_authentication_required": "Proxy Authentication Required",
"request_timeout": "Request Timeout",
"conflict": "Conflict",
"gone": "Gone",
"length_required": "Length Required",
"precondition_failed": "Precondition Failed",
"request_entity_too_large": "Request Entity Too Large",
"request-uri_too_long": "Request-URI Too Long",
"unsupported_media_type": "Unsupported Media Type",
"requested_range_not_satisfiable": "Requested Range Not Satisfiable",
"expectation_failed": "Expectation Failed",
"im_a_teapot": "I'm a teapot",
"authentication_timeout": "Authentication Timeout",
"internal_server_error": "Internal Server Error",
"not_implemented": "Not Implemented",
"bad_gateway": "Bad Gateway",
"service_unavailable": "Service Unavailable",
"gateway_timeout": "Gateway Timeout",
"insufficient_storage": "Insufficient Storage"
};
constants.http_status = {
"550": 550,
"551": 551,
"Internal JIO Error": 550,
"Internal Storage Error": 551,
"internal_jio_error": 550,
"internal_storage_error": 551,
"200": 200,
"201": 201,
"204": 204,
"205": 205,
"400": 400,
"401": 401,
"402": 402,
"403": 403,
"404": 404,
"405": 405,
"406": 406,
"407": 407,
"408": 408,
"409": 409,
"410": 410,
"411": 411,
"412": 412,
"413": 413,
"414": 414,
"415": 415,
"416": 416,
"417": 417,
"418": 418,
"419": 419,
"500": 500,
"501": 501,
"502": 502,
"503": 503,
"504": 504,
"507": 507,
"Ok": 200,
"Created": 201,
"No Content": 204,
"Reset Content": 205,
"Bad Request": 400,
"Unauthorized": 401,
"Payment Required": 402,
"Forbidden": 403,
"Not Found": 404,
"Method Not Allowed": 405,
"Not Acceptable": 406,
"Proxy Authentication Required": 407,
"Request Timeout": 408,
"Conflict": 409,
"Gone": 410,
"Length Required": 411,
"Precondition Failed": 412,
"Request Entity Too Large": 413,
"Request-URI Too Long": 414,
"Unsupported Media Type": 415,
"Requested Range Not Satisfiable": 416,
"Expectation Failed": 417,
"I'm a teapot": 418,
"Authentication Timeout": 419,
"Internal Server Error": 500,
"Not Implemented": 501,
"Bad Gateway": 502,
"Service Unavailable": 503,
"Gateway Timeout": 504,
"Insufficient Storage": 507,
"ok": 200,
"created": 201,
"no_content": 204,
"reset_content": 205,
"bad_request": 400,
"unauthorized": 401,
"payment_required": 402,
"forbidden": 403,
"not_found": 404,
"method_not_allowed": 405,
"not_acceptable": 406,
"proxy_authentication_required": 407,
"request_timeout": 408,
"conflict": 409,
"gone": 410,
"length_required": 411,
"precondition_failed": 412,
"request_entity_too_large": 413,
"request-uri_too_long": 414,
"unsupported_media_type": 415,
"requested_range_not_satisfiable": 416,
"expectation_failed": 417,
"im_a_teapot": 418,
"authentication_timeout": 419,
"internal_server_error": 500,
"not_implemented": 501,
"bad_gateway": 502,
"service_unavailable": 503,
"gateway_timeout": 504,
"insufficient_storage": 507
};
constants.http_action = {
"550": "error",
"551": "error",
"Internal JIO Error": "error",
"Internal Storage Error": "error",
"internal_jio_error": "error",
"internal_storage_error": "error",
"200": "success",
"201": "success",
"204": "success",
"205": "success",
"400": "error",
"401": "error",
"402": "error",
"403": "error",
"404": "error",
"405": "error",
"406": "error",
"407": "error",
"408": "error",
"409": "error",
"410": "error",
"411": "error",
"412": "error",
"413": "error",
"414": "error",
"415": "error",
"416": "error",
"417": "error",
"418": "error",
"419": "retry",
"500": "retry",
"501": "error",
"502": "error",
"503": "retry",
"504": "retry",
"507": "error",
"Ok": "success",
"Created": "success",
"No Content": "success",
"Reset Content": "success",
"Bad Request": "error",
"Unauthorized": "error",
"Payment Required": "error",
"Forbidden": "error",
"Not Found": "error",
"Method Not Allowed": "error",
"Not Acceptable": "error",
"Proxy Authentication Required": "error",
"Request Timeout": "error",
"Conflict": "error",
"Gone": "error",
"Length Required": "error",
"Precondition Failed": "error",
"Request Entity Too Large": "error",
"Request-URI Too Long": "error",
"Unsupported Media Type": "error",
"Requested Range Not Satisfiable": "error",
"Expectation Failed": "error",
"I'm a teapot": "error",
"Authentication Timeout": "retry",
"Internal Server Error": "retry",
"Not Implemented": "error",
"Bad Gateway": "error",
"Service Unavailable": "retry",
"Gateway Timeout": "retry",
"Insufficient Storage": "error",
"ok": "success",
"created": "success",
"no_content": "success",
"reset_content": "success",
"bad_request": "error",
"unauthorized": "error",
"payment_required": "error",
"forbidden": "error",
"not_found": "error",
"method_not_allowed": "error",
"not_acceptable": "error",
"proxy_authentication_required": "error",
"request_timeout": "error",
"conflict": "error",
"gone": "error",
"length_required": "error",
"precondition_failed": "error",
"request_entity_too_large": "error",
"request-uri_too_long": "error",
"unsupported_media_type": "error",
"requested_range_not_satisfiable": "error",
"expectation_failed": "error",
"im_a_teapot": "error",
"authentication_timeout": "retry",
"internal_server_error": "retry",
"not_implemented": "error",
"bad_gateway": "error",
"service_unavailable": "retry",
"gateway_timeout": "retry",
"insufficient_storage": "error"
};
constants.content_type_re =
/^([a-z]+\/[a-zA-Z0-9\+\-\.]+)(?:\s*;\s*charset\s*=\s*([a-zA-Z0-9\-]+))?$/;
constants.emptyFunction = function () {
return;
};
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global exports, Blob, FileReader, Deferred, hex_sha256 */
/**
* Do not exports these tools unless they are not writable, not configurable.
*/
exports.util = {};
/**
* Clones jsonable object in deep
*
* @param {A} object The jsonable object to clone
* @return {A} The cloned object
*/
function jsonDeepClone(object) {
var tmp = JSON.stringify(object);
if (tmp === undefined) {
return undefined;
}
return JSON.parse(tmp);
}
exports.util.jsonDeepClone = jsonDeepClone;
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, Function, null.
*
* It can also clone object which are serializable, like Date.
*
* To make a class serializable, you need to implement the `toJSON` function
* which returns a JSON representation of the object. The return value is used
* as first parameter of the object constructor.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (object === null) {
return null;
}
if (typeof object === 'object') {
if (Object.getPrototypeOf(object) === Object.prototype) {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
if (object instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is a invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. In
// browsers, give `null` as parameter to `new Date()` doesn't return an
// invalid date.
return new Date(object);
}
// clone serializable objects
if (typeof object.toJSON === 'function') {
return new (Object.getPrototypeOf(object).constructor)(object.toJSON());
}
// cannot clone
return object;
}
return object;
}
exports.util.deepClone = deepClone;
/**
* Update a dictionnary by adding/replacing key values from another dict.
* Enumerable values equal to undefined are also used.
*
* @param {Object} original The dict to update
* @param {Object} other The other dict
* @return {Object} The updated original dict
*/
function dictUpdate(original, other) {
var k;
for (k in other) {
if (other.hasOwnProperty(k)) {
original[k] = other[k];
}
}
return original;
}
exports.util.dictUpdate = dictUpdate;
/**
* Like 'dict.clear()' in python. Delete all dict entries.
*
* @method dictClear
* @param {Object} self The dict to clear
*/
function dictClear(dict) {
var i;
for (i in dict) {
if (dict.hasOwnProperty(i)) {
delete dict[i];
// dictClear(dict);
// break;
}
}
}
exports.util.dictClear = dictClear;
/**
* Filter a dict to keep only values which keys are in `keys` list.
*
* @param {Object} dict The dict to filter
* @param {Array} keys The key list to keep
*/
function dictFilter(dict, keys) {
var i, buffer = [];
for (i = 0; i < keys.length; i += 1) {
buffer[i] = dict[keys[i]];
}
dictClear(dict);
for (i = 0; i < buffer.length; i += 1) {
dict[keys[i]] = buffer[i];
}
}
exports.util.dictFilter = dictFilter;
/**
* A faster version of `array.indexOf(value)` -> `indexOf(value, array)`
*
* @param {Any} value The value to search for
* @param {Array} array The array to browse
* @return {Number} index of value, -1 otherwise
*/
function indexOf(value, array) {
var i;
for (i = 0; i < array.length; i += 1) {
if (array[i] === value) {
return i;
}
}
return -1;
}
exports.util.indexOf = indexOf;
/**
* Gets all elements of an array and classifies them in a dict of array.
* Dict keys are element types, and values are list of element of type 'key'.
*
* @param {Array} array The array of elements to pop
* @return {Object} The type dict
*/
function arrayValuesToTypeDict(array) {
var i, type, types = {};
for (i = 0; i < array.length; i += 1) {
type = Array.isArray(array[i]) ? 'array' : typeof array[i];
if (!types[type]) {
types[type] = [array[i]];
} else {
types[type][types[type].length] = array[i];
}
}
return types;
}
/**
* An Universal Unique ID generator
*
* @return {String} The new UUID.
*/
function generateUuid() {
function S4() {
return ('0000' + Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16)).slice(-4);
}
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
}
exports.util.generateUuid = generateUuid;
/**
* Returns the number with the lowest value
*
* @param {Number} *values The values to compare
* @return {Number} The minimum
*/
function min() {
var i, val;
for (i = 1; i < arguments.length; i += 1) {
if (val === undefined || val > arguments[i]) {
val = arguments[i];
}
}
return val;
}
exports.util.min = min;
/**
* Returns the number with the greatest value
*
* @param {Number} *values The values to compare
* @return {Number} The maximum
*/
function max() {
var i, val;
for (i = 1; i < arguments.length; i += 1) {
if (val === undefined || val < arguments[i]) {
val = arguments[i];
}
}
return val;
}
exports.util.max = max;
/**
* JSON stringify a value. Dict keys are sorted in order to make a kind of
* deepEqual thanks to a simple strict equal string comparison.
*
* JSON.stringify({"a": "b", "c": "d"}) ===
* JSON.stringify({"c": "d", "a": "b"}) // false
*
* deepEqual({"a": "b", "c": "d"}, {"c": "d", "a": "b"}); // true
*
* uniqueJSONStringify({"a": "b", "c": "d"}) ===
* uniqueJSONStringify({"c": "d", "a": "b"}) // true
*
* @param {Any} value The value to stringify
* @param {Function} [replacer] A function to replace values during parse
*/
function uniqueJSONStringify(value, replacer) {
function subStringify(value, key) {
var i, res;
if (typeof replacer === 'function') {
value = replacer(key, value);
}
if (Array.isArray(value)) {
res = [];
for (i = 0; i < value.length; i += 1) {
res[res.length] = subStringify(value[i], i);
if (res[res.length - 1] === undefined) {
res[res.length - 1] = 'null';
}
}
return '[' + res.join(',') + ']';
}
if (typeof value === 'object' && value !== null &&
typeof value.toJSON !== 'function') {
res = [];
for (i in value) {
if (value.hasOwnProperty(i)) {
res[res.length] = subStringify(value[i], i);
if (res[res.length - 1] !== undefined) {
res[res.length - 1] = JSON.stringify(i) + ":" + res[res.length - 1];
} else {
res.length -= 1;
}
}
}
res.sort();
return '{' + res.join(',') + '}';
}
return JSON.stringify(value);
}
return subStringify(value, '');
}
exports.util.uniqueJSONStringify = uniqueJSONStringify;
function makeBlobDigest(blob) {
var deferred = new Deferred(), fr = new FileReader();
fr.onload = function () {
deferred.resolve('sha256-' + hex_sha256(fr.result));
};
fr.onerror = function () {
deferred.reject(); // XXX
};
fr.readAsBinaryString(blob);
return deferred.promise();
}
exports.util.makeBlobDigest = makeBlobDigest;
function makeBinaryStringDigest(string) {
return 'sha256-' + hex_sha256(string);
}
exports.util.makeBinaryStringDigest = makeBinaryStringDigest;
function blobAsBinaryString(blob) {
var deferred = new Deferred(), fr = new FileReader();
fr.onload = function () {
deferred.resolve(fr.result);
};
fr.onerror = function () {
deferred.reject(); // XXX
};
fr.readAsBinaryString(blob);
return deferred.promise();
}
exports.util.blobAsBinaryString = blobAsBinaryString;
/**
* Acts like `Array.prototype.concat` but does not create a copy of the original
* array. It extends the original array and return it.
*
* @param {Array} array The array to extend
* @param {Any} [args*] Values to add in the array
* @return {Array} The original array
*/
function arrayExtend(array) { // args*
var i, j;
for (i = 1; i < arguments.length; i += 1) {
if (Array.isArray(arguments[i])) {
for (j = 0; j < arguments[i].length; j += 1) {
array[array.length] = arguments[i][j];
}
} else {
array[array.length] = arguments[i];
}
}
return array;
}
exports.util.arrayExtend = arrayExtend;
/*jslint indent:2, maxlen: 80, sloppy: true */
var jioException = function (spec, my) {
var that = {};
spec = spec || {};
my = my || {};
that.name = 'jioException';
that.message = spec.message || 'Unknown Reason.';
that.toString = function () {
return that.name + ': ' + that.message;
};
return that;
};
var invalidCommandState = function (spec, my) {
var that = jioException(spec, my), command = spec.command;
spec = spec || {};
that.name = 'invalidCommandState';
that.toString = function () {
return that.name + ': ' +
command.getLabel() + ', ' + that.message;
};
return that;
};
var invalidStorage = function (spec, my) {
var that = jioException(spec, my), type = spec.storage.getType();
spec = spec || {};
that.name = 'invalidStorage';
that.toString = function () {
return that.name + ': ' +
'Type "' + type + '", ' + that.message;
};
return that;
};
var invalidStorageType = function (spec, my) {
var that = jioException(spec, my), type = spec.type;
that.name = 'invalidStorageType';
that.toString = function () {
return that.name + ': ' +
type + ', ' + that.message;
};
return that;
};
var jobNotReadyException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'jobNotReadyException';
return that;
};
var tooMuchTriesJobException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'tooMuchTriesJobException';
return that;
};
var invalidJobException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'invalidJobException';
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global setTimeout, Job, createStorage, deepClone, IODeferred, min */
function enableJobExecuter(jio, shared) { // , options) {
// uses 'job', 'jobDone', 'jobFail' and 'jobNotify' events
// emits 'jobRun' and 'jobEnd' events
// listeners
shared.on('job', function (param) {
var storage;
if (param.state === 'ready') {
param.tried += 1;
param.started = new Date();
param.state = 'running';
param.modified = new Date();
shared.emit('jobRun', param);
try {
storage = createStorage(deepClone(param.storage_spec));
} catch (e) {
return param.command.reject(
'internal_storage_error',
'invalid description',
'Check if the storage description respects the ' +
'constraints provided by the storage designer. (' +
e.name + ": " + e.message + ')'
);
}
if (typeof storage[param.method] !== 'function') {
return param.command.reject(
'not_implemented',
'method missing',
'Storage "' + param.storage_spec.type + '", "' +
param.method + '" method is missing.'
);
}
setTimeout(function () {
storage[param.method](
deepClone(param.command),
deepClone(param.kwargs),
deepClone(param.options)
);
});
}
});
shared.on('jobDone', function (param, args) {
var d;
if (param.state === 'running') {
param.state = 'done';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.deferred) {
d = IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
d.resolve.apply(d, args);
}
}
});
shared.on('jobFail', function (param, args) {
var d;
if (param.state === 'running') {
param.state = 'fail';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.deferred) {
d = IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
d.reject.apply(d, args);
}
}
});
shared.on('jobNotify', function (param, args) {
if (param.state === 'running' && param.deferred) {
param.deferred.notify.apply(param.deferred, args);
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend */
function enableJobMaker(jio, shared, options) {
// dependencies
// - param.method
// - param.storage_spec
// - param.kwargs
// - param.options
// uses (Job)
// - param.created date
// - param.modified date
// - param.tried number >= 0
// - param.state string 'ready'
// - param.method string
// - param.storage_spec object
// - param.kwargs object
// - param.options object
// - param.command object
// uses method events
// add emits 'job' events
// the job can emit 'jobDone', 'jobFail' and 'jobNotify'
shared.job_keys = arrayExtend(shared.job_keys || [], [
"created",
"modified",
"tried",
"state",
"method",
"storage_spec",
"kwargs",
"options"
]);
// listeners
shared.rest_method_names.forEach(function (method) {
shared.on(method, function (param) {
if (param.deferred) {
// params are good
param.created = new Date();
param.tried = 0;
param.state = 'ready';
param.command = {};
param.command.resolve = function () {
shared.emit('jobDone', param, arguments);
};
param.command.success = param.command.resolve;
param.command.reject = function () {
shared.emit('jobFail', param, arguments);
};
param.command.error = param.command.reject;
param.command.notify = function () {
shared.emit('jobNotify', param, arguments);
};
param.command.storage = function () {
if (param.state === 'running') {
shared.createRestApi.apply(null, arguments);
}
};
param.modified = new Date();
shared.emit('job', param);
}
});
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, localStorage, Workspace, uniqueJSONStringify, JobQueue,
JobWorkspace, constants */
function enableJobQueue(jio, shared, options) {
// dependencies
// - shared.storage_spec Object
// uses
// - options.workspace Workspace
// - shared.job_keys String Array
// creates
// - shared.storage_spec_str String
// - shared.workspace Workspace
// - shared.job_queue JobWorkspace
// uses 'job', 'jobRun', 'jobStop', 'jobEnd' events
// emits 'jobEnd' events
if (options.job_management !== false) {
shared.job_keys = arrayExtend(shared.job_keys || [], ["id"]);
if (typeof options.workspace !== 'object') {
shared.workspace = localStorage;
} else {
shared.workspace = new Workspace(options.workspace);
}
if (!shared.storage_spec_str) {
shared.storage_spec_str = uniqueJSONStringify(shared.storage_spec);
}
shared.job_queue = new JobWorkspace(
shared.workspace,
new JobQueue([]),
'jio/jobs/' + shared.storage_spec_str,
shared.job_keys
);
shared.on('job', function (param) {
if (!param.stored) {
shared.job_queue.load();
shared.job_queue.post(param);
shared.job_queue.save();
param.stored = true;
}
});
['jobRun', 'jobStop'].forEach(function (event) {
shared.on(event, function (param) {
if (param.stored) {
shared.job_queue.load();
if (param.state === 'done' || param.state === 'fail') {
if (shared.job_queue.remove(param.id)) {
shared.job_queue.save();
delete param.storad;
}
} else {
shared.job_queue.put(param);
shared.job_queue.save();
}
}
});
});
shared.on('jobEnd', function (param) {
if (param.stored) {
shared.job_queue.load();
if (shared.job_queue.remove(param.id)) {
shared.job_queue.save();
}
}
});
}
shared.on('job', function (param) {
if (!param.command.end) {
param.command.end = function () {
shared.emit('jobEnd', param);
};
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, setTimeout, indexOf, min */
function enableJobRetry(jio, shared, options) {
// dependencies
// - param.method
// - param.storage_spec
// - param.kwargs
// - param.options
// - param.command
// uses
// - param.modified date
// - param.tried number >= 0
// - param.max_retry >= 0 or undefined
// - param.state string 'ready' 'waiting'
// - param.method string
// - param.storage_spec object
// - param.kwargs object
// - param.options object
// - param.command object
// uses 'job' and 'jobRetry' events
// emits 'job', 'jobFail' and 'jobStateChange' events
// job can emit 'jobRetry'
shared.job_keys = arrayExtend(shared.job_keys || [], ["max_retry"]);
function defaultMaxRetry(param) {
if (
indexOf(param.method, [
'post',
'put',
'remove',
'putAttachment',
'removeAttachment',
'repair'
]) !== -1
) {
return 0;
}
return 3;
}
function positiveNumberOrDefault(number, default_value) {
return (typeof number === 'number' &&
number >= 0 ?
number : default_value);
}
// listeners
shared.on('job', function (param) {
if (typeof param.max_retry !== 'number' || param.max_retry < 0) {
param.max_retry = positiveNumberOrDefault(
param.options.max_retry,
defaultMaxRetry(param)
);
}
param.command.retry = function () {
shared.emit('jobRetry', param, arguments);
};
});
shared.on('jobRetry', function (param, args) {
if (param.state === 'running') {
if (param.max_retry === undefined ||
param.max_retry === 0 ||
param.max_retry > param.tried) {
param.state = 'waiting';
param.modified = new Date();
shared.emit('jobStateChange', param);
setTimeout(function () {
param.state = 'ready';
param.modified = new Date();
shared.emit('job', param);
}, min(10000, param.tried * 2000));
} else {
shared.emit('jobFail', param, args);
}
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayExtend, setTimeout, clearTimeout */
function enableJobTimeout(jio, shared, options) {
// dependencies
// - param.tried number > 0
// - param.state string 'running'
// uses
// - param.tried number > 0
// - param.timeout number >= 0
// - param.timeout_ident Timeout
// - param.state string 'running'
// uses 'job', 'jobDone', 'jobFail', 'jobRetry' and 'jobNotify' events
shared.job_keys = arrayExtend(shared.job_keys || [], ["timeout"]);
function positiveNumberOrDefault(number, default_value) {
return (typeof number === 'number' &&
number >= 0 ?
number : default_value);
}
// 10 seconds by default
var default_timeout = positiveNumberOrDefault(options.default_timeout, 10000);
function timeoutReject(param) {
return function () {
param.command.reject(
'request_timeout',
'timeout',
'Operation canceled after around ' + (
Date.now() - param.modified.getTime()
) + ' milliseconds of inactivity.'
);
};
}
// listeners
shared.on('job', function (param) {
if (typeof param.timeout !== 'number' || param.timeout < 0) {
param.timeout = positiveNumberOrDefault(
param.options.timeout,
default_timeout
);
}
param.modified = new Date();
});
["jobDone", "jobFail", "jobRetry"].forEach(function (event) {
shared.on(event, function (param) {
clearTimeout(param.timeout_ident);
delete param.timeout_ident;
});
});
["jobRun", "jobNotify", "jobEnd"].forEach(function (event) {
shared.on(event, function (param) {
clearTimeout(param.timeout_ident);
if (param.state === 'running' && param.timeout > 0) {
param.timeout_ident = setTimeout(timeoutReject(param), param.timeout);
param.modified = new Date();
} else {
delete param.timeout_ident;
}
});
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global arrayValuesToTypeDict, dictClear, Deferred, deepClone */
// adds methods to JIO
// - post
// - put
// - get
// - remove
// - allDocs
// - putAttachment
// - getAttachment
// - removeAttachment
// - check
// - repair
// event shared objet
// - storage_spec object
// - method string
// - kwargs object
// - options object
// - command object
function enableRestAPI(jio, shared) { // (jio, shared, options)
shared.rest_method_names = [
"post",
"put",
"get",
"remove",
"allDocs",
"putAttachment",
"getAttachment",
"removeAttachment",
"check",
"repair"
];
function prepareParamAndEmit(method, storage_spec, args) {
var promise, callback, type_dict, param = {};
type_dict = arrayValuesToTypeDict(Array.prototype.slice.call(args));
type_dict.object = type_dict.object || [];
if (method !== 'allDocs') {
param.kwargs = type_dict.object.shift();
if (param.kwargs === undefined) {
throw new TypeError("JIO()." + method +
"(): Argument 1 is not of type 'object'");
}
param.kwargs = deepClone(param.kwargs);
} else {
param.kwargs = {};
}
param.options = deepClone(type_dict.object.shift()) || {};
//param.deferred = new IODeferred(method, param.kwargs, param.options);
param.deferred = new Deferred();
promise = param.deferred.promise();
type_dict['function'] = type_dict['function'] || [];
if (type_dict['function'].length === 1) {
callback = type_dict['function'].shift();
promise.done(function (answer) {
callback(undefined, answer);
});
promise.fail(function (answer) {
callback(answer, undefined);
});
} else if (type_dict['function'].length > 1) {
promise.done(type_dict['function'].shift());
promise.fail(type_dict['function'].shift());
if (type_dict['function'].length === 1) {
promise.always(type_dict['function'].shift());
}
}
type_dict = dictClear(type_dict);
param.storage_spec = storage_spec;
param.method = method;
shared.emit(method, param);
return promise;
}
shared.createRestApi = function (storage_spec, that) {
if (that === undefined) {
that = {};
}
shared.rest_method_names.forEach(function (method) {
that[method] = function () {
return prepareParamAndEmit(method, storage_spec, arguments);
};
});
return that;
};
shared.createRestApi(shared.storage_spec, jio);
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global Blob, IODeferred, Metadata */
function enableRestParamChecker(jio, shared) {
// dependencies
// - param.deferred
// - param.kwargs
// checks the kwargs and convert value if necessary
// which is a dict of method to use to announce that
// the command is finished
// tools
function checkId(param) {
if (typeof param.kwargs._id !== 'string' || param.kwargs._id === '') {
IODeferred.createFromParam(param).reject(
'bad_request',
'wrong document id',
'Document id must be a non empty string.'
);
delete param.deferred;
return false;
}
return true;
}
function checkAttachmentId(param) {
if (typeof param.kwargs._attachment !== 'string' ||
param.kwargs._attachment === '') {
IODeferred.createFromParam(param).reject(
'bad_request',
'wrong attachment id',
'Attachment id must be a non empty string.'
);
delete param.deferred;
return false;
}
return true;
}
// listeners
shared.on('post', function (param) {
if (param.kwargs._id !== undefined) {
if (!checkId(param)) {
return;
}
}
new Metadata(param.kwargs).format();
});
[
"put",
"get",
"remove",
"check",
"repair"
].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
return;
}
new Metadata(param.kwargs).format();
});
});
shared.on('putAttachment', function (param) {
if (!checkId(param) || !checkAttachmentId(param)) {
return;
}
if (!(param.kwargs._blob instanceof Blob) &&
typeof param.kwargs._data === 'string') {
param.kwargs._blob = new Blob([param.kwargs._data], {
"type": param.kwargs._content_type || param.kwargs._mimetype
});
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else if (param.kwargs._blob instanceof Blob) {
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else if (param.kwargs._data instanceof Blob) {
param.kwargs._blob = param.kwargs._data;
delete param.kwargs._data;
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else {
IODeferred.createFromParam(param).reject(
'bad_request',
'wrong attachment',
'Attachment information must be like {"_id": document id, ' +
'"_attachment": attachment name, "_data": string, ["_mimetype": ' +
'content type]} or {"_id": document id, "_attachment": ' +
'attachment name, "_blob": Blob}'
);
delete param.deferred;
}
});
[
"getAttachment",
"removeAttachment"
].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
checkAttachmentId(param);
}
});
});
}
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports, require('md5'));
return module(exports);
}
window.jIO = {};
module(window.jIO, {hex_md5: hex_md5});
}(['exports', 'md5'], function (exports, md5) {
"use strict";
module(window.jIO);
}(['exports'], function (exports) {
/**
* Add a secured (write permission denied) property to an object.
*
* @method defineConstant
* @param {Object} object The object to fill
* @param {String} key The object key where to store the property
* @param {Any} value The value to store
*/
function defineConstant(object, key, value) {
Object.defineProperty(object, key, {
"configurable": false,
"enumerable": true,
"writable": false,
"value": value
});
return object;
}
var localstorage, hex_md5 = md5.hex_md5;
if (typeof localStorage !== "undefined") {
localstorage = {
getItem: function (item) {
var value = localStorage.getItem(item);
return value === null ? null : JSON.parse(value);
},
setItem: function (item, value) {
return localStorage.setItem(item, JSON.stringify(value));
},
removeItem: function (item) {
return localStorage.removeItem(item);
},
clone: function () {
return JSON.parse(JSON.stringify(localStorage));
/**
* Secures all enumerable functions from an object, making them
* not configurable, not writable, not enumerable.
*
* @method secureMethods
* @param {Object} object The object to secure
*/
function secureMethods(object) {
var key;
for (key in object) {
if (object.hasOwnProperty(key)) {
if (typeof object[key] === "function") {
Object.defineProperty(object, key, {
"configurable": false,
"enumerable": false,
"writable": false,
"value": object[key]
});
}
}
}
};
} else {
(function () {
var pseudo_localStorage = {};
localstorage = {
getItem: function (item) {
var value = pseudo_localStorage[item];
return value === undefined ? null : JSON.parse(value);
},
setItem: function (item, value) {
pseudo_localStorage[item] = JSON.stringify(value);
},
removeItem: function (item) {
delete pseudo_localStorage[item];
},
clone: function () {
return JSON.parse(JSON.stringify(pseudo_localStorage));
return object;
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super
* one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
};
}());
}
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global spec: true, localstorage: true,
activityUpdater: true, jobManager: true, storage: true,
storage_type_object: true, invalidStorageType: true, jobRules: true,
job: true, postCommand: true, putCommand: true, getCommand:true,
allDocsCommand: true, putAttachmentCommand: true,
getAttachmentCommand: true, removeAttachmentCommand: true,
removeCommand: true, checkCommand: true, repairCommand: true */
// Class jio
var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
spec = spec || {};
// Attributes //
priv.id = null;
priv.storage_spec = spec;
priv.environments = {};
// initialize //
priv.init = function () {
// Initialize the jio id and add the new id to the list
if (priv.id === null) {
var i, jio_id_a =
localstorage.getItem(jio_id_array_name) || [];
priv.id = 1;
for (i = 0; i < jio_id_a.length; i += 1) {
if (jio_id_a[i] >= priv.id) {
priv.id = jio_id_a[i] + 1;
}
}
jio_id_a.push(priv.id);
localstorage.setItem(jio_id_array_name, jio_id_a);
activityUpdater.setId(priv.id);
jobManager.setId(priv.id);
}
};
// Methods //
/**
* Returns a storage from a storage description.
* @method storage
* @param {object} spec The specifications.
* @param {object} my The protected object.
* @param {string} forcetype Force storage type
* @return {object} The storage object.
*/
Object.defineProperty(that, "storage", {
configurable: false,
enumerable: false,
writable: false,
value: function (spec, my, forcetype) {
var spec_str, type;
spec = spec || {};
my = my || {};
my.basicStorage = storage;
spec_str = JSON.stringify(spec);
// environment initialization
priv.environments[spec_str] = priv.environments[spec_str] || {};
my.env = priv.environments[spec_str];
my.storage = that.storage; // NOTE : or proxy storage
type = forcetype || spec.type || 'base';
if (type === 'base') {
return storage(spec, my);
}
if (!storage_type_object[type]) {
throw invalidStorageType({
"type": type,
"message": "Storage does not exists."
});
}
return storage_type_object[type](spec, my);
}
});
jobManager.storage = that.storage;
Object.defineProperty(that, "start", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
priv.init();
activityUpdater.start();
jobManager.start();
}
});
Object.defineProperty(that, "stop", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
jobManager.stop();
}
});
Object.defineProperty(that, "close", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
activityUpdater.stop();
jobManager.stop();
priv.id = null;
}
});
/**
* Returns the jio id.
* @method getId
* @return {number} The jio id.
*/
Object.defineProperty(that, "getId", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return priv.id;
}
});
/**
* Returns the jio job rules object used by the job manager.
* @method getJobRules
* @return {object} The job rules object
*/
Object.defineProperty(that, "getJobRules", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return jobRules;
}
});
/**
* Checks if the storage description is valid or not.
* @method validateStorageDescription
* @param {object} description The description object.
* @return {boolean} true if ok, else false.
*/
Object.defineProperty(that, "validateStorageDescription", {
configurable: false,
enumerable: false,
writable: false,
value: function (description) {
return that.storage(description).isValid();
}
});
Object.defineProperty(that, "getJobArray", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return jobManager.serialized();
}
});
priv.makeCallbacks = function (param, callback1, callback2) {
param.callback = function (err, val) {
if (err) {
param.error(err);
} else {
param.success(val);
}
};
param.success = function (val) {
param.callback(undefined, val);
};
param.error = function (err) {
param.callback(err, undefined);
};
if (typeof callback1 === 'function') {
if (typeof callback2 === 'function') {
param.success = callback1;
param.error = callback2;
} else {
param.callback = callback1;
}
} else {
param.callback = function () {};
}
};
priv.parametersToObject = function (list, default_options) {
var k, i = 0, callbacks = [], param = {"options": {}};
for (i = 0; i < list.length; i += 1) {
if (typeof list[i] === 'object') {
// this is the option
param.options = list[i];
for (k in default_options) {
if ((typeof default_options[k]) !== (typeof list[i][k])) {
param.options[k] = default_options[k];
}
}
}
if (typeof list[i] === 'function') {
// this is a callback
callbacks.push(list[i]);
}
}
priv.makeCallbacks(param, callbacks[0], callbacks[1]);
return param;
};
priv.addJob = function (commandCreator, spec) {
jobManager.addJob(job({
"storage": that.storage(priv.storage_spec),
"command": commandCreator(spec)
}));
};
/**
* Post a document.
* @method post
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id (optional)
* For revision managing: choose at most one of the following informations:
* - {string} _rev The revision we want to update
* - {string} _revs_info The revision information we want the document to have
* - {string} _revs The revision history we want the document to have
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "post", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(postCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put a document.
* @method put
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* For revision managing: choose at most one of the following informations:
* - {string} _rev The revision we want to update
* - {string} _revs_info The revision information we want the document to have
* - {string} _revs The revision history we want the document to have
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "put", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(putCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Get a document.
* @method get
* @param {string} doc The document object. Contains at least:
* - {string} _id The document id
* For revision managing:
* - {string} _rev The revision we want to get. (optional)
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* For revision managing:
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "get", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(getCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Remove a document.
* @method remove
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* For revision managing:
* - {string} _rev The revision we want to remove
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "remove", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 0}
);
priv.addJob(removeCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Get a list of documents.
* @method allDocs
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} include_docs Include document metadata
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "allDocs", {
configurable: false,
enumerable: false,
writable: false,
value: function (options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(allDocsCommand, {
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Get an attachment from a document.
* @method gettAttachment
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* - {string} _attachment The attachment id
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "getAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(getAttachmentCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put an attachment to a document.
* @method putAttachment
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* - {string} _attachment The attachment id
* - {string} _data The attachment data
* - {string} _mimetype The attachment mimetype
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "putAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(putAttachmentCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put an attachment to a document.
* @method putAttachment
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* - {string} _attachment The attachment id
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "removeAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(removeAttachmentCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Check a document.
* @method check
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "check", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(checkCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Repair a document.
* @method repair
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "repair", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(repairCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
var jio = function(spec) {
return that;
}; // End Class jio
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global exports, jio, invalidStorageType */
var storage_type_object = { // -> 'key':constructorFunction
'base': function () { // overriden by jio
return undefined;
}
};
/**
* Creates a new jio instance.
* @method newJio
* @param {object} spec The storage description
* @return {object} The new Jio instance.
*/
Object.defineProperty(exports, "newJio", {
configurable: false,
enumerable: true,
writable: false,
value: function (spec) {
var storage = spec, instance = null;
if (typeof storage === 'string') {
storage = JSON.parse(storage);
} else {
storage = JSON.stringify(storage);
if (storage !== undefined) {
storage = JSON.parse(storage);
}
}
storage = storage || {
type: 'base'
};
instance = jio(storage);
instance.start();
return instance;
}
});
/**
* Add a storage type to jio.
* @method addStorageType
* @param {string} type The storage type
* @param {function} constructor The associated constructor
*/
Object.defineProperty(exports, "addStorageType", {
configurable: false,
enumerable: true,
writable: false,
value: function (type, constructor) {
constructor = constructor || function () {
return null;
};
if (storage_type_object[type]) {
throw invalidStorageType({
type: type,
message: 'Already known.'
});
}
storage_type_object[type] = constructor;
}
});
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobIdHandler: true, initialStatus: true, invalidJobException: true,
waitStatus: true, failStatus: true, tooMuchTriesJobException: true,
jobManager: true, jobNotReadyException: true, onGoingStatus: true */
var job = function (spec) {
var that = {},
priv = {};
spec = spec || {};
priv.id = jobIdHandler.nextId();
priv.command = spec.command;
priv.storage = spec.storage;
priv.status = initialStatus();
priv.date = new Date();
// Initialize //
if (!priv.storage) {
throw invalidJobException({
job: that,
message: 'No storage set'
});
}
if (!priv.command) {
throw invalidJobException({
job: that,
message: 'No command set'
});
}
// Methods //
/**
* Returns the job command.
* @method getCommand
* @return {object} The job command.
*/
that.getCommand = function () {
return priv.command;
};
that.getStatus = function () {
return priv.status;
};
that.getId = function () {
return priv.id;
};
that.getStorage = function () {
return priv.storage;
};
that.getDate = function () {
return priv.date;
};
/**
* Checks if the job is ready.
* @method isReady
* @return {boolean} true if ready, else false.
*/
that.isReady = function () {
if (priv.command.getTried() === 0) {
return priv.status.canStart();
}
return priv.status.canRestart();
};
/**
* Returns a serialized version of this job.
* @method serialized
* @return {object} The serialized job.
*/
that.serialized = function () {
return {
id: priv.id,
date: priv.date.getTime(),
status: priv.status.serialized(),
command: priv.command.serialized(),
storage: priv.storage.serialized()
};
};
/**
* Tells the job to wait for another one.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function (job) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForJob(job);
};
/**
* Tells the job to do not wait for a job.
* @method dontWaitForJob
* @param {object} job The other job.
*/
that.dontWaitFor = function (job) {
if (priv.status.getLabel() === 'wait') {
priv.status.dontWaitForJob(job);
}
};
/**
* Tells the job to wait for a while.
* @method waitForTime
* @param {number} ms Time to wait in millisecond.
*/
that.waitForTime = function (ms) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForTime(ms);
};
/**
* Tells the job to do not wait for a while anymore.
* @method stopWaitForTime
*/
that.stopWaitForTime = function () {
if (priv.status.getLabel() === 'wait') {
priv.status.stopWaitForTime();
}
};
that.eliminated = function () {
priv.command.error({
status: 10,
statusText: 'Stopped',
error: 'stopped',
message: 'This job has been stopped by another one.',
reason: 'this job has been stopped by another one'
});
};
that.notAccepted = function () {
priv.command.onEndDo(function () {
priv.status = failStatus();
jobManager.terminateJob(that);
});
priv.command.error({
status: 11,
statusText: 'Not Accepted',
error: 'not_accepted',
message: 'This job is already running.',
reason: 'this job is already running'
});
};
/**
* Updates the date of the job with the another one.
* @method update
* @param {object} job The other job.
*/
that.update = function (job) {
priv.command.addCallbacks(job.getCommand().onSuccessDo()[0],
job.getCommand().onErrorDo()[0]);
priv.date = new Date(job.getDate().getTime());
};
/**
* Executes this job.
* @method execute
*/
that.execute = function () {
if (!that.getCommand().canBeRetried()) {
throw tooMuchTriesJobException({
job: that,
message: 'The job was invoked too much time.'
});
}
if (!that.isReady()) {
throw jobNotReadyException({
job: that,
message: 'Can not execute this job.'
});
}
priv.status = onGoingStatus();
priv.command.onRetryDo(function () {
var ms = priv.command.getTried();
ms = ms * ms * 200;
if (ms > 10000) {
ms = 10000;
}
that.waitForTime(ms);
});
priv.command.onEndDo(function (status) {
priv.status = status;
jobManager.terminateJob(that);
});
priv.command.execute(priv.storage);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
var jobIdHandler = (function (spec) {
var that = {},
id = 0;
spec = spec || {};
// Methods //
that.nextId = function () {
id = id + 1;
return id;
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localstorage: true, setInterval: true, clearInterval: true,
command: true, job: true, jobRules: true */
var jobManager = (function (spec) {
var that = {},
job_array_name = 'jio/job_array',
priv = {};
spec = spec || {};
// Attributes //
priv.id = spec.id;
priv.interval_id = null;
priv.interval = 200;
priv.job_array = [];
// Methods //
/**
* Get the job array name in the localStorage
* @method getJobArrayName
* @return {string} The job array name
*/
priv.getJobArrayName = function () {
return job_array_name + '/' + priv.id;
};
/**
* Returns the job array from the localStorage
* @method getJobArray
* @return {array} The job array.
*/
priv.getJobArray = function () {
return localstorage.getItem(priv.getJobArrayName()) || [];
};
/**
* Does a backup of the job array in the localStorage.
* @method copyJobArrayToLocal
*/
priv.copyJobArrayToLocal = function () {
var new_a = [],
i;
for (i = 0; i < priv.job_array.length; i += 1) {
new_a.push(priv.job_array[i].serialized());
}
localstorage.setItem(priv.getJobArrayName(), new_a);
};
/**
* Removes a job from the current job array.
* @method removeJob
* @param {object} job The job object.
*/
priv.removeJob = function (job) {
var i,
tmp_job_array = [];
for (i = 0; i < priv.job_array.length; i += 1) {
if (priv.job_array[i] !== job) {
tmp_job_array.push(priv.job_array[i]);
}
}
priv.job_array = tmp_job_array;
priv.copyJobArrayToLocal();
};
/**
* Sets the job manager id.
* @method setId
* @param {number} id The id.
*/
that.setId = function (id) {
priv.id = id;
};
/**
* Starts listening to the job array, executing them regulary.
* @method start
*/
that.start = function () {
var i;
if (priv.interval_id === null) {
priv.interval_id = setInterval(function () {
priv.restoreOldJio();
for (i = 0; i < priv.job_array.length; i += 1) {
that.execute(priv.job_array[i]);
}
}, priv.interval);
}
};
/**
* Stops listening to the job array.
* @method stop
*/
that.stop = function () {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
if (priv.job_array.length === 0) {
localstorage.removeItem(priv.getJobArrayName());
}
}
};
/**
* Try to restore an the inactive older jio instances.
* It will restore the on going or initial jobs from their job array
* and it will add them to this job array.
* @method restoreOldJio
*/
priv.restoreOldJio = function () {
var i,
jio_id_a;
priv.lastrestore = priv.lastrestore || 0;
if (priv.lastrestore > (Date.now()) - 2000) {
return;
}
jio_id_a = localstorage.getItem('jio/id_array') || [];
for (i = 0; i < jio_id_a.length; i += 1) {
priv.restoreOldJioId(jio_id_a[i]);
}
priv.lastrestore = Date.now();
};
/**
* Try to restore an old jio according to an id.
* @method restoreOldJioId
* @param {number} id The jio id.
*/
priv.restoreOldJioId = function (id) {
var jio_date;
jio_date = localstorage.getItem('jio/id/' + id) || 0;
if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec
priv.restoreOldJobFromJioId(id);
priv.removeOldJioId(id);
priv.removeJobArrayFromJioId(id);
}
};
/**
* Try to restore all jobs from another jio according to an id.
* @method restoreOldJobFromJioId
* @param {number} id The jio id.
*/
priv.restoreOldJobFromJioId = function (id) {
var i,
command_object,
jio_job_array;
jio_job_array = localstorage.getItem('jio/job_array/' + id) || [];
for (i = 0; i < jio_job_array.length; i += 1) {
command_object = command(jio_job_array[i].command);
if (command_object.canBeRestored()) {
that.addJob(job({
storage: that.storage(jio_job_array[i].storage),
command: command_object
}));
}
}
};
/**
* Removes a jio instance according to an id.
* @method removeOldJioId
* @param {number} id The jio id.
*/
priv.removeOldJioId = function (id) {
var i,
jio_id_array,
new_array = [];
jio_id_array = localstorage.getItem('jio/id_array') || [];
for (i = 0; i < jio_id_array.length; i += 1) {
if (jio_id_array[i] !== id) {
new_array.push(jio_id_array[i]);
}
}
localstorage.setItem('jio/id_array', new_array);
localstorage.removeItem('jio/id/' + id);
};
/**
* Removes a job array from a jio instance according to an id.
* @method removeJobArrayFromJioId
* @param {number} id The jio id.
*/
priv.removeJobArrayFromJioId = function (id) {
localstorage.removeItem('jio/job_array/' + id);
};
/**
* Executes a job.
* @method execute
* @param {object} job The job object.
*/
that.execute = function (job) {
try {
job.execute();
} catch (e) {
switch (e.name) {
case 'jobNotReadyException':
break; // do nothing
case 'tooMuchTriesJobException':
break; // do nothing
default:
throw e;
}
}
priv.copyJobArrayToLocal();
};
/**
* Checks if a job exists in the job array according to a job id.
* @method jobIdExists
* @param {number} id The job id.
* @return {boolean} true if exists, else false.
*/
that.jobIdExists = function (id) {
var i;
for (i = 0; i < priv.job_array.length; i += 1) {
if (priv.job_array[i].getId() === id) {
return true;
}
}
return false;
};
/**
* Terminate a job. It only remove it from the job array.
* @method terminateJob
* @param {object} job The job object
*/
that.terminateJob = function (job) {
priv.removeJob(job);
};
/**
* Adds a job to the current job array.
* @method addJob
* @param {object} job The new job.
*/
that.addJob = function (job) {
var result_array = that.validateJobAccordingToJobList(priv.job_array, job);
priv.appendJob(job, result_array);
};
/**
* Generate a result array containing action string to do with the good job.
* @method validateJobAccordingToJobList
* @param {array} job_array A job array.
* @param {object} job The new job to compare with.
* @return {array} A result array.
*/
that.validateJobAccordingToJobList = function (job_array, job) {
var i,
result_array = [];
for (i = 0; i < job_array.length; i += 1) {
result_array.push(jobRules.validateJobAccordingToJob(job_array[i], job));
}
return result_array;
};
/**
* It will manage the job in order to know what to do thanks to a result
* array. The new job can be added to the job array, but it can also be
* not accepted. It is this method which can tells jobs to wait for another
* one, to replace one or to eliminate some while browsing.
* @method appendJob
* @param {object} job The job to append.
* @param {array} result_array The result array.
*/
priv.appendJob = function (job, result_array) {
var i;
if (priv.job_array.length !== result_array.length) {
throw new RangeError("Array out of bound");
}
for (i = 0; i < result_array.length; i += 1) {
if (result_array[i].action === 'dont accept') {
return job.notAccepted();
}
}
for (i = 0; i < result_array.length; i += 1) {
switch (result_array[i].action) {
case 'eliminate':
result_array[i].job.eliminated();
priv.removeJob(result_array[i].job);
break;
case 'update':
result_array[i].job.update(job);
priv.copyJobArrayToLocal();
return;
case 'wait':
job.waitForJob(result_array[i].job);
break;
default:
break;
}
}
priv.job_array.push(job);
priv.copyJobArrayToLocal();
};
that.serialized = function () {
var a = [],
i,
job_array = priv.job_array || [];
for (i = 0; i < job_array.length; i += 1) {
a.push(job_array[i].serialized());
}
return a;
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
var jobRules = (function () {
var that = {}, priv = {};
priv.compare = {};
priv.action = {};
Object.defineProperty(that, "eliminate", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'eliminate';
}
});
Object.defineProperty(that, "update", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'update';
}
});
Object.defineProperty(that, "dontAccept", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'dont accept';
}
});
Object.defineProperty(that, "wait", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'wait';
}
});
Object.defineProperty(that, "ok", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'none';
}
});
that.default_action = that.ok;
that.default_compare = function (job1, job2) {
return job1.getId() !== job2.getId() &&
job1.getStatus().getLabel() !== "done" &&
job1.getStatus().getLabel() !== "fail" &&
JSON.stringify(job1.getStorage().serialized()) ===
JSON.stringify(job2.getStorage().serialized());
};
// Compare Functions //
Object.defineProperty(that, "sameDocumentId", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getDocId() === job2.getCommand().getDocId();
}
});
Object.defineProperty(that, "sameRevision", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getDocInfo("_rev") ===
job2.getCommand().getDocInfo("_rev");
}
});
Object.defineProperty(that, "sameAttachmentId", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getAttachmentId() ===
job2.getCommand().getAttachmentId();
}
});
Object.defineProperty(that, "sameDocument", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return JSON.stringify(job1.getCommand().cloneDoc()) ===
JSON.stringify(job2.getCommand().cloneDoc());
}
});
Object.defineProperty(that, "sameOption", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return JSON.stringify(job1.getCommand().cloneOption()) ===
JSON.stringify(job2.getCommand().cloneOption());
}
});
// Methods //
/**
* Returns an action according the jobs given in parameters.
* @method getAction
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {string} An action string.
*/
priv.getAction = function (job1, job2) {
var method1, method2, tmp = priv.action, i, j, condition_list = [], res;
method1 = job1.getCommand().getLabel();
method2 = job2.getCommand().getLabel();
tmp = tmp[method1] = tmp[method1] || {};
tmp = tmp[method2] = tmp[method2] || [];
for (i = 0; i < tmp.length; i += 1) {
// browsing all method1 method2 rules
condition_list = tmp[i].condition_list;
res = true;
for (j = 0; j < condition_list.length; j += 1) {
// test all the rule's conditions
if (!condition_list[j](job1, job2)) {
res = false;
break;
}
}
if (res) {
// if all respects condition list, then action
return tmp[i].rule();
}
}
return that.default_action();
};
/**
* Checks if the two jobs are comparable.
* @method canCompare
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {boolean} true if comparable, else false.
*/
priv.canCompare = function (job1, job2) {
var method1, method2;
method1 = job1.getCommand().getLabel();
method2 = job2.getCommand().getLabel();
if (priv.compare[method1] && priv.compare[method1][method2]) {
return priv.compare[method1][method2](job1, job2);
}
return that.default_compare(job1, job2);
};
/**
* Returns an action string to show what to do if we want to add a job.
* @method validateJobAccordingToJob
* @param {object} job1 The current job.
* @param {object} job2 The new job.
* @return {string} The action string.
*/
Object.defineProperty(that, "validateJobAccordingToJob", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
if (priv.canCompare(job1, job2)) {
return {
action: priv.getAction(job1, job2),
job: job1
};
}
return {
action: that.default_action(job1, job2),
job: job1
};
}
});
/**
* Adds a rule the action rules.
* @method addActionRule
* @param {string} method1 The action label from the current job.
* @param {boolean} ongoing Is this action is on going or not?
* @param {string} method2 The action label from the new job.
* @param {function} rule The rule that return an action string.
*/
Object.defineProperty(that, "addActionRule", {
configurable: false,
enumerable: false,
writable: false,
value: function (method1, method2, condition_list, rule) {
var tmp = priv.action;
tmp = tmp[method1] = tmp[method1] || {};
tmp = tmp[method2] = tmp[method2] || [];
tmp.push({
"condition_list": condition_list,
"rule": rule
});
}
});
/**
* Adds a rule the compare rules.
* @method addCompareRule
* @param {string} method1 The action label from the current job.
* @param {string} method2 The action label from the new job.
* @param {function} rule The rule that return a boolean
* - true if job1 and job2 can be compared, else false.
*/
Object.defineProperty(that, "addCompareRule", {
configurable: false,
enumerable: false,
writable: false,
value: function (method1, method2, rule) {
priv.compare[method1] = priv.compare[method1] || {};
priv.compare[method1][method2] = rule;
}
});
////////////////////////////////////////////////////////////////////////////
// Adding some rules
/*
Rules
original job |job to add |condition |action
post post same doc update
" " same docid, same rev wait
" put " "
" putA " "
" remove " "
" removeA " "
put post same docid, same rev wait
" put same doc update
" " same docid, same rev wait
" putA " "
" remove " "
" removeA " "
putA post same docid, same rev wait
" put " "
" putA same doc update
" " same docid, same rev, same attmt wait
" remove same docid, same rev "
" removeA same docid, same rev, same attmt "
remove post same docid, same rev wait
" put " "
" putA " "
" remove " update
" removeA " wait
removeA post same docid, same rev wait
" put " "
" putA same docid, same rev, same attmt "
" remove same docid, same rev "
" removeA same doc update
" removeA same docid, same rev, same attmt wait
get get same doc, same options update
getA getA same doc, same options update
allDocs allDocs same doc, same options update
*/
that.addActionRule("post", "post", [that.sameDocument], that.update);
that.addActionRule("post", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("post", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("post", "putAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("post", "remove",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("post", "removeAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("put", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("put", "put", [that.sameDocument], that.update);
that.addActionRule("put", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("put", "putAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("put", "remove",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("put", "removeAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("putAttachment", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("putAttachment", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("putAttachment", "putAttachment", [that.sameDocument],
that.update);
that.addActionRule("putAttachment", "putAttachment", [
that.sameDocumentId,
that.sameRevision,
that.sameAttachmentId
], that.wait);
that.addActionRule("putAttachment", "remove",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("putAttachment", "removeAttachment", [
that.sameDocumentId,
that.sameRevision,
that.sameAttachmentId
], that.wait);
that.addActionRule("remove", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("remove", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("remove", "putAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("remove", "remove",
[that.sameDocumentId, that.sameRevision], that.update);
that.addActionRule("remove", "removeAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("removeAttachment", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("removeAttachment", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("removeAttachment", "putAttachment", [
that.sameDocumentId,
that.sameRevision,
that.sameAttachmentId
], that.wait);
that.addActionRule("removeAttachment", "remove",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("removeAttachment", "removeAttachment",
[that.sameDocument], that.update);
that.addActionRule("removeAttachment", "removeAttachment", [
that.sameDocumentId,
that.sameRevision,
that.sameAttachmentId
], that.wait);
that.addActionRule("get", "get",
[that.sameDocument, that.sameOption], that.update);
that.addActionRule("getAttachment", "getAttachment",
[that.sameDocument, that.sameOption], that.update);
that.addActionRule("allDocs", "allDocs",
[that.sameDocument, that.sameOption], that.update);
// end adding rules
////////////////////////////////////////////////////////////////////////////
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var doneStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'done';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return false;
};
that.isDone = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var failStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'fail';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var initialStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return "initial";
};
that.canStart = function () {
return true;
};
that.canRestart = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var jobStatus = function (spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'job status';
};
that.canStart = function () {};
that.canRestart = function () {};
that.serialized = function () {
return {"label": that.getLabel()};
};
that.isWaitStatus = function () {
return false;
};
that.isDone = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var onGoingStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'on going';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true, jobManager: true */
var waitStatus = function (spec, my) {
var that = jobStatus(spec, my), priv = {};
spec = spec || {};
my = my || {};
// Attributes //
priv.job_id_array = spec.job_id_array || [];
priv.threshold = 0;
// Methods //
/**
* Returns the label of this status.
* @method getLabel
* @return {string} The label: 'wait'.
*/
that.getLabel = function () {
return 'wait';
};
/**
* Refresh the job id array to wait.
* @method refreshJobIdArray
*/
priv.refreshJobIdArray = function () {
var tmp_job_id_array = [], i;
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (jobManager.jobIdExists(priv.job_id_array[i])) {
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for the job end before start again.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function (job) {
var i;
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (priv.job_id_array[i] === job.getId()) {
return;
}
}
priv.job_id_array.push(job.getId());
};
/**
* The status stops to wait for this job.
* @method dontWaitForJob
* @param {object} job The job to stop waiting for.
*/
that.dontWaitForJob = function (job) {
var i, tmp_job_id_array = [];
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (priv.job_id_array[i] !== job.getId()) {
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for some milliseconds.
* @method waitForTime
* @param {number} ms The number of milliseconds
*/
that.waitForTime = function (ms) {
priv.threshold = Date.now() + ms;
};
/**
* The status stops to wait for some time.
* @method stopWaitForTime
*/
that.stopWaitForTime = function () {
priv.threshold = 0;
};
that.canStart = function () {
priv.refreshJobIdArray();
return (priv.job_id_array.length === 0 && Date.now() >= priv.threshold);
};
that.canRestart = function () {
return that.canStart();
};
that.serialized = function () {
return {
"label": that.getLabel(),
"waitfortime": priv.threshold,
"waitforjob": priv.job_id_array
};
};
/**
* Checks if this status is waitStatus
* @method isWaitStatus
* @return {boolean} true
*/
that.isWaitStatus = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true, jobManager: true, job: true */
var storage = function (spec, my) {
var that = {}, priv = {};
spec = spec || {};
my = my || {};
// Attributes //
priv.type = spec.type || '';
// Methods //
Object.defineProperty(that, "getType", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return priv.type;
}
});
/**
* Execute the command on this storage.
* @method execute
* @param {object} command The command
*/
that.execute = function (command) {
that.success = command.success;
that.error = command.error;
that.retry = command.retry;
that.end = command.end;
if (that.validate(command)) {
command.executeOn(that);
}
};
/**
* Override this function to validate specifications.
* @method isValid
* @return {boolean} true if ok, else false.
*/
that.isValid = function () {
return true;
};
that.validate = function () {
var mess = that.validateState();
if (mess) {
that.error({
"status": 0,
"statusText": "Invalid Storage",
"error": "invalid_storage",
"message": mess,
"reason": mess
});
return false;
}
return true;
};
/**
* Returns a serialized version of this storage.
* @method serialized
* @return {object} The serialized storage.
*/
that.serialized = function () {
var o = that.specToStore() || {};
o.type = that.getType();
return o;
};
/**
* Returns an object containing spec to store on localStorage, in order to
* be restored later if something wrong happen.
* Override this method!
* @method specToStore
* @return {object} The spec to store
*/
that.specToStore = function () {
return {};
};
/**
* Validate the storage state. It returns a empty string all is ok.
* @method validateState
* @return {string} empty: ok, else error message.
*/
that.validateState = function () {
return '';
};
that.post = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Post\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.put = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Put\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.putAttachment = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"PutAttachment\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.get = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Get\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.allDocs = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"AllDocs\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.remove = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Remove\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.check = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.repair = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.success = function () {};
that.retry = function () {};
that.error = function () {};
that.end = function () {}; // terminate the current job.
priv.newCommand = function (method, spec) {
var o = spec || {};
o.label = method;
return command(o, my);
};
priv.storage = my.storage;
delete my.storage;
that.addJob = function (method, storage_spec, doc, option, success, error) {
var command_opt = {
doc: doc,
options: option,
callbacks: {success: success, error: error}
};
jobManager.addJob(job({
storage: priv.storage(storage_spec || {}),
command: priv.newCommand(method, command_opt)
}, my));
};
return that;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment