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;
This diff is collapsed.
/*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
}
};
}());
}
});
}
This diff is collapsed.
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;
}());
This diff is collapsed.
This diff is collapsed.
/*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;
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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