pax_global_header 0000666 0000000 0000000 00000000064 14230544473 0014520 g ustar 00root root 0000000 0000000 52 comment=d5ae73d6fd57e02f4f95abc5a3556d2dbea5edc9
jio-master-src/ 0000775 0000000 0000000 00000000000 14230544473 0013723 5 ustar 00root root 0000000 0000000 jio-master-src/src/ 0000775 0000000 0000000 00000000000 14230544473 0014512 5 ustar 00root root 0000000 0000000 jio-master-src/src/jio.date/ 0000775 0000000 0000000 00000000000 14230544473 0016207 5 ustar 00root root 0000000 0000000 jio-master-src/src/jio.date/jiodate.js 0000664 0000000 0000000 00000013306 14230544473 0020167 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*global window, moment */
/*jslint nomen: true, maxlen: 200*/
(function (window, moment) {
"use strict";
// /**
// * Add a secured (write permission denied) property to an object.
// *
// * @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 _export(key, value) {
// Object.defineProperty(to_export, key, {
// "configurable": false,
// "enumerable": true,
// "writable": false,
// "value": value
// });
// }
var YEAR = 'year',
MONTH = 'month',
DAY = 'day',
HOUR = 'hour',
MIN = 'minute',
SEC = 'second',
MSEC = 'millisecond',
precision_grade = {
'year': 0,
'month': 1,
'day': 2,
'hour': 3,
'minute': 4,
'second': 5,
'millisecond': 6
},
lesserPrecision = function (p1, p2) {
return (precision_grade[p1] < precision_grade[p2]) ? p1 : p2;
},
JIODate;
JIODate = function (str) {
// in case of forgotten 'new'
if (!(this instanceof JIODate)) {
return new JIODate(str);
}
if (str instanceof JIODate) {
this.mom = str.mom.clone();
this._precision = str._precision;
return;
}
if (str === undefined) {
this.mom = moment();
this.setPrecision(MSEC);
return;
}
this.mom = null;
this._str = str;
// http://www.w3.org/TR/NOTE-datetime
// http://dotat.at/tmp/ISO_8601-2004_E.pdf
// XXX these regexps fail to detect many invalid dates.
if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+\-][0-2]\d:[0-5]\d|Z)/)
|| str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/)) {
// ISO, milliseconds
this.mom = moment(str);
this.setPrecision(MSEC);
} else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
|| str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/)) {
// ISO, seconds
this.mom = moment(str);
this.setPrecision(SEC);
} else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
|| str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d/)) {
// ISO, minutes
this.mom = moment(str);
this.setPrecision(MIN);
} else if (str.match(/\d\d\d\d-\d\d-\d\d \d\d/)) {
this.mom = moment(str);
this.setPrecision(HOUR);
} else if (str.match(/\d\d\d\d-\d\d-\d\d/)) {
this.mom = moment(str);
this.setPrecision(DAY);
} else if (str.match(/\d\d\d\d-\d\d/)) {
this.mom = moment(str);
this.setPrecision(MONTH);
} else if (str.match(/\d\d\d\d/)) {
// Creating a moment with only the year will show this deprecation
// warning:
//
// Deprecation warning: moment construction falls back to js Date. This is
// discouraged and will be removed in upcoming major release. Please refer
// to https://github.com/moment/moment/issues/1407 for more info.
//
// TL;DR: parsing year-only strings with momentjs falls back to native
// Date and it won't correctly represent the year in local time if UTF
// offset is negative.
//
// The solution is to use the format parameter, so momentjs won't fall
// back to the native Date and we will have the correct year in local
// time.
//
this.mom = moment(str, 'YYYY');
this.setPrecision(YEAR);
}
if (!this.mom) {
throw new Error("Cannot parse: " + str);
}
};
JIODate.prototype.setPrecision = function (prec) {
this._precision = prec;
};
JIODate.prototype.getPrecision = function () {
return this._precision;
};
JIODate.prototype.cmp = function (other) {
var m1 = this.mom,
m2 = other.mom,
p = lesserPrecision(this._precision, other._precision);
return m1.isBefore(m2, p) ? -1 : (m1.isSame(m2, p) ? 0 : +1);
};
JIODate.prototype.toPrecisionString = function (precision) {
var fmt;
precision = precision || this._precision;
fmt = {
'millisecond': 'YYYY-MM-DD HH:mm:ss.SSS',
'second': 'YYYY-MM-DD HH:mm:ss',
'minute': 'YYYY-MM-DD HH:mm',
'hour': 'YYYY-MM-DD HH',
'day': 'YYYY-MM-DD',
'month': 'YYYY-MM',
'year': 'YYYY'
}[precision];
if (!fmt) {
throw new TypeError("Unsupported precision value '" + precision + "'");
}
return this.mom.format(fmt);
};
JIODate.prototype.toString = function () {
return this._str;
};
// _export('JIODate', JIODate);
//
// _export('YEAR', YEAR);
// _export('MONTH', MONTH);
// _export('DAY', DAY);
// _export('HOUR', HOUR);
// _export('MIN', MIN);
// _export('SEC', SEC);
// _export('MSEC', MSEC);
window.jiodate = {
JIODate: JIODate,
YEAR: YEAR,
MONTH: MONTH,
DAY: DAY,
HOUR: HOUR,
MIN: MIN,
SEC: SEC,
MSEC: MSEC
};
}(window, moment));
jio-master-src/src/jio.js 0000664 0000000 0000000 00000043000 14230544473 0015626 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*global window, RSVP, Blob, XMLHttpRequest, QueryFactory, Query, atob,
FileReader, ArrayBuffer, Uint8Array */
(function (window, RSVP, Blob, QueryFactory, Query, atob,
FileReader, ArrayBuffer, Uint8Array) {
"use strict";
/* Safari does not define DOMError */
/* + compat issue with private Firefox */
if (window.DOMError === undefined) {
window.DOMError = function FakeDOMError(message) {
this.message = message;
};
}
var util = {},
jIO;
function jIOError(message, status_code) {
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
this.status_code = status_code || 500;
}
jIOError.prototype = new Error();
jIOError.prototype.constructor = jIOError;
util.jIOError = jIOError;
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Number} param.timeout The request timeout value
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.responseType = param.dataType || "";
if (typeof param.headers === 'object' && param.headers !== null) {
for (k in param.headers) {
if (param.headers.hasOwnProperty(k)) {
xhr.setRequestHeader(k, param.headers[k]);
}
}
}
xhr.addEventListener("load", function (e) {
if (e.target.status >= 400) {
return reject(e);
}
resolve(e);
});
xhr.addEventListener("error", reject);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
}
}
if (param.timeout !== undefined && param.timeout !== 0) {
xhr.timeout = param.timeout;
xhr.ontimeout = function () {
return reject(new jIO.util.jIOError("Gateway Timeout", 504));
};
}
if (typeof param.beforeSend === 'function') {
param.beforeSend(xhr);
}
xhr.send(param.data);
}, function () {
xhr.abort();
});
}
util.ajax = ajax;
function readBlobAsText(blob, encoding) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.readAsText(blob, encoding);
}, function () {
fr.abort();
});
}
util.readBlobAsText = readBlobAsText;
function readBlobAsArrayBuffer(blob) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.readAsArrayBuffer(blob);
}, function () {
fr.abort();
});
}
util.readBlobAsArrayBuffer = readBlobAsArrayBuffer;
function readBlobAsDataURL(blob) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.readAsDataURL(blob);
}, function () {
fr.abort();
});
}
util.readBlobAsDataURL = readBlobAsDataURL;
function stringify(obj) {
// Implement a stable JSON.stringify
// Object's keys are alphabetically ordered
var key,
key_list,
i,
value,
result_list;
if (obj === undefined) {
return undefined;
}
if (obj === null) {
return 'null';
}
if (obj.constructor === Object) {
key_list = Object.keys(obj).sort();
result_list = [];
for (i = 0; i < key_list.length; i += 1) {
key = key_list[i];
value = stringify(obj[key]);
if (value !== undefined) {
result_list.push(stringify(key) + ':' + value);
}
}
return '{' + result_list.join(',') + '}';
}
if (obj.constructor === Array) {
result_list = [];
for (i = 0; i < obj.length; i += 1) {
result_list.push(stringify(obj[i]));
}
return '[' + result_list.join(',') + ']';
}
return JSON.stringify(obj);
}
util.stringify = stringify;
function base64toBlob(b64String, mimeString) {
var byteString = atob(b64String),
// write the bytes of the string to an ArrayBuffer
arrayBuffer = new ArrayBuffer(byteString.length),
_ia = new Uint8Array(arrayBuffer),
i;
for (i = 0; i < byteString.length; i += 1) {
_ia[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], {type: mimeString});
}
util.base64toBlob = base64toBlob;
// https://gist.github.com/davoclavo/4424731
function dataURItoBlob(dataURI) {
if (dataURI === 'data:') {
return new Blob();
}
// convert base64 to raw binary data held in a string
var mimeString = dataURI.split(',')[0].split(':')[1];
mimeString = mimeString.slice(0, mimeString.length - ";base64".length);
return base64toBlob(dataURI.split(',')[1], mimeString);
}
util.dataURItoBlob = dataURItoBlob;
// tools
function checkId(argument_list, storage, method_name) {
if (typeof argument_list[0] !== 'string' || argument_list[0] === '') {
throw new jIO.util.jIOError(
"Document id must be a non empty string on '" + storage.__type +
"." + method_name + "'.",
400
);
}
}
function checkAttachmentId(argument_list, storage, method_name) {
if (typeof argument_list[1] !== 'string' || argument_list[1] === '') {
throw new jIO.util.jIOError(
"Attachment id must be a non empty string on '" + storage.__type +
"." + method_name + "'.",
400
);
}
}
function ensurePushableQueue(callback, argument_list, context) {
var result;
try {
result = callback.apply(context, argument_list);
} catch (e) {
return new RSVP.Queue()
.push(function returnPushableError() {
return RSVP.reject(e);
});
}
if (result instanceof RSVP.Queue) {
return result;
}
return new RSVP.Queue(result);
}
function declareMethod(klass, name, precondition_function, post_function) {
klass.prototype[name] = function () {
var argument_list = arguments,
context = this,
precondition_result,
storage_method,
queue;
// Precondition function are not asynchronous
if (precondition_function !== undefined) {
precondition_result = precondition_function.apply(
context.__storage,
[argument_list, context, name]
);
}
storage_method = context.__storage[name];
if (storage_method === undefined) {
throw new jIO.util.jIOError(
"Capacity '" + name + "' is not implemented on '" +
context.__type + "'",
501
);
}
queue = ensurePushableQueue(storage_method, argument_list,
context.__storage);
if (post_function !== undefined) {
queue
.push(function (result) {
return post_function.call(
context,
argument_list,
result,
precondition_result
);
});
}
return queue;
};
// Allow chain
return this;
}
/////////////////////////////////////////////////////////////////
// jIO Storage Proxy
/////////////////////////////////////////////////////////////////
function JioProxyStorage(type, storage) {
if (!(this instanceof JioProxyStorage)) {
return new JioProxyStorage();
}
this.__type = type;
this.__storage = storage;
}
declareMethod(JioProxyStorage, "put", checkId, function (argument_list) {
return argument_list[0];
});
declareMethod(JioProxyStorage, "get", checkId);
declareMethod(JioProxyStorage, "bulk");
declareMethod(JioProxyStorage, "remove", checkId, function (argument_list) {
return argument_list[0];
});
JioProxyStorage.prototype.post = function () {
var context = this,
argument_list = arguments;
return ensurePushableQueue(function () {
var storage_method = context.__storage.post;
if (storage_method === undefined) {
throw new jIO.util.jIOError(
"Capacity 'post' is not implemented on '" + context.__type + "'",
501
);
}
return context.__storage.post.apply(context.__storage, argument_list);
});
};
declareMethod(JioProxyStorage, 'putAttachment', function (argument_list,
storage,
method_name) {
checkId(argument_list, storage, method_name);
checkAttachmentId(argument_list, storage, method_name);
var options = argument_list[3] || {};
if (typeof argument_list[2] === 'string') {
argument_list[2] = new Blob([argument_list[2]], {
"type": options._content_type || options._mimetype ||
"text/plain;charset=utf-8"
});
} else if (!(argument_list[2] instanceof Blob)) {
throw new jIO.util.jIOError(
'Attachment content is not a blob',
400
);
}
});
declareMethod(JioProxyStorage, 'removeAttachment', function (argument_list,
storage,
method_name) {
checkId(argument_list, storage, method_name);
checkAttachmentId(argument_list, storage, method_name);
});
declareMethod(JioProxyStorage, 'getAttachment', function (argument_list,
storage,
method_name) {
var result = "blob";
// if (param.storage_spec.type !== "indexeddb" &&
// param.storage_spec.type !== "dav" &&
// (param.kwargs._start !== undefined
// || param.kwargs._end !== undefined)) {
// restCommandRejecter(param, [
// 'bad_request',
// 'unsupport',
// '_start, _end not support'
// ]);
// return false;
// }
checkId(argument_list, storage, method_name);
checkAttachmentId(argument_list, storage, method_name);
// Drop optional parameters, which are only used in postfunction
if (argument_list[2] !== undefined) {
result = argument_list[2].format || result;
delete argument_list[2].format;
}
return result;
}, function (argument_list, blob, convert) {
var result;
if (!(blob instanceof Blob)) {
throw new jIO.util.jIOError(
"'getAttachment' (" + argument_list[0] + " , " +
argument_list[1] + ") on '" + this.__type +
"' does not return a Blob.",
501
);
}
if (convert === "blob") {
result = blob;
} else if (convert === "data_url") {
result = new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (evt) {
return evt.target.result;
});
} else if (convert === "array_buffer") {
result = new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsArrayBuffer(blob);
})
.push(function (evt) {
return evt.target.result;
});
} else if (convert === "text") {
result = new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsText(blob);
})
.push(function (evt) {
return evt.target.result;
});
} else if (convert === "json") {
result = new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsText(blob);
})
.push(function (evt) {
return JSON.parse(evt.target.result);
});
} else {
throw new jIO.util.jIOError(
this.__type + ".getAttachment format: '" + convert +
"' is not supported",
400
);
}
return result;
});
JioProxyStorage.prototype.buildQuery = function () {
var storage_method = this.__storage.buildQuery,
context = this,
argument_list = arguments;
if (storage_method === undefined) {
throw new jIO.util.jIOError(
"Capacity 'buildQuery' is not implemented on '" + this.__type + "'",
501
);
}
return ensurePushableQueue(storage_method, argument_list,
context.__storage);
};
JioProxyStorage.prototype.hasCapacity = function (name) {
var storage_method = this.__storage.hasCapacity,
capacity_method = this.__storage[name];
if (capacity_method !== undefined) {
return true;
}
if ((storage_method === undefined) ||
!storage_method.apply(this.__storage, arguments)) {
throw new jIO.util.jIOError(
"Capacity '" + name + "' is not implemented on '" + this.__type + "'",
501
);
}
return true;
};
JioProxyStorage.prototype.allDocs = function (options) {
var context = this;
if (options === undefined) {
options = {};
}
return ensurePushableQueue(function () {
if (context.hasCapacity("list") &&
((options.query === undefined) || context.hasCapacity("query")) &&
((options.sort_on === undefined) || context.hasCapacity("sort")) &&
((options.group_by === undefined) || context.hasCapacity("group")) &&
((options.select_list === undefined) ||
context.hasCapacity("select")) &&
((options.include_docs === undefined) ||
context.hasCapacity("include")) &&
((options.limit === undefined) || context.hasCapacity("limit"))) {
return context.buildQuery(options)
.push(function (result) {
return {
data: {
rows: result,
total_rows: result.length
}
};
});
}
});
};
declareMethod(JioProxyStorage, "allAttachments", checkId);
declareMethod(JioProxyStorage, "repair");
JioProxyStorage.prototype.repair = function () {
var context = this,
argument_list = arguments;
return ensurePushableQueue(function () {
var storage_method = context.__storage.repair;
if (storage_method !== undefined) {
return context.__storage.repair.apply(context.__storage,
argument_list);
}
});
};
/////////////////////////////////////////////////////////////////
// Storage builder
/////////////////////////////////////////////////////////////////
function JioBuilder() {
if (!(this instanceof JioBuilder)) {
return new JioBuilder();
}
this.__storage_types = {};
}
JioBuilder.prototype.createJIO = function (storage_spec, util) {
if (typeof storage_spec.type !== 'string') {
throw new TypeError("Invalid storage description");
}
if (!this.__storage_types[storage_spec.type]) {
throw new TypeError("Unknown storage '" + storage_spec.type + "'");
}
return new JioProxyStorage(
storage_spec.type,
new this.__storage_types[storage_spec.type](storage_spec, util)
);
};
JioBuilder.prototype.addStorage = function (type, Constructor) {
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 (this.__storage_types[type] !== undefined) {
throw new TypeError("jIO.addStorage(): Storage type already exists");
}
this.__storage_types[type] = Constructor;
};
JioBuilder.prototype.util = util;
JioBuilder.prototype.QueryFactory = QueryFactory;
JioBuilder.prototype.Query = Query;
/////////////////////////////////////////////////////////////////
// global
/////////////////////////////////////////////////////////////////
jIO = new JioBuilder();
window.jIO = jIO;
}(window, RSVP, Blob, QueryFactory, Query, atob,
FileReader, ArrayBuffer, Uint8Array));
jio-master-src/src/jio.storage/ 0000775 0000000 0000000 00000000000 14230544473 0016736 5 ustar 00root root 0000000 0000000 jio-master-src/src/jio.storage/cloudooostorage.js 0000664 0000000 0000000 00000013264 14230544473 0022512 0 ustar 00root root 0000000 0000000 /*
* Copyright 2018, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global window, jIO, RSVP, DOMParser, XMLSerializer, navigator*/
(function (window, jIO, RSVP, DOMParser, XMLSerializer, navigator) {
"use strict";
/* Document is not defined in ServiceWorker */
if (window.document === undefined) {
window.document = {
createElementNS: function () {
throw new Error(
'document.createElementNS is not supported by ' + navigator.userAgent
);
}
};
}
var parser = new DOMParser(),
serializer = new XMLSerializer();
function makeXmlRpcRequest(file, from, to, conversion_kw) {
var xml = parser.parseFromString(
'' +
'convertFile' +
'' +
'' +
'' +
'' +
'',
'text/xml'
),
elt,
member,
name,
value,
key,
struct = xml.getElementsByTagName('struct'),
string_list = xml.getElementsByTagName('string');
string_list[0].textContent = file;
string_list[1].textContent = from;
string_list[2].textContent = to;
if (conversion_kw) {
for (key in conversion_kw) {
if (conversion_kw.hasOwnProperty(key)) {
elt = window.document.createElementNS(null, conversion_kw[key][1]);
elt.textContent = conversion_kw[key][0];
value = window.document.createElementNS(null, "value");
value.appendChild(elt);
name = window.document.createElementNS(null, "name");
name.textContent = key;
member = window.document.createElementNS(null, "member");
member.appendChild(name);
member.appendChild(value);
struct[0].appendChild(member);
}
}
}
return serializer.serializeToString(xml);
}
/**
* convert a blob
* from a format to another
* return converted blob.
**/
function convert(url, blob, from, to, conversion_kw) {
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (result) {
return jIO.util.ajax({
type: 'POST',
url: url,
data: makeXmlRpcRequest(
result.target.result.split('base64,')[1],
from,
to,
conversion_kw
)
});
})
.push(function (result) {
var data = parser.parseFromString(
result.target.responseText,
"application/xml"
), error;
if (data.getElementsByTagName('fault').length === 0) {
return jIO.util.base64toBlob(
data.querySelector('string').textContent,
to
);
}
error = new jIO.util.jIOError('Conversion failed', 500);
error.detail = data.querySelector('string').textContent;
throw error;
});
}
/**
* The jIO CloudoooStorage extension
*
* Convert attachment : att_id?from="format"&to="format"
*
* @class CloudoooStorage
* @constructor
*/
function CloudoooStorage(spec) {
this._url = spec.url;
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
CloudoooStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.put = function () {
return this._sub_storage.put.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.getAttachment = function () {
return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.putAttachment = function (id, name, blob,
conversion_kw
) {
var storage = this;
return storage.get(id)
.push(function (doc) {
return convert(storage._url, blob, doc.from, doc.to, conversion_kw);
})
.push(function (converted_blob) {
return storage._sub_storage.putAttachment(id, name, converted_blob);
});
};
CloudoooStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.hasCapacity = function () {
return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments);
};
CloudoooStorage.prototype.buildQuery = function () {
return this._sub_storage.buildQuery.apply(this._sub_storage, arguments);
};
jIO.addStorage('cloudooo', CloudoooStorage);
}(window, jIO, RSVP, DOMParser, XMLSerializer, navigator));
jio-master-src/src/jio.storage/cryptstorage.js 0000664 0000000 0000000 00000014737 14230544473 0022036 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer*/
(function (jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer) {
"use strict";
/*
The cryptography system used by this storage is AES-GCM.
Here is an example of how to generate a key to the json format:
return new RSVP.Queue()
.push(function () {
return crypto.subtle.generateKey({name: "AES-GCM", length: 256},
true, ["encrypt", "decrypt"]);
})
.push(function (key) {
return crypto.subtle.exportKey("jwk", key);
})
.push(function (json_key) {
var jio = jIO.createJIO({
type: "crypt",
key: json_key,
sub_storage: {storage_definition}
});
});
Find more informations about this cryptography system on
https://github.com/diafygi/webcrypto-examples#aes-gcm
*/
/**
* The JIO Cryptography Storage extension
*
* @class CryptStorage
* @constructor
*/
var MIME_TYPE = "application/x-jio-aes-gcm-encryption";
function CryptStorage(spec) {
this._key = spec.key;
this._jsonKey = true;
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
function convertKey(that) {
return new RSVP.Queue()
.push(function () {
return crypto.subtle.importKey("jwk", that._key,
"AES-GCM", false,
["encrypt", "decrypt"]);
})
.push(function (res) {
that._key = res;
that._jsonKey = false;
return;
});
}
CryptStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.post = function () {
return this._sub_storage.post.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.put = function () {
return this._sub_storage.put.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.hasCapacity = function () {
return this._sub_storage.hasCapacity.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.buildQuery = function () {
return this._sub_storage.buildQuery.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.putAttachment = function (id, name, blob) {
var initializaton_vector = crypto.getRandomValues(new Uint8Array(12)),
that = this;
return new RSVP.Queue()
.push(function () {
if (that._jsonKey === true) {
return convertKey(that);
}
return;
})
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (dataURL) {
//string->arraybuffer
var strLen = dataURL.target.result.length,
buf = new ArrayBuffer(strLen),
bufView = new Uint8Array(buf),
i;
dataURL = dataURL.target.result;
for (i = 0; i < strLen; i += 1) {
bufView[i] = dataURL.charCodeAt(i);
}
return crypto.subtle.encrypt({
name : "AES-GCM",
iv : initializaton_vector
},
that._key, buf);
})
.push(function (coded) {
var final_blob = new Blob([initializaton_vector, coded],
{type: MIME_TYPE});
return that._sub_storage.putAttachment(id, name, final_blob);
});
};
CryptStorage.prototype.getAttachment = function (id, name) {
var that = this;
return that._sub_storage.getAttachment(id, name)
.push(function (blob) {
if (blob.type !== MIME_TYPE) {
return blob;
}
return new RSVP.Queue()
.push(function () {
if (that._jsonKey === true) {
return convertKey(that);
}
return;
})
.push(function () {
return jIO.util.readBlobAsArrayBuffer(blob);
})
.push(function (coded) {
var initializaton_vector;
coded = coded.target.result;
initializaton_vector = new Uint8Array(coded.slice(0, 12));
return new RSVP.Queue()
.push(function () {
return crypto.subtle.decrypt({
name : "AES-GCM",
iv : initializaton_vector
},
that._key, coded.slice(12));
})
.push(function (arr) {
//arraybuffer->string
arr = String.fromCharCode.apply(null, new Uint8Array(arr));
return jIO.util.dataURItoBlob(arr);
})
.push(undefined, function (error) {
if (error instanceof DOMException) {
return blob;
}
throw error;
});
});
});
};
CryptStorage.prototype.removeAttachment = function () {
return this._sub_storage.removeAttachment.apply(this._sub_storage,
arguments);
};
CryptStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage,
arguments);
};
jIO.addStorage('crypt', CryptStorage);
}(jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer));
jio-master-src/src/jio.storage/davstorage.js 0000664 0000000 0000000 00000023030 14230544473 0021431 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, RSVP, DOMParser, Blob */
// JIO Dav Storage Description :
// {
// type: "dav",
// url: {string},
// basic_login: {string} // Basic authentication
// }
// NOTE: to get the authentication type ->
// curl --verbose -X OPTION http://domain/
// In the headers: "WWW-Authenticate: Basic realm="DAV-upload"
(function (jIO, RSVP, DOMParser, Blob) {
"use strict";
function ajax(storage, options) {
if (options === undefined) {
options = {};
}
if (storage._authorization !== undefined) {
if (options.headers === undefined) {
options.headers = {};
}
options.headers.Authorization = storage._authorization;
}
if (storage._with_credentials !== undefined) {
if (options.xhrFields === undefined) {
options.xhrFields = {};
}
options.xhrFields.withCredentials = storage._with_credentials;
}
// if (start !== undefined) {
// if (end !== undefined) {
// headers.Range = "bytes=" + start + "-" + end;
// } else {
// headers.Range = "bytes=" + start + "-";
// }
// }
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax(options);
});
}
function restrictDocumentId(id) {
if (id.indexOf("/") !== 0) {
throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
400);
}
return id;
}
function restrictAttachmentId(id) {
if (id.indexOf("/") !== -1) {
throw new jIO.util.jIOError("attachment " + id + " is forbidden",
400);
}
}
/**
* The JIO WebDAV Storage extension
*
* @class DavStorage
* @constructor
*/
function DavStorage(spec) {
if (typeof spec.url !== 'string') {
throw new TypeError("DavStorage 'url' is not of type string");
}
this._url = spec.url;
// XXX digest login
if (typeof spec.basic_login === 'string') {
this._authorization = "Basic " + spec.basic_login;
}
this._with_credentials = spec.with_credentials;
}
DavStorage.prototype.put = function (id, param) {
var that = this;
id = restrictDocumentId(id);
if (Object.getOwnPropertyNames(param).length > 0) {
// Reject if param has some properties
throw new jIO.util.jIOError("Can not store properties: " +
Object.getOwnPropertyNames(param), 400);
}
return new RSVP.Queue()
.push(function () {
return ajax(that, {
type: "MKCOL",
url: that._url + id
});
})
.push(undefined, function (err) {
if ((err.target !== undefined) &&
(err.target.status === 405)) {
return;
}
throw err;
});
};
DavStorage.prototype.remove = function (id) {
id = restrictDocumentId(id);
return ajax(this, {
type: "DELETE",
url: this._url + id
});
};
DavStorage.prototype.get = function (id) {
var context = this;
id = restrictDocumentId(id);
return new RSVP.Queue()
.push(function () {
return ajax(context, {
type: "PROPFIND",
url: context._url + id,
dataType: "text",
headers: {
// Increasing this value is a performance killer
Depth: "1"
}
});
})
.push(function () {
return {};
}, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
throw error;
});
};
DavStorage.prototype.allAttachments = function (id) {
var context = this;
id = restrictDocumentId(id);
return new RSVP.Queue()
.push(function () {
return ajax(context, {
type: "PROPFIND",
url: context._url + id,
dataType: "text",
headers: {
// Increasing this value is a performance killer
Depth: "1"
}
});
})
.push(function (response) {
// Extract all meta informations and return them to JSON
var i,
attachment = {},
new_id,
attachment_list = new DOMParser().parseFromString(
response.target.responseText,
"text/xml"
).querySelectorAll(
"D\\:response, response"
);
// exclude parent folder and browse
for (i = 1; i < attachment_list.length; i += 1) {
// XXX Only get files for now
new_id = attachment_list[i].querySelector("D\\:href, href").
textContent.split('/').slice(-1)[0];
// XXX Ugly
if ((new_id !== undefined) && (new_id !== "")) {
attachment[new_id] = {};
}
}
return attachment;
}, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
throw error;
});
};
DavStorage.prototype.putAttachment = function (id, name, blob) {
var that = this;
id = restrictDocumentId(id);
restrictAttachmentId(name);
return new RSVP.Queue()
.push(function () {
return ajax(that, {
type: "PUT",
url: that._url + id + name,
data: blob
});
})
.push(undefined, function (error) {
if (error.target.status === 403 || error.target.status === 424) {
throw new jIO.util.jIOError("Cannot access subdocument", 404);
}
throw error;
});
};
DavStorage.prototype.getAttachment = function (id, name) {
var context = this;
id = restrictDocumentId(id);
restrictAttachmentId(name);
return new RSVP.Queue()
.push(function () {
return ajax(context, {
type: "GET",
url: context._url + id + name,
dataType: "blob"
});
})
.push(function (response) {
return new Blob(
[response.target.response || response.target.responseText],
{"type": response.target.getResponseHeader('Content-Type') ||
"application/octet-stream"}
);
}, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find attachment: "
+ id + " , " + name,
404);
}
throw error;
});
};
DavStorage.prototype.removeAttachment = function (id, name) {
var context = this;
id = restrictDocumentId(id);
restrictAttachmentId(name);
return new RSVP.Queue()
.push(function () {
return ajax(context, {
type: "DELETE",
url: context._url + id + name
});
})
.push(undefined, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find attachment: "
+ id + " , " + name,
404);
}
throw error;
});
};
// JIO COMMANDS //
// wedDav methods rfc4918 (short summary)
// COPY Reproduces single resources (files) and collections (directory
// trees). Will overwrite files (if specified by request) but will
// respond 209 (Conflict) if it would overwrite a tree
// DELETE deletes files and directory trees
// GET just the vanilla HTTP/1.1 behaviour
// HEAD ditto
// LOCK locks a resources
// MKCOL creates a directory
// MOVE Moves (rename or copy) a file or a directory tree. Will
// 'overwrite' files (if specified by the request) but will respond
// 209 (Conflict) if it would overwrite a tree.
// OPTIONS If WebDAV is enabled and available for the path this reports the
// WebDAV extension methods
// PROPFIND Retrieves the requested file characteristics, DAV lock status
// and 'dead' properties for individual files, a directory and its
// child files, or a directory tree
// PROPPATCHset and remove 'dead' meta-data properties
// PUT Update or create resource or collections
// UNLOCK unlocks a resource
// Notes: all Ajax requests should be CORS (cross-domain)
// adding custom headers triggers preflight OPTIONS request!
// http://remysharp.com/2011/04/21/getting-cors-working/
jIO.addStorage('dav', DavStorage);
}(jIO, RSVP, DOMParser, Blob));
jio-master-src/src/jio.storage/documentstorage.js 0000664 0000000 0000000 00000017127 14230544473 0022507 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global Blob, RSVP, unescape, escape*/
(function (jIO, Blob, RSVP, unescape, escape) {
"use strict";
/**
* The jIO DocumentStorage extension
*
* @class DocumentStorage
* @constructor
*/
function DocumentStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._document_id = spec.document_id;
this._repair_attachment = spec.repair_attachment || false;
}
var DOCUMENT_EXTENSION = ".json",
DOCUMENT_REGEXP = new RegExp("^jio_document/([\\w=]+)" +
DOCUMENT_EXTENSION + "$"),
ATTACHMENT_REGEXP = new RegExp("^jio_attachment/([\\w=]+)/([\\w=]+)$"),
btoa = function (str) {
return window.btoa(unescape(encodeURIComponent(str)));
},
atob = function (str) {
return decodeURIComponent(escape(window.atob(str)));
};
function getSubAttachmentIdFromParam(id, name) {
if (name === undefined) {
return 'jio_document/' + btoa(id) + DOCUMENT_EXTENSION;
}
return 'jio_attachment/' + btoa(id) + "/" + btoa(name);
}
DocumentStorage.prototype.get = function (id) {
return this._sub_storage.getAttachment(
this._document_id,
getSubAttachmentIdFromParam(id),
{format: "json"}
);
};
DocumentStorage.prototype.allAttachments = function (id) {
return this._sub_storage.allAttachments(this._document_id)
.push(function (result) {
var attachments = {},
exec,
key;
for (key in result) {
if (result.hasOwnProperty(key)) {
if (ATTACHMENT_REGEXP.test(key)) {
exec = ATTACHMENT_REGEXP.exec(key);
try {
if (atob(exec[1]) === id) {
attachments[atob(exec[2])] = {};
}
} catch (ignore) {
// Check if unable to decode base64 data
}
}
}
}
return attachments;
});
};
DocumentStorage.prototype.put = function (doc_id, param) {
return this._sub_storage.putAttachment(
this._document_id,
getSubAttachmentIdFromParam(doc_id),
new Blob([JSON.stringify(param)], {type: "application/json"})
)
.push(function () {
return doc_id;
});
};
DocumentStorage.prototype.remove = function (id) {
var context = this;
return this.allAttachments(id)
.push(function (result) {
var key,
promise_list = [];
for (key in result) {
if (result.hasOwnProperty(key)) {
promise_list.push(context.removeAttachment(id, key));
}
}
return RSVP.all(promise_list);
})
.push(function () {
return context._sub_storage.removeAttachment(
context._document_id,
getSubAttachmentIdFromParam(id)
);
})
.push(function () {
return id;
});
};
DocumentStorage.prototype.repair = function () {
var context = this;
return this._sub_storage.repair.apply(this._sub_storage, arguments)
.push(function (result) {
if (context._repair_attachment) {
return context._sub_storage.allAttachments(context._document_id)
.push(function (result_dict) {
var promise_list = [],
id_dict = {},
attachment_dict = {},
id,
attachment,
exec,
key;
for (key in result_dict) {
if (result_dict.hasOwnProperty(key)) {
id = undefined;
attachment = undefined;
if (DOCUMENT_REGEXP.test(key)) {
try {
id = atob(DOCUMENT_REGEXP.exec(key)[1]);
} catch (ignore) {
// Check if unable to decode base64 data
}
if (id !== undefined) {
id_dict[id] = null;
}
} else if (ATTACHMENT_REGEXP.test(key)) {
exec = ATTACHMENT_REGEXP.exec(key);
try {
id = atob(exec[1]);
attachment = atob(exec[2]);
} catch (ignore) {
// Check if unable to decode base64 data
}
if (attachment !== undefined) {
if (!id_dict.hasOwnProperty(id)) {
if (!attachment_dict.hasOwnProperty(id)) {
attachment_dict[id] = {};
}
attachment_dict[id][attachment] = null;
}
}
}
}
}
for (id in attachment_dict) {
if (attachment_dict.hasOwnProperty(id)) {
if (!id_dict.hasOwnProperty(id)) {
for (attachment in attachment_dict[id]) {
if (attachment_dict[id].hasOwnProperty(attachment)) {
promise_list.push(context.removeAttachment(
id,
attachment
));
}
}
}
}
}
return RSVP.all(promise_list);
});
}
return result;
});
};
DocumentStorage.prototype.hasCapacity = function (capacity) {
return (capacity === "list");
};
DocumentStorage.prototype.buildQuery = function () {
return this._sub_storage.allAttachments(this._document_id)
.push(function (attachment_dict) {
var result = [],
key;
for (key in attachment_dict) {
if (attachment_dict.hasOwnProperty(key)) {
if (DOCUMENT_REGEXP.test(key)) {
try {
result.push({
id: atob(DOCUMENT_REGEXP.exec(key)[1]),
value: {}
});
} catch (ignore) {
// Check if unable to decode base64 data
}
}
}
}
return result;
});
};
DocumentStorage.prototype.getAttachment = function (id, name) {
return this._sub_storage.getAttachment(
this._document_id,
getSubAttachmentIdFromParam(id, name)
);
};
DocumentStorage.prototype.putAttachment = function (id, name, blob) {
return this._sub_storage.putAttachment(
this._document_id,
getSubAttachmentIdFromParam(id, name),
blob
);
};
DocumentStorage.prototype.removeAttachment = function (id, name) {
return this._sub_storage.removeAttachment(
this._document_id,
getSubAttachmentIdFromParam(id, name)
);
};
jIO.addStorage('document', DocumentStorage);
}(jIO, Blob, RSVP, unescape, escape));
jio-master-src/src/jio.storage/drivetojiomapping.js 0000664 0000000 0000000 00000017212 14230544473 0023031 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global RSVP, Blob*/
(function (jIO, RSVP, Blob) {
"use strict";
/**
* The jIO FileSystemBridgeStorage extension
*
* @class FileSystemBridgeStorage
* @constructor
*/
function FileSystemBridgeStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
var DOCUMENT_EXTENSION = ".json",
DOCUMENT_KEY = "/.jio_documents/",
ROOT = "/";
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
FileSystemBridgeStorage.prototype.get = function (id) {
var context = this;
return new RSVP.Queue()
// First, try to get explicit reference to the document
.push(function () {
// First get the document itself if it exists
return context._sub_storage.getAttachment(
DOCUMENT_KEY,
id + DOCUMENT_EXTENSION,
{format: "json"}
);
})
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
// Second, try to get default attachment
return context._sub_storage.allAttachments(ROOT)
.push(function (attachment_dict) {
if (attachment_dict.hasOwnProperty(id)) {
return {};
}
throw new jIO.util.jIOError("Cannot find document " + id,
404);
});
}
throw error;
});
};
FileSystemBridgeStorage.prototype.allAttachments = function (id) {
var context = this;
return context._sub_storage.allAttachments(ROOT)
.push(function (attachment_dict) {
if (attachment_dict.hasOwnProperty(id)) {
return {
enclosure: {}
};
}
// Second get the document itself if it exists
return context._sub_storage.getAttachment(
DOCUMENT_KEY,
id + DOCUMENT_EXTENSION
)
.push(function () {
return {};
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
throw new jIO.util.jIOError("Cannot find document " + id,
404);
}
throw error;
});
});
};
FileSystemBridgeStorage.prototype.put = function (doc_id, param) {
var context = this;
// XXX Handle conflict!
return context._sub_storage.putAttachment(
DOCUMENT_KEY,
doc_id + DOCUMENT_EXTENSION,
new Blob([JSON.stringify(param)], {type: "application/json"})
)
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return context._sub_storage.put(DOCUMENT_KEY, {})
.push(function () {
return context._sub_storage.putAttachment(
DOCUMENT_KEY,
doc_id + DOCUMENT_EXTENSION,
new Blob([JSON.stringify(param)],
{type: "application/json"})
);
});
}
throw error;
})
.push(function () {
return doc_id;
});
};
FileSystemBridgeStorage.prototype.remove = function (doc_id) {
var context = this,
got_error = false;
return new RSVP.Queue()
// First, try to remove enclosure
.push(function () {
return context._sub_storage.removeAttachment(
ROOT,
doc_id
);
})
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
got_error = true;
return;
}
throw error;
})
// Second, try to remove explicit doc
.push(function () {
return context._sub_storage.removeAttachment(
DOCUMENT_KEY,
doc_id + DOCUMENT_EXTENSION
);
})
.push(undefined, function (error) {
if ((!got_error) && (error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return doc_id;
}
throw error;
});
};
FileSystemBridgeStorage.prototype.hasCapacity = function (capacity) {
return (capacity === "list");
};
FileSystemBridgeStorage.prototype.buildQuery = function () {
var result_dict = {},
context = this;
return new RSVP.Queue()
// First, get list of explicit documents
.push(function () {
return context._sub_storage.allAttachments(DOCUMENT_KEY);
})
.push(function (result) {
var key;
for (key in result) {
if (result.hasOwnProperty(key)) {
if (endsWith(key, DOCUMENT_EXTENSION)) {
result_dict[key.substring(
0,
key.length - DOCUMENT_EXTENSION.length
)] = null;
}
}
}
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return;
}
throw error;
})
// Second, get list of enclosure
.push(function () {
return context._sub_storage.allAttachments(ROOT);
})
.push(function (result) {
var key;
for (key in result) {
if (result.hasOwnProperty(key)) {
result_dict[key] = null;
}
}
})
// Finally, build the result
.push(function () {
var result = [],
key;
for (key in result_dict) {
if (result_dict.hasOwnProperty(key)) {
result.push({
id: key,
value: {}
});
}
}
return result;
});
};
FileSystemBridgeStorage.prototype.getAttachment = function (id, name) {
if (name !== "enclosure") {
throw new jIO.util.jIOError("Only support 'enclosure' attachment",
400);
}
return this._sub_storage.getAttachment(ROOT, id);
};
FileSystemBridgeStorage.prototype.putAttachment = function (id, name, blob) {
if (name !== "enclosure") {
throw new jIO.util.jIOError("Only support 'enclosure' attachment",
400);
}
return this._sub_storage.putAttachment(
ROOT,
id,
blob
);
};
FileSystemBridgeStorage.prototype.removeAttachment = function (id, name) {
if (name !== "enclosure") {
throw new jIO.util.jIOError("Only support 'enclosure' attachment",
400);
}
return this._sub_storage.removeAttachment(ROOT, id);
};
FileSystemBridgeStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
jIO.addStorage('drivetojiomapping', FileSystemBridgeStorage);
}(jIO, RSVP, Blob));
jio-master-src/src/jio.storage/dropboxstorage.js 0000664 0000000 0000000 00000026613 14230544473 0022346 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Dropbox Storage. Type = "dropbox".
* Dropbox "database" storage.
*/
/*global Blob, jIO, RSVP*/
/*jslint nomen: true*/
(function (jIO, RSVP, Blob, JSON) {
"use strict";
var GET_URL = "https://content.dropboxapi.com/2/files/download",
UPLOAD_URL = "https://content.dropboxapi.com/2/files/upload",
REMOVE_URL = "https://api.dropboxapi.com/2/files/delete_v2",
CREATE_DIR_URL = "https://api.dropboxapi.com/2/files/create_folder_v2",
METADATA_URL = "https://api.dropboxapi.com/2/files/get_metadata",
LIST_FOLDER_URL = "https://api.dropboxapi.com/2/files/list_folder",
LIST_MORE_URL = "https://api.dropboxapi.com/2/files/list_folder/continue";
function restrictDocumentId(id) {
if (id.indexOf("/") !== 0) {
throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
400);
}
return id.slice(0, -1);
}
function restrictAttachmentId(id) {
if (id.indexOf("/") !== -1) {
throw new jIO.util.jIOError("attachment " + id + " is forbidden",
400);
}
}
function recursiveAllAttachments(result, token, id, cursor) {
var data,
url;
if (cursor === undefined) {
data = {
"path": id,
"recursive": false,
"include_media_info": false,
"include_deleted": false,
"include_has_explicit_shared_members": false,
"include_mounted_folders": true
};
url = LIST_FOLDER_URL;
} else {
data = {"cursor": cursor};
url = LIST_MORE_URL;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "POST",
url: url,
headers: {
"Authorization": "Bearer " + token,
"Content-Type": "application/json"
},
data: JSON.stringify(data)
});
})
.push(function (evt) {
var obj = JSON.parse(evt.target.response || evt.target.responseText),
i;
for (i = 0; i < obj.entries.length; i += 1) {
if (obj.entries[i][".tag"] === "file") {
result[obj.entries[i].name] = {};
}
}
if (obj.has_more) {
return recursiveAllAttachments(result, token, id, obj.cursor);
}
return result;
}, function (error) {
if (error.target !== undefined && error.target.status === 409) {
var err_content = JSON.parse(error.target.response ||
error.target.responseText);
if ((err_content.error['.tag'] === 'path') &&
(err_content.error.path['.tag'] === 'not_folder')) {
throw new jIO.util.jIOError("Not a directory: " + id + "/",
404);
}
if ((err_content.error['.tag'] === 'path') &&
(err_content.error.path['.tag'] === 'not_found')) {
throw new jIO.util.jIOError("Cannot find document: " + id + "/",
404);
}
}
throw error;
});
}
/**
* The JIO Dropbox Storage extension
*
* @class DropboxStorage
* @constructor
*/
function DropboxStorage(spec) {
if (typeof spec.access_token !== 'string' || !spec.access_token) {
throw new TypeError("Access Token' must be a string " +
"which contains more than one character.");
}
this._access_token = spec.access_token;
}
DropboxStorage.prototype.put = function (id, param) {
var that = this;
id = restrictDocumentId(id);
if (Object.getOwnPropertyNames(param).length > 0) {
// Reject if param has some properties
throw new jIO.util.jIOError("Can not store properties: " +
Object.getOwnPropertyNames(param), 400);
}
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "POST",
url: CREATE_DIR_URL,
headers: {
"Authorization": "Bearer " + that._access_token,
"Content-Type": "application/json"
},
data: JSON.stringify({"path": id, "autorename": false})
});
})
.push(undefined, function (err) {
if ((err.target !== undefined) &&
(err.target.status === 409)) {
var err_content = JSON.parse(err.target.response ||
err.target.responseText);
if ((err_content.error['.tag'] === 'path') &&
(err_content.error.path['.tag'] === 'conflict')) {
// Directory already exists, no need to fail
return;
}
}
throw err;
});
};
DropboxStorage.prototype.remove = function (id) {
id = restrictDocumentId(id);
return jIO.util.ajax({
type: "POST",
url: REMOVE_URL,
headers: {
"Authorization": "Bearer " + this._access_token,
"Content-Type": "application/json"
},
data: JSON.stringify({"path": id})
});
};
DropboxStorage.prototype.get = function (id) {
var that = this;
if (id === "/") {
return {};
}
id = restrictDocumentId(id);
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "POST",
url: METADATA_URL,
headers: {
"Authorization": "Bearer " + that._access_token,
"Content-Type": "application/json"
},
data: JSON.stringify({"path": id})
});
})
.push(function (evt) {
var obj = JSON.parse(evt.target.response ||
evt.target.responseText);
if (obj[".tag"] === "folder") {
return {};
}
throw new jIO.util.jIOError("Not a directory: " + id + "/", 404);
}, function (error) {
if (error.target !== undefined && error.target.status === 409) {
var err_content = JSON.parse(error.target.response ||
error.target.responseText);
if ((err_content.error['.tag'] === 'path') &&
(err_content.error.path['.tag'] === 'not_found')) {
throw new jIO.util.jIOError("Cannot find document: " + id + "/",
404);
}
}
throw error;
});
};
DropboxStorage.prototype.allAttachments = function (id) {
id = restrictDocumentId(id);
return recursiveAllAttachments({}, this._access_token, id);
};
//currently, putAttachment will fail with files larger than 150MB,
//due to the Dropbox API. the API provides the "chunked_upload" method
//to pass this limit, but upload process becomes more complex to implement.
//
//putAttachment will also create a folder if you try to put an attachment
//to an inexisting foler.
DropboxStorage.prototype.putAttachment = function (id, name, blob) {
id = restrictDocumentId(id);
restrictAttachmentId(name);
return jIO.util.ajax({
type: "POST",
url: UPLOAD_URL,
headers: {
"Authorization": "Bearer " + this._access_token,
"Content-Type": "application/octet-stream",
"Dropbox-API-Arg": JSON.stringify({
"path": id + "/" + name,
"mode": "overwrite",
"autorename": false,
"mute": false
})
},
data: blob
});
};
DropboxStorage.prototype.getAttachment = function (id, name) {
var context = this;
id = restrictDocumentId(id);
restrictAttachmentId(name);
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
url: GET_URL,
type: "POST",
dataType: "blob",
headers: {
"Authorization": "Bearer " + context._access_token,
"Dropbox-API-Arg": JSON.stringify({"path": id + "/" + name})
}
});
})
.push(function (evt) {
if (evt.target.response instanceof Blob) {
return evt.target.response;
}
return new Blob(
[evt.target.responseText],
{"type": evt.target.getResponseHeader('Content-Type') ||
"application/octet-stream"}
);
}, function (error) {
if (error.target !== undefined && error.target.status === 409) {
if (!(error.target.response instanceof Blob)) {
var err_content = JSON.parse(error.target.responseText);
if ((err_content.error['.tag'] === 'path') &&
(err_content.error.path['.tag'] === 'not_found')) {
throw new jIO.util.jIOError("Cannot find attachment: " +
id + "/, " + name, 404);
}
throw error;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsText(error.target.response);
})
.push(function (evt) {
var err_content2 = JSON.parse(evt.target.result);
if ((err_content2.error['.tag'] === 'path') &&
(err_content2.error.path['.tag'] === 'not_found')) {
throw new jIO.util.jIOError("Cannot find attachment: " +
id + "/, " + name, 404);
}
throw error;
});
}
throw error;
});
};
//removeAttachment removes also directories.(due to Dropbox API)
DropboxStorage.prototype.removeAttachment = function (id, name) {
var that = this;
id = restrictDocumentId(id);
restrictAttachmentId(name);
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "POST",
url: REMOVE_URL,
headers: {
"Authorization": "Bearer " + that._access_token,
"Content-Type": "application/json"
},
data: JSON.stringify({"path": id + "/" + name})
});
}).push(undefined, function (error) {
if (error.target !== undefined && error.target.status === 409) {
var err_content = JSON.parse(error.target.response ||
error.target.responseText);
if ((err_content.error['.tag'] === 'path_lookup') &&
(err_content.error.path_lookup['.tag'] === 'not_found')) {
throw new jIO.util.jIOError("Cannot find attachment: " +
id + "/, " + name, 404);
}
}
throw error;
});
};
jIO.addStorage('dropbox', DropboxStorage);
}(jIO, RSVP, Blob, JSON));
jio-master-src/src/jio.storage/erp5storage.js 0000664 0000000 0000000 00000046622 14230544473 0021546 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
// JIO ERP5 Storage Description :
// {
// type: "erp5"
// url: {string}
// }
/*jslint nomen: true, unparam: true */
/*global jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery*/
(function (jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery) {
"use strict";
function ajax(storage, options) {
if (options === undefined) {
options = {};
}
if (options.xhrFields === undefined) {
options.xhrFields = {};
}
if (storage._access_token !== undefined) {
if (options.headers === undefined) {
options.headers = {};
}
options.headers['X-ACCESS-TOKEN'] = storage._access_token;
options.xhrFields.withCredentials = false;
} else {
options.xhrFields.withCredentials = true;
}
return jIO.util.ajax(options);
}
function getSiteDocument(storage) {
return new RSVP.Queue()
.push(function () {
return ajax(storage, {
"type": "GET",
"url": storage._url
});
})
.push(function (event) {
return JSON.parse(event.target.responseText);
});
}
function getDocumentAndHateoas(storage, id, options) {
if (options === undefined) {
options = {};
}
return getSiteDocument(storage)
.push(function (site_hal) {
// XXX need to get modified metadata
return new RSVP.Queue()
.push(function () {
return ajax(storage, {
"type": "GET",
"url": UriTemplate.parse(site_hal._links.traverse.href)
.expand({
relative_url: id,
view: options._view
})
});
})
.push(undefined, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find document: " + id, 404);
}
throw error;
});
});
}
var allowed_field_dict = {
"StringField": null,
"EmailField": null,
"IntegerField": null,
"FloatField": null,
"TextAreaField": null
};
function extractPropertyFromFormJSON(json) {
var form = json._embedded._view,
converted_json = {
portal_type: new URI(json._links.type.href).segment(2)
.replace("portal_types/", "")
},
form_data_json = {},
field,
key,
prefix_length,
result;
if (json._links.hasOwnProperty('parent')) {
converted_json.parent_relative_url =
new URI(json._links.parent.href).segment(2);
}
form_data_json.form_id = {
"key": [form.form_id.key],
"default": form.form_id["default"]
};
// XXX How to store datetime
for (key in form) {
if (form.hasOwnProperty(key)) {
field = form[key];
prefix_length = 0;
if (key.indexOf('my_') === 0 && field.editable) {
prefix_length = 3;
}
if (key.indexOf('your_') === 0) {
prefix_length = 5;
}
if ((prefix_length !== 0) &&
(allowed_field_dict.hasOwnProperty(field.type))) {
form_data_json[key.substring(prefix_length)] = {
"default": field["default"],
"key": field.key
};
converted_json[key.substring(prefix_length)] = field["default"];
}
}
}
result = {
data: converted_json,
form_data: form_data_json
};
if (form.hasOwnProperty('_actions') &&
form._actions.hasOwnProperty('put')) {
result.action_href = form._actions.put.href;
}
return result;
}
function extractPropertyFromForm(context, id) {
return context.getAttachment(id, "view")
.push(function (blob) {
return jIO.util.readBlobAsText(blob);
})
.push(function (evt) {
return JSON.parse(evt.target.result);
})
.push(function (json) {
return extractPropertyFromFormJSON(json);
});
}
// XXX docstring
function ERP5Storage(spec) {
if (typeof spec.url !== "string" || !spec.url) {
throw new TypeError("ERP5 'url' must be a string " +
"which contains more than one character.");
}
this._url = spec.url;
this._default_view_reference = spec.default_view_reference;
if (spec.hasOwnProperty('access_token')) {
this._access_token = spec.access_token;
}
}
function convertJSONToGet(json) {
var key,
result = json.data;
// Remove all ERP5 hateoas links / convert them into jIO ID
for (key in result) {
if (result.hasOwnProperty(key)) {
if (!result[key]) {
delete result[key];
}
}
}
return result;
}
ERP5Storage.prototype.get = function (id) {
return extractPropertyFromForm(this, id)
.push(function (result) {
return convertJSONToGet(result);
});
};
ERP5Storage.prototype.post = function (data) {
var context = this,
new_id;
return getSiteDocument(this)
.push(function (site_hal) {
var form_data = new FormData();
form_data.append("portal_type", data.portal_type);
form_data.append("parent_relative_url", data.parent_relative_url);
return ajax(context, {
type: "POST",
url: site_hal._actions.add.href,
data: form_data
});
})
.push(function (evt) {
var location = evt.target.getResponseHeader("X-Location"),
uri = new URI(location);
new_id = uri.segment(2);
return context.put(new_id, data);
})
.push(function () {
return new_id;
});
};
ERP5Storage.prototype.put = function (id, data) {
var context = this;
return extractPropertyFromForm(context, id)
.push(function (result) {
var key,
json = result.form_data,
form_data = {};
form_data[json.form_id.key] = json.form_id["default"];
// XXX How to store datetime:!!!!!
for (key in data) {
if (data.hasOwnProperty(key)) {
if (key === "form_id") {
throw new jIO.util.jIOError(
"ERP5: forbidden property: " + key,
400
);
}
if ((key !== "portal_type") && (key !== "parent_relative_url")) {
if (!json.hasOwnProperty(key)) {
throw new jIO.util.jIOError(
"ERP5: can not store property: " + key,
400
);
}
form_data[json[key].key] = data[key];
}
}
}
if (!result.hasOwnProperty('action_href')) {
throw new jIO.util.jIOError(
"ERP5: can not modify document: " + id,
403
);
}
return context.putAttachment(
id,
result.action_href,
new Blob([JSON.stringify(form_data)], {type: "application/json"})
);
});
};
ERP5Storage.prototype.allAttachments = function (id) {
var context = this;
return getDocumentAndHateoas(this, id)
.push(function () {
if (context._default_view_reference === undefined) {
return {
links: {}
};
}
return {
view: {},
links: {}
};
});
};
ERP5Storage.prototype.getAttachment = function (id, action, options) {
var storage = this;
if (options === undefined) {
options = {};
}
if (action === "view") {
if (this._default_view_reference === undefined) {
throw new jIO.util.jIOError(
"Cannot find attachment view for: " + id,
404
);
}
return getDocumentAndHateoas(this, id,
{"_view": this._default_view_reference})
.push(function (response) {
var result = JSON.parse(response.target.responseText);
// Remove all ERP5 hateoas links / convert them into jIO ID
// XXX Change default action to an jio urn with attachment name inside
// if Base_edit, do put URN
// if others, do post URN (ie, unique new attachment name)
// XXX Except this attachment name should be generated when
return new Blob(
[JSON.stringify(result)],
{"type": 'application/hal+json'}
);
});
}
if (action === "links") {
return getDocumentAndHateoas(this, id)
.push(function (response) {
return new Blob(
[JSON.stringify(JSON.parse(response.target.responseText))],
{"type": 'application/hal+json'}
);
});
}
if (action.indexOf(this._url) === 0) {
return new RSVP.Queue()
.push(function () {
var start,
end,
range,
request_options = {
"type": "GET",
"dataType": "blob",
"url": action
};
if (options.start !== undefined || options.end !== undefined) {
start = options.start || 0;
end = options.end;
if (end !== undefined && end < 0) {
throw new jIO.util.jIOError("end must be positive",
400);
}
if (start < 0) {
range = "bytes=" + start;
} else if (end === undefined) {
range = "bytes=" + start + "-";
} else {
if (start > end) {
throw new jIO.util.jIOError("start is greater than end",
400);
}
range = "bytes=" + start + "-" + end;
}
request_options.headers = {Range: range};
}
return ajax(storage, request_options);
})
.push(function (evt) {
if (evt.target.response === undefined) {
return new Blob(
[evt.target.responseText],
{"type": evt.target.getResponseHeader("Content-Type")}
);
}
return evt.target.response;
}, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find attachment: " + action,
404);
}
throw error;
});
}
throw new jIO.util.jIOError("ERP5: not support get attachment: " + action,
400);
};
ERP5Storage.prototype.putAttachment = function (id, name, blob) {
var storage = this;
// Assert we use a callable on a document from the ERP5 site
if (name.indexOf(this._url) !== 0) {
throw new jIO.util.jIOError("Can not store outside ERP5: " +
name, 400);
}
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsText(blob);
})
.push(function (evt) {
var form_data = JSON.parse(evt.target.result),
data = new FormData(),
array,
i,
key,
value;
for (key in form_data) {
if (form_data.hasOwnProperty(key)) {
if (Array.isArray(form_data[key])) {
array = form_data[key];
} else {
array = [form_data[key]];
}
for (i = 0; i < array.length; i += 1) {
value = array[i];
if (typeof value === "object") {
data.append(key, jIO.util.dataURItoBlob(value.url),
value.file_name);
} else {
data.append(key, value);
}
}
}
}
return ajax(storage, {
"type": "POST",
"url": name,
"data": data,
"dataType": "blob"
});
});
};
ERP5Storage.prototype.hasCapacity = function (name) {
return ((name === "list") || (name === "query") ||
(name === "select") || (name === "limit") ||
(name === "sort") || (name === "group"));
};
function isSingleLocalRoles(parsed_query) {
if ((parsed_query instanceof SimpleQuery) &&
(parsed_query.operator === undefined) &&
(parsed_query.key === 'local_roles')) {
// local_roles:"Assignee"
return parsed_query.value;
}
}
function isSingleDomain(parsed_query) {
if ((parsed_query instanceof SimpleQuery) &&
(parsed_query.operator === undefined) &&
(parsed_query.key !== undefined) &&
(parsed_query.key.indexOf('selection_domain_') === 0)) {
// domain_region:"europe/france"
var result = {};
result[parsed_query.key.slice('selection_domain_'.length)] =
parsed_query.value;
return result;
}
}
function isMultipleLocalRoles(parsed_query) {
var i,
sub_query,
is_multiple = true,
local_role_list = [];
if ((parsed_query instanceof ComplexQuery) &&
(parsed_query.operator === 'OR')) {
for (i = 0; i < parsed_query.query_list.length; i += 1) {
sub_query = parsed_query.query_list[i];
if ((sub_query instanceof SimpleQuery) &&
(sub_query.key !== undefined) &&
(sub_query.key === 'local_roles')) {
local_role_list.push(sub_query.value);
} else {
is_multiple = false;
}
}
if (is_multiple) {
// local_roles:"Assignee" OR local_roles:"Assignor"
return local_role_list;
}
}
}
ERP5Storage.prototype.buildQuery = function (options) {
var storage = this;
// if (typeof options.query !== "string") {
// options.query = (options.query ?
// jIO.Query.objectToSearchText(options.query) :
// undefined);
// }
return getSiteDocument(this)
.push(function (site_hal) {
var query = options.query,
i,
key,
parsed_query,
sub_query,
result_list,
local_roles,
local_role_found = false,
selection_domain_found = false,
selection_domain,
sort_list = [],
group_list = [];
if (options.query) {
parsed_query = jIO.QueryFactory.create(options.query);
result_list = isSingleLocalRoles(parsed_query);
if (result_list) {
query = undefined;
local_roles = result_list;
} else {
result_list = isSingleDomain(parsed_query);
if (result_list) {
query = undefined;
selection_domain = result_list;
} else {
result_list = isMultipleLocalRoles(parsed_query);
if (result_list) {
query = undefined;
local_roles = result_list;
} else if ((parsed_query instanceof ComplexQuery) &&
(parsed_query.operator === 'AND')) {
// portal_type:"Person" AND local_roles:"Assignee"
// AND selection_domain_region:"europe/france"
for (i = 0; i < parsed_query.query_list.length; i += 1) {
sub_query = parsed_query.query_list[i];
if (!local_role_found) {
result_list = isSingleLocalRoles(sub_query);
if (result_list) {
local_roles = result_list;
parsed_query.query_list.splice(i, 1);
query = jIO.Query.objectToSearchText(parsed_query);
local_role_found = true;
sub_query = parsed_query.query_list[i];
} else {
result_list = isMultipleLocalRoles(sub_query);
if (result_list) {
local_roles = result_list;
parsed_query.query_list.splice(i, 1);
query = jIO.Query.objectToSearchText(parsed_query);
local_role_found = true;
sub_query = parsed_query.query_list[i];
}
}
}
result_list = isSingleDomain(sub_query);
if (result_list) {
selection_domain_found = false;
for (key in result_list) {
if (result_list.hasOwnProperty(key) &&
((selection_domain === undefined) ||
(!selection_domain.hasOwnProperty(key)))) {
if (selection_domain === undefined) {
selection_domain = {};
}
selection_domain[key] = result_list[key];
selection_domain_found = true;
}
}
if (selection_domain_found === true) {
parsed_query.query_list.splice(i, 1);
query = jIO.Query.objectToSearchText(parsed_query);
i -= 1;
}
}
}
}
}
}
}
if (options.sort_on) {
for (i = 0; i < options.sort_on.length; i += 1) {
sort_list.push(JSON.stringify(options.sort_on[i]));
}
}
if (options.group_by) {
group_list = options.group_by;
}
if (selection_domain) {
selection_domain = JSON.stringify(selection_domain);
}
return ajax(storage, {
"type": "GET",
"url": UriTemplate.parse(site_hal._links.raw_search.href)
.expand({
query: query,
// XXX Force erp5 to return embedded document
select_list: options.select_list || ["title", "reference"],
limit: options.limit,
sort_on: sort_list,
group_by: group_list,
local_roles: local_roles,
selection_domain: selection_domain
})
});
})
.push(function (response) {
return JSON.parse(response.target.responseText);
})
.push(function (catalog_json) {
var data = catalog_json._embedded.contents,
count = data.length,
i,
uri,
item,
result = [];
for (i = 0; i < count; i += 1) {
item = data[i];
uri = new URI(item._links.self.href);
delete item._links;
result.push({
id: uri.segment(2),
value: item
});
}
return result;
});
};
jIO.addStorage("erp5", ERP5Storage);
}(jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery));
jio-master-src/src/jio.storage/fallbackstorage.js 0000664 0000000 0000000 00000005414 14230544473 0022424 0 ustar 00root root 0000000 0000000 /*
* JIO extension for resource replication.
* Copyright (C) 2021 Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO*/
(function (jIO) {
"use strict";
function FallbackStorage(spec) {
this._sub_storage = this._current_storage = jIO.createJIO(spec.sub_storage);
if (spec.hasOwnProperty('fallback_storage')) {
this._fallback_storage = jIO.createJIO(spec.fallback_storage);
this._checked = false;
} else {
this._checked = true;
}
}
var method_name_list = [
'get',
'put',
'post',
'remove',
'buildQuery',
'getAttachment',
'putAttachment',
'allAttachments',
'repair'
],
i;
function methodFallback(method_name) {
return function () {
var storage = this,
queue = storage._current_storage[method_name].apply(
storage._current_storage,
arguments
),
argument_list = arguments;
if (!storage._checked) {
queue
.push(function (result) {
storage._checked = true;
return result;
}, function (error) {
storage._checked = true;
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 500)) {
// If storage is not working, use fallback instead
storage._current_storage = storage._fallback_storage;
return storage._current_storage[method_name].apply(
storage._current_storage,
argument_list
);
}
throw error;
});
}
return queue;
};
}
for (i = 0; i < method_name_list.length; i += 1) {
FallbackStorage.prototype[method_name_list[i]] =
methodFallback(method_name_list[i]);
}
FallbackStorage.prototype.hasCapacity = function hasCapacity(name) {
return (this._sub_storage.hasCapacity(name) &&
this._fallback_storage.hasCapacity(name));
};
jIO.addStorage('fallback', FallbackStorage);
}(jIO));
jio-master-src/src/jio.storage/fbstorage.js 0000664 0000000 0000000 00000010710 14230544473 0021247 0 ustar 00root root 0000000 0000000 /*
* Copyright 2017, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true */
/*global RSVP, UriTemplate*/
(function (jIO, RSVP, UriTemplate) {
"use strict";
var GET_POST_URL = "https://graph.facebook.com/v2.9/{+post_id}" +
"?fields={+fields}&access_token={+access_token}",
get_post_template = UriTemplate.parse(GET_POST_URL),
GET_FEED_URL = "https://graph.facebook.com/v2.9/{+user_id}/feed" +
"?fields={+fields}&limit={+limit}&since={+since}&access_token=" +
"{+access_token}",
get_feed_template = UriTemplate.parse(GET_FEED_URL);
function FBStorage(spec) {
if (typeof spec.access_token !== 'string' || !spec.access_token) {
throw new TypeError("Access Token must be a string " +
"which contains more than one character.");
}
if (typeof spec.user_id !== 'string' || !spec.user_id) {
throw new TypeError("User ID must be a string " +
"which contains more than one character.");
}
this._access_token = spec.access_token;
this._user_id = spec.user_id;
this._default_field_list = spec.default_field_list || [];
this._default_limit = spec.default_limit || 500;
}
FBStorage.prototype.get = function (id) {
var that = this;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "GET",
url: get_post_template.expand({post_id: id,
fields: that._default_field_list, access_token: that._access_token})
});
})
.push(function (result) {
return JSON.parse(result.target.responseText);
});
};
function paginateResult(url, result, select_list) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "GET",
url: url
});
})
.push(function (response) {
return JSON.parse(response.target.responseText);
},
function (err) {
throw new jIO.util.jIOError("Getting feed failed " + err.toString(),
err.target.status);
})
.push(function (response) {
if (response.data.length === 0) {
return result;
}
var i, j, obj = {};
for (i = 0; i < response.data.length; i += 1) {
obj.id = response.data[i].id;
obj.value = {};
for (j = 0; j < select_list.length; j += 1) {
obj.value[select_list[j]] = response.data[i][select_list[j]];
}
result.push(obj);
obj = {};
}
return paginateResult(response.paging.next, result, select_list);
});
}
FBStorage.prototype.buildQuery = function (query) {
var that = this, fields = [], limit = this._default_limit,
template_argument = {
user_id: this._user_id,
limit: limit,
access_token: this._access_token
};
if (query.include_docs) {
fields = fields.concat(that._default_field_list);
}
if (query.select_list) {
fields = fields.concat(query.select_list);
}
if (query.limit) {
limit = query.limit[1];
}
template_argument.fields = fields;
template_argument.limit = limit;
return paginateResult(get_feed_template.expand(template_argument), [],
fields)
.push(function (result) {
if (!query.limit) {
return result;
}
return result.slice(query.limit[0], query.limit[1]);
});
};
FBStorage.prototype.hasCapacity = function (name) {
var this_storage_capacity_list = ["list", "select", "include", "limit"];
if (this_storage_capacity_list.indexOf(name) !== -1) {
return true;
}
};
jIO.addStorage('facebook', FBStorage);
}(jIO, RSVP, UriTemplate)); jio-master-src/src/jio.storage/gdrivestorage.js 0000664 0000000 0000000 00000017441 14230544473 0022150 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Google Drive Storage. Type = "gdrive".
* Google Drive "database" storage.
*/
/*global jIO, Blob, RSVP, UriTemplate, JSON*/
/*jslint nomen: true*/
(function (jIO, Blob, RSVP, UriTemplate, JSON) {
"use strict";
var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" +
"{?uploadType,access_token}",
upload_template = UriTemplate.parse(UPLOAD_URL),
REMOVE_URL = "https://www.googleapis.com/drive/v2/" +
"files{/id,trash}{?access_token}",
remove_template = UriTemplate.parse(REMOVE_URL),
LIST_URL = "https://www.googleapis.com/drive/v2/files" +
"?prettyPrint=false{&pageToken}&q=trashed=false" +
"&fields=nextPageToken,items(id){&access_token}",
list_template = UriTemplate.parse(LIST_URL),
GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}",
get_template = UriTemplate.parse(GET_URL);
function handleError(error, id) {
if (error.target.status === 404) {
throw new jIO.util.jIOError(
"Cannot find document: " + id,
404
);
}
throw error;
}
function listPage(result, token) {
var i,
obj;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": "GET",
"url": list_template.expand({
pageToken : (result.nextPageToken || ""),
access_token: token
})
});
})
.push(function (data) {
obj = JSON.parse(data.target.response || data.target.responseText);
for (i = 0; i < obj.items.length; i += 1) {
obj.items[i].value = {};
result.push(obj.items[i]);
}
result.nextPageToken = obj.nextPageToken;
return result;
}, handleError);
}
function checkName(name) {
if (name !== "enclosure") {
throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400);
}
}
/**
* The JIO Google Drive Storage extension
*
* @class GdriveStorage
* @constructor
*/
function GdriveStorage(spec) {
if (spec === undefined || spec.access_token === undefined ||
typeof spec.access_token !== 'string') {
throw new TypeError("Access Token must be a string " +
"which contains more than one character.");
}
if (spec.trashing !== undefined &&
(spec.trashing !== true && spec.trashing !== false)) {
throw new TypeError("trashing parameter" +
" must be a boolean (true or false)");
}
this._trashing = spec.trashing || true;
this._access_token = spec.access_token;
return;
}
function recursiveAllDocs(result, accessToken) {
return new RSVP.Queue()
.push(function () {
return listPage(result, accessToken);
})
.push(function () {
if (result.nextPageToken) {
return recursiveAllDocs(result, accessToken);
}
return result;
});
}
GdriveStorage.prototype.hasCapacity = function (name) {
return (name === "list");
};
GdriveStorage.prototype.buildQuery = function () {
return recursiveAllDocs([], this._access_token);
};
function sendMetaData(id, param, token) {
var boundary = "-------314159265358979323846";
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": id ? "PUT" : "POST",
"url": upload_template.expand({
access_token: token,
id: id || [],
upload: id ? [] : "upload",
uploadType: "multipart"
}),
headers: {
"Content-Type" : 'multipart/related; boundary="' + boundary + '"'
},
data: '--' + boundary + '\n' +
'Content-Type: application/json; charset=UTF-8\n\n' +
JSON.stringify(param) + '\n\n--' + boundary + "--"
});
})
.push(function (result) {
var obj = JSON.parse(result.target.responseText);
return obj.id;
},
function (error) {handleError(error, id); });
}
GdriveStorage.prototype.put = function (id, param) {
return sendMetaData(id, param, this._access_token);
};
GdriveStorage.prototype.post = function (param) {
return sendMetaData(undefined, param, this._access_token);
};
function sendData(id, blob, token) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": "PUT",
"url": upload_template.expand({
access_token: token,
upload: "upload",
id: id,
uploadType: "media"
}),
data: blob
});
})
.push(function (data) {
data = JSON.parse(data.target.responseText);
if (data.mimeType === "application/vnd.google-apps.folder") {
throw new jIO.util.jIOError("cannot put attachments to folder", 400);
}
return data;
}, function (error) {handleError(error, id); });
}
GdriveStorage.prototype.putAttachment = function (id, name, blob) {
checkName(name);
return sendData(id, blob, this._access_token);
};
GdriveStorage.prototype.remove = function (id) {
var that = this;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: that._trashing ? "POST" : "DELETE",
url: remove_template.expand({
id : id,
access_token : that._access_token,
trash : that._trashing ? "trash" : []
})
});
})
.push(undefined, function (error) {handleError(error, id); });
};
function getData(id, attach, token) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "GET",
dataType: attach ? "blob" : "json",
url: get_template.expand({
id: id,
alt: attach ? "media" : [],
access_token: token
}),
headers: {
"Authorization" : "Bearer " + token
}
});
})
.push(function (evt) {
return evt.target.response ||
(attach ? new Blob([evt.target.responseText],
{"type" :
evt.target.responseHeaders["Content-Type"]}) :
JSON.parse(evt.target.responseText));
}, function (error) {handleError(error, id); });
}
GdriveStorage.prototype.get = function (id) {
return getData(id, false, this._access_token);
};
GdriveStorage.prototype.getAttachment = function (id, name) {
checkName(name);
return getData(id, true, this._access_token);
};
GdriveStorage.prototype.allAttachments = function (id) {
var token = this._access_token;
return new RSVP.Queue()
.push(function () {
return getData(id, false, token);
})
.push(function (data) {
if (data.mimeType === "application/vnd.google-apps.folder") {
return {};
}
return {"enclosure": {}};
});
};
jIO.addStorage('gdrive', GdriveStorage);
}(jIO, Blob, RSVP, UriTemplate, JSON));
jio-master-src/src/jio.storage/gidstorage.js 0000664 0000000 0000000 00000044545 14230544473 0021440 0 ustar 00root root 0000000 0000000 /*
* JIO extension for resource global identifier management.
* Copyright (C) 2013 Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global define, jIO */
/**
* JIO GID Storage. Type = 'gid'.
* Identifies document with their global identifier representation
*
* Sub storages must support queries and include_docs options.
*
* Storage Description:
*
* {
* "type": "gid",
* "sub_storage": {},
* "constraints": {
* "default": {
* "identifier": "list", // ['a', 1]
* "type": "DCMIType", // 'Text'
* "title": "string" // 'something blue'
* },
* "Text": {
* "format": "contentType" // contains 'text/plain;charset=utf-8'
* },
* "Image": {
* "version": "json" // value as is
* }
* }
* }
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO);
}(['jio'], function (jIO) {
"use strict";
var dcmi_types, metadata_actions, content_type_re, tool;
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'
};
metadata_actions = {
/**
* Returns the metadata value
*/
json: function (value) {
return value;
},
/**
* Returns the metadata if there is a string
*/
string: function (value) {
if (!Array.isArray(value)) {
if (typeof value === 'object') {
return value.content;
}
return value;
}
},
/**
* Returns the metadata in a array format
*/
list: function (value) {
var i, new_value = [];
if (Array.isArray(value)) {
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[new_value.length] = value[i].content;
} else {
new_value[new_value.length] = value[i];
}
}
} else if (value !== undefined) {
value = [value];
}
return value;
},
/**
* Returns the metadata if there is a string equal to a DCMIType
*/
DCMIType: function (value) {
var i;
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object' && dcmi_types[value[i].content]) {
return value[i].content;
}
if (dcmi_types[value[i]]) {
return value[i];
}
}
},
/**
* Returns the metadata content type if exist
*/
contentType: function (value) {
var i;
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (value[i] === 'object') {
if (content_type_re.test(value[i].content)) {
return value[i].content;
}
} else {
if (content_type_re.test(value[i])) {
return value[i];
}
}
}
},
/**
* Returns the metadata if it is a date
*/
date: function (value) {
var d;
if (!Array.isArray(value)) {
if (typeof value === 'object') {
d = new Date(value.content);
value = value.content;
} else {
d = new Date(value);
}
}
if (Object.prototype.toString.call(d) === "[object Date]") {
if (!isNaN(d.getTime())) {
return value;
}
}
}
};
content_type_re = new RegExp(
'^([a-z]+\\/[a-zA-Z0-9\\+\\-\\.]+)' +
'((?:\\s*;\\s*[a-zA-Z\\+\\-\\.]+\\s*=' +
'\\s*[a-zA-Z0-9\\-\\+\\.,]+)*)$'
);
tool = {
"deepClone": jIO.util.deepClone
};
/**
* Creates a gid from metadata and constraints.
*
* @param {Object} metadata The metadata to use
* @param {Object} constraints The constraints
* @return {String} The gid or undefined if metadata doesn't respect the
* constraints
*/
function gidFormat(metadata, constraints) {
var types, i, j, meta_key, result = [], tmp, constraint, actions;
types = (metadata_actions.list(metadata.type) || []).slice();
types.unshift('default');
for (i = 0; i < types.length; i += 1) {
constraint = constraints[types[i]];
for (meta_key in constraint) {
if (constraint.hasOwnProperty(meta_key)) {
actions = constraint[meta_key];
if (!Array.isArray(actions)) {
actions = [actions];
}
for (j = 0; j < actions.length; j += 1) {
tmp = metadata_actions[
actions[j]
](metadata[meta_key]);
if (tmp === undefined) {
return;
}
}
result[result.length] = [meta_key, tmp];
}
}
}
// sort dict keys to make gid universal
result.sort(function (a, b) {
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
});
tmp = {};
for (i = 0; i < result.length; i += 1) {
tmp[result[i][0]] = result[i][1];
}
return JSON.stringify(tmp);
}
/**
* Convert a gid to a jio query.
*
* @param {Object,String} gid The gid
* @return {Object} A jio serialized query
*/
function gidToJIOQuery(gid) {
var k, i, result = [], meta, content;
if (typeof gid === 'string') {
gid = JSON.parse(gid);
}
for (k in gid) {
if (gid.hasOwnProperty(k)) {
meta = gid[k];
if (!Array.isArray(meta)) {
meta = [meta];
}
for (i = 0; i < meta.length; i += 1) {
content = meta[i];
if (typeof content === 'object') {
content = content.content;
}
result[result.length] = {
"type": "simple",
"operator": "=",
"key": k,
"value": content
};
}
}
}
return {
"type": "complex",
"operator": "AND",
"query_list": result
};
}
/**
* Parse the gid and returns a metadata object containing gid keys and values.
*
* @param {String} gid The gid to convert
* @param {Object} constraints The constraints
* @return {Object} The gid metadata
*/
function gidParse(gid, constraints) {
var object;
try {
object = JSON.parse(gid);
} catch (e) {
return;
}
if (gid !== gidFormat(object, constraints)) {
return;
}
return object;
}
/**
* The gid storage used by JIO.
*
* This storage change the id of a document with its global id. A global id
* is representation of a document metadata used to define it as uniq. The way
* to generate global ids can be define in the storage description. It allows
* us use duplicating storage with different sub storage kind.
*
* @class GidStorage
*/
function GidStorage(spec) {
var that = this, priv = {};
priv.sub_storage = spec.sub_storage;
priv.constraints = spec.constraints || {
"default": {
"type": "DCMIType",
"title": "string"
}
};
// JIO Commands
/**
* Generic command for post or put one.
*
* This command will check if the document already exist with an allDocs
* and a jio query. If exist, then post will fail. Put will update the
* retrieved document thanks to its real id. If no documents are found, post
* and put will create a new document with the sub storage id generator.
*
* @method putOrPost
* @private
* @param {Command} command The JIO command
* @param {String} method The command method
*/
priv.putOrPost = function (command, metadata, method) {
var gid, jio_query, doc = tool.deepClone(metadata);
gid = gidFormat(doc, priv.constraints);
if (gid === undefined || (doc._id && gid !== doc._id)) {
return command.error(
"bad_request",
"metadata should respect constraints",
"Cannot " + method + " document"
);
}
jio_query = gidToJIOQuery(gid);
command.storage(priv.sub_storage).allDocs({
"query": jio_query
}).then(function (response) {
var update_method = method;
response = response.data;
if (response.total_rows !== 0) {
if (method === 'post') {
return command.error(
"conflict",
"Document already exists",
"Cannot " + method + " document"
);
}
doc = tool.deepClone(metadata);
doc._id = response.rows[0].id;
} else {
doc = tool.deepClone(metadata);
delete doc._id;
update_method = 'post';
}
command.storage(priv.sub_storage)[update_method](
doc
).then(function (response) {
response.id = gid;
command.success(response);
}, function (err) {
err.message = "Cannot " + method + " document";
command.error(err);
});
}, function (err) {
err.message = "Cannot " + method + " document";
command.error(err);
});
};
/**
* Generic command for putAttachment, getAttachment or removeAttachment.
*
* This command will check if the document exist with an allDocs and a
* jio query. If not exist, then it returns 404. Otherwise the
* action will be done on the attachment of the found document.
*
* @method putGetOrRemoveAttachment
* @private
* @param {Object} command The JIO command
* @param {Object} doc The command parameters
* @param {String} method The command method
*/
priv.putGetOrRemoveAttachment = function (command, doc, method) {
var gid_object, jio_query;
gid_object = gidParse(doc._id, priv.constraints);
if (gid_object === undefined) {
return command.error(
"bad_request",
"metadata should respect constraints",
"Cannot " + method + " attachment"
);
}
jio_query = gidToJIOQuery(gid_object);
command.storage(priv.sub_storage).allDocs({
"query": jio_query
}).then(function (response) {
response = response.data;
if (response.total_rows === 0) {
return command.error(
"not_found",
"Document already exists",
"Cannot " + method + " attachment"
);
}
gid_object = doc._id;
doc._id = response.rows[0].id;
command.storage(priv.sub_storage)[method + "Attachment"](
doc
).then(function (response) {
response.id = gid_object;
command.success(response);
}, function (err) {
err.message = "Cannot " + method + " attachment";
command.error(err);
});
}, function (err) {
err.message = "Cannot " + method + " attachment";
command.error(err);
});
};
/**
* See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}.
*
* @method post
* @param {Command} command The JIO command
*/
that.post = function (command, metadata) {
priv.putOrPost(command, metadata, 'post');
};
/**
* See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}.
*
* @method put
* @param {Command} command The JIO command
*/
that.put = function (command, metadata) {
priv.putOrPost(command, metadata, 'put');
};
/**
* Puts an attachment to a document thank to its gid, a sub allDocs and a
* jio query.
*
* @method putAttachment
* @param {Command} command The JIO command
*/
that.putAttachment = function (command, param) {
priv.putGetOrRemoveAttachment(command, param, 'put');
};
/**
* Gets a document thank to its gid, a sub allDocs and a jio query.
*
* @method get
* @param {Object} command The JIO command
*/
that.get = function (command, param) {
var gid_object, jio_query;
gid_object = gidParse(param._id, priv.constraints);
if (gid_object === undefined) {
return command.error(
"bad_request",
"metadata should respect constraints",
"Cannot get document"
);
}
jio_query = gidToJIOQuery(gid_object);
command.storage(priv.sub_storage).allDocs({
"query": jio_query,
"include_docs": true
}).then(function (response) {
response = response.data;
if (response.total_rows === 0) {
return command.error(
"not_found",
"missing",
"Cannot get document"
);
}
response.rows[0].doc._id = param._id;
return command.success({"data": response.rows[0].doc});
}, function (err) {
err.message = "Cannot get document";
return command.error(err);
});
};
/**
* Gets an attachment from a document thank to its gid, a sub allDocs and a
* jio query.
*
* @method getAttachment
* @param {Command} command The JIO command
*/
that.getAttachment = function (command, param) {
priv.putGetOrRemoveAttachment(command, param, 'get');
};
/**
* Remove a document thank to its gid, sub allDocs and a jio query.
*
* @method remove
* @param {Command} command The JIO command.
*/
that.remove = function (command, doc) {
var gid_object, jio_query;
gid_object = gidParse(doc._id, priv.constraints);
if (gid_object === undefined) {
return command.error(
"bad_request",
"metadata should respect constraints",
"Cannot remove document"
);
}
jio_query = gidToJIOQuery(gid_object);
command.storage(priv.sub_storage).allDocs({
"query": jio_query
}).then(function (response) {
response = response.data;
if (response.total_rows === 0) {
return command.error(
"not_found",
"missing",
"Cannot remove document"
);
}
gid_object = doc._id;
doc = {"_id": response.rows[0].id};
command.storage(priv.sub_storage).remove(
doc
).then(function (response) {
response.id = gid_object;
command.success(response);
}, function (err) {
err.message = "Cannot remove document";
command.error(err);
});
}, function (err) {
err.message = "Cannot remove document";
command.error(err);
});
};
/**
* Removes an attachment to a document thank to its gid, a sub allDocs and a
* jio query.
*
* @method removeAttachment
* @param {Command} command The JIO command
*/
that.removeAttachment = function (command, param) {
priv.putGetOrRemoveAttachment(command, param, 'remove');
};
/**
* Retrieve a list of document which respect gid constraints.
*
* @method allDocs
* @param {Command} command The JIO command
*/
that.allDocs = function (command, param, options) {
/*jslint unparam: true */
var include_docs;
include_docs = options.include_docs;
options.include_docs = true;
command.storage(priv.sub_storage).allDocs(
options
).then(function (response) {
/*jslint ass: true */
var result = [], doc_gids = {}, row, gid;
response = response.data;
while ((row = response.rows.shift()) !== undefined) {
gid = gidFormat(row.doc, priv.constraints);
if (gid !== undefined) {
if (!doc_gids[gid]) {
doc_gids[gid] = true;
row.id = gid;
delete row.key;
result[result.length] = row;
if (include_docs === true) {
row.doc._id = gid;
} else {
delete row.doc;
}
}
}
}
doc_gids = undefined; // free memory
row = undefined;
command.success({"data": {
"total_rows": result.length,
"rows": result
}});
}, function (err) {
err.message = "Cannot get all documents";
return command.error(err);
});
};
that.check = function (command, param, options) {
return that.repair(command, param, options, "check");
};
that.repair = function (command, param, options, action) {
var gid_object, jio_query, sub_storage;
if (typeof param._id !== "string" || !param._id) {
return command.error("bad_request", "document id must be provided");
}
if (action === undefined) {
action = "repair";
}
gid_object = gidParse(param._id, priv.constraints);
if (gid_object === undefined) {
return command.error(
"bad_request",
"metadata should respect constraints",
"Cannot " + action + " document"
);
}
jio_query = gidToJIOQuery(gid_object);
sub_storage = command.storage(priv.sub_storage);
sub_storage.allDocs({
"query": jio_query
}).then(function (response) {
response = response.data;
if (response.total_rows === 0) {
// document not found, nothing to repair or check
return command.success();
}
return sub_storage[action]({"_id": response.rows[0].id}, options);
}).then(command.success, command.error, command.notify);
};
}
jIO.addStorage('gid', GidStorage);
}));
jio-master-src/src/jio.storage/httpstorage.js 0000664 0000000 0000000 00000007063 14230544473 0021646 0 ustar 00root root 0000000 0000000 /*
* Copyright 2017, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*global RSVP, Blob*/
/*jslint nomen: true*/
(function (jIO, RSVP, Blob) {
"use strict";
function HttpStorage(spec) {
if (spec.hasOwnProperty('catch_error')) {
this._catch_error = spec.catch_error;
} else {
this._catch_error = false;
}
// If timeout not set, use 0 for no timeout value
this._timeout = spec.timeout || 0;
}
HttpStorage.prototype.get = function (id) {
var context = this;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: 'HEAD',
url: id,
timeout: context._timeout
});
})
.push(undefined, function (error) {
if (context._catch_error) {
return error;
}
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find url " + id, 404);
}
throw error;
})
.push(function (response) {
var key_list = ["Content-Disposition", "Content-Type", "Date",
"Last-Modified", "Vary", "Cache-Control", "Etag",
"Accept-Ranges", "Content-Range"],
i,
key,
value,
result = {};
result.Status = response.target.status;
for (i = 0; i < key_list.length; i += 1) {
key = key_list[i];
value = response.target.getResponseHeader(key);
if (value !== null) {
result[key] = value;
}
}
return result;
});
};
HttpStorage.prototype.allAttachments = function () {
return {enclosure: {}};
};
HttpStorage.prototype.getAttachment = function (id, name) {
var context = this;
if (name !== 'enclosure') {
throw new jIO.util.jIOError("Forbidden attachment: "
+ id + " , " + name,
400);
}
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: 'GET',
url: id,
dataType: "blob",
timeout: context._timeout
});
})
.push(undefined, function (error) {
if (context._catch_error) {
return error;
}
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find url " + id, 404);
}
throw error;
})
.push(function (response) {
return new Blob(
[response.target.response || response.target.responseText],
{"type": response.target.getResponseHeader('Content-Type') ||
"application/octet-stream"}
);
});
};
jIO.addStorage('http', HttpStorage);
}(jIO, RSVP, Blob)); jio-master-src/src/jio.storage/indexeddbstorage.js 0000664 0000000 0000000 00000053654 14230544473 0022624 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Indexed Database Storage.
*
* A local browser "database" storage greatly more powerful than localStorage.
*
* Description:
*
* {
* "type": "indexeddb",
* "database":
* }
*
* The database name will be prefixed by "jio:", so if the database property is
* "hello", then you can manually reach this database with
* `indexedDB.open("jio:hello");`. (Or
* `indexedDB.deleteDatabase("jio:hello");`.)
*
* For more informations:
*
* - http://www.w3.org/TR/IndexedDB/
* - https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB
*/
/*jslint nomen: true */
/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest,
DOMError, DOMException, Set*/
(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest,
DOMError, DOMException, Set) {
"use strict";
// Read only as changing it can lead to data corruption
var UNITE = 2000000,
INDEX_PREFIX = 'doc.';
function IndexedDBStorage(description) {
if (typeof description.database !== "string" ||
description.database === "") {
throw new TypeError("IndexedDBStorage 'database' description property " +
"must be a non-empty string");
}
this._database_name = "jio:" + description.database;
this._version = description.version;
this._index_key_list = description.index_key_list || [];
}
IndexedDBStorage.prototype.hasCapacity = function (name) {
return ((name === "list") || (name === "include"));
};
function buildKeyPath(key_list) {
return key_list.join("_");
}
function handleUpgradeNeeded(evt, index_key_list) {
var db = evt.target.result,
store,
current_store_list = Array.from(db.objectStoreNames),
current_index_list,
i,
index_key;
if (current_store_list.indexOf("metadata") === -1) {
store = db.createObjectStore("metadata", {
keyPath: "_id",
autoIncrement: false
});
// It is not possible to use openKeyCursor on keypath directly
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=19955
store.createIndex("_id", "_id", {unique: true});
} else {
store = evt.target.transaction.objectStore("metadata");
}
current_index_list = new Set(store.indexNames);
current_index_list.delete("_id");
for (i = 0; i < index_key_list.length; i += 1) {
// Prefix the index name to prevent conflict with _id
index_key = INDEX_PREFIX + index_key_list[i];
if (current_index_list.has(index_key)) {
current_index_list.delete(index_key);
} else {
store.createIndex(index_key, index_key,
{unique: false});
}
}
current_index_list = Array.from(current_index_list);
for (i = 0; i < current_index_list.length; i += 1) {
store.deleteIndex(current_index_list[i]);
}
if (current_store_list.indexOf("attachment") === -1) {
store = db.createObjectStore("attachment", {
keyPath: "_key_path",
autoIncrement: false
});
store.createIndex("_id", "_id", {unique: false});
}
if (current_store_list.indexOf("blob") === -1) {
store = db.createObjectStore("blob", {
keyPath: "_key_path",
autoIncrement: false
});
store.createIndex("_id_attachment",
["_id", "_attachment"], {unique: false});
store.createIndex("_id", "_id", {unique: false});
}
}
function waitForOpenIndexedDB(storage, callback) {
var request,
db_name = storage._database_name;
function canceller() {
if ((request !== undefined) && (request.result !== undefined)) {
request.result.close();
}
}
function resolver(resolve, reject) {
// Open DB //
request = indexedDB.open(db_name, storage._version);
request.onerror = function (error) {
canceller();
if ((error !== undefined) &&
(error.target instanceof IDBOpenDBRequest) &&
((error.target.error instanceof DOMError) ||
(error.target.error instanceof DOMException))) {
reject(new jIO.util.jIOError(
"Connection to: " + db_name + " failed: " +
error.target.error.message,
500
));
} else {
reject(error);
}
};
request.onabort = function () {
canceller();
reject("Aborting connection to: " + db_name);
};
request.ontimeout = function () {
reject("Connection to: " + db_name + " timeout");
};
request.onblocked = function () {
canceller();
reject("Connection to: " + db_name + " was blocked");
};
// Create DB if necessary //
request.onupgradeneeded = function (evt) {
handleUpgradeNeeded(evt, storage._index_key_list);
};
request.onversionchange = function () {
canceller();
reject(db_name + " was upgraded");
};
request.onsuccess = function () {
var result;
try {
result = callback(request.result);
} catch (error) {
reject(error);
}
return new RSVP.Queue(result)
.push(function (final_result) {
canceller();
resolve(final_result);
}, function (error) {
canceller();
reject(error);
});
};
}
return new RSVP.Promise(resolver, canceller);
}
function waitForTransaction(db, stores, flag, callback) {
var tx = db.transaction(stores, flag);
function canceller() {
try {
tx.abort();
} catch (unused) {
// Transaction already finished
return;
}
}
function resolver(resolve, reject) {
var result;
try {
result = callback(tx);
} catch (error) {
reject(error);
}
tx.oncomplete = function () {
return new RSVP.Queue(result)
.push(resolve, function (error) {
canceller();
reject(error);
});
};
tx.onerror = reject;
tx.onabort = reject;
}
return new RSVP.Promise(resolver, canceller);
}
function waitForIDBRequest(request) {
return new RSVP.Promise(function (resolve, reject) {
request.onerror = reject;
request.onsuccess = resolve;
});
}
function waitForAllSynchronousCursor(request, callback) {
var force_cancellation = false;
function canceller() {
force_cancellation = true;
}
function resolver(resolve, reject) {
request.onerror = reject;
request.onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor && !force_cancellation) {
try {
callback(cursor);
} catch (error) {
reject(error);
}
// continue to next iteration
cursor["continue"]();
} else {
resolve();
}
};
}
return new RSVP.Promise(resolver, canceller);
}
IndexedDBStorage.prototype.buildQuery = function (options) {
var result_list = [],
context = this;
function pushIncludedMetadata(cursor) {
result_list.push({
"id": cursor.primaryKey,
"value": {},
"doc": cursor.value.doc
});
}
function pushMetadata(cursor) {
result_list.push({
"id": cursor.primaryKey,
"value": {}
});
}
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["metadata"], "readonly",
function (tx) {
var key = "_id";
if (options.include_docs === true) {
return waitForAllSynchronousCursor(
tx.objectStore("metadata").index(key).openCursor(),
pushIncludedMetadata
);
}
return waitForAllSynchronousCursor(
tx.objectStore("metadata").index(key).openKeyCursor(),
pushMetadata
);
});
});
})
.push(function () {
return result_list;
});
};
IndexedDBStorage.prototype.get = function (id) {
var context = this;
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["metadata"], "readonly",
function (tx) {
return waitForIDBRequest(tx.objectStore("metadata").get(id));
});
});
})
.push(function (evt) {
if (evt.target.result) {
return evt.target.result.doc;
}
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id + "' in the 'metadata' store",
404
);
});
};
IndexedDBStorage.prototype.allAttachments = function (id) {
var attachment_dict = {},
context = this;
function addEntry(cursor) {
attachment_dict[cursor.primaryKey.slice(cursor.key.length + 1)] = {};
}
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["metadata", "attachment"], "readonly",
function (tx) {
return RSVP.all([
waitForIDBRequest(tx.objectStore("metadata").get(id)),
waitForAllSynchronousCursor(
tx.objectStore("attachment").index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
addEntry
)
]);
});
});
})
.push(function (result_list) {
var evt = result_list[0];
if (!evt.target.result) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id +
"' in the 'metadata' store",
404
);
}
return attachment_dict;
});
};
IndexedDBStorage.prototype.put = function (id, metadata) {
return waitForOpenIndexedDB(this, function (db) {
return waitForTransaction(db, ["metadata"], "readwrite",
function (tx) {
return waitForIDBRequest(tx.objectStore("metadata").put({
"_id": id,
"doc": metadata
}));
});
});
};
IndexedDBStorage.prototype.remove = function (id) {
return waitForOpenIndexedDB(this, function (db) {
return waitForTransaction(db, ["metadata", "attachment", "blob"],
"readwrite", function (tx) {
var promise_list = [],
metadata_store = tx.objectStore("metadata"),
attachment_store = tx.objectStore("attachment"),
blob_store = tx.objectStore("blob");
function deleteAttachment(cursor) {
promise_list.push(
waitForIDBRequest(attachment_store.delete(cursor.primaryKey))
);
}
function deleteBlob(cursor) {
promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
}
return RSVP.all([
waitForIDBRequest(metadata_store.delete(id)),
waitForAllSynchronousCursor(
attachment_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteAttachment
),
waitForAllSynchronousCursor(
blob_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteBlob
),
])
.then(function () {
return RSVP.all(promise_list);
});
});
});
};
IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
if (options === undefined) {
options = {};
}
var start,
end,
array_buffer_list = [],
context = this;
start = options.start || 0;
end = options.end;
// Stream the blob content
if ((start !== 0) || (end !== undefined)) {
if (start < 0 || ((end !== undefined) && (end < 0))) {
throw new jIO.util.jIOError(
"_start and _end must be positive",
400
);
}
if ((end !== undefined) && (start > end)) {
throw new jIO.util.jIOError("_start is greater than _end",
400);
}
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
blob_store = tx.objectStore("blob"),
start_index,
end_index,
promise_list = [];
start_index = Math.floor(start / UNITE);
if (end !== undefined) {
end_index = Math.floor(end / UNITE);
if (end % UNITE === 0) {
end_index -= 1;
}
}
function getBlobKey(cursor) {
var index = parseInt(
cursor.primaryKey.slice(key_path.length + 1),
10
);
if ((start !== 0) && (index < start_index)) {
// No need to fetch blobs at the start
return;
}
if ((end !== undefined) && (index > end_index)) {
// No need to fetch blobs at the end
return;
}
// Sort the blob by their index
promise_list.splice(
index - start_index,
0,
waitForIDBRequest(blob_store.get(cursor.primaryKey))
);
}
// Get all blob keys to check if they must be fetched
return waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
getBlobKey
)
.then(function () {
return RSVP.all(promise_list);
});
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
index,
i;
for (i = 0; i < result_list.length; i += 1) {
array_buffer_list.push(result_list[i].target.result.blob);
}
blob = new Blob(array_buffer_list,
{type: "application/octet-stream"});
index = Math.floor(start / UNITE) * UNITE;
if (end === undefined) {
end = blob.size;
} else {
end = end - index;
}
return blob.slice(start - index, end,
"application/octet-stream");
});
}
// Request the full blob
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["attachment", "blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
attachment_store = tx.objectStore("attachment"),
blob_store = tx.objectStore("blob");
function getBlob(cursor) {
var index = parseInt(
cursor.primaryKey.slice(key_path.length + 1),
10
);
// Sort the blob by their index
array_buffer_list.splice(
index,
0,
cursor.value.blob
);
}
return RSVP.all([
// Get the attachment info (mime type)
waitForIDBRequest(attachment_store.get(
key_path
)),
// Get all needed blobs
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])),
getBlob
)
]);
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
attachment = result_list[0].target.result;
// Should raise if key is not good
if (!attachment) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store",
404
);
}
blob = new Blob(array_buffer_list,
{type: attachment.info.content_type});
if (blob.size !== attachment.info.length) {
throw new jIO.util.jIOError(
"IndexedDB: attachment '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store is broken",
500
);
}
return blob;
});
};
IndexedDBStorage.prototype.putAttachment = function (id, name, blob) {
var context = this;
return new RSVP.Queue()
.push(function () {
// Split the blob first
return jIO.util.readBlobAsArrayBuffer(blob);
})
.push(function (event) {
var array_buffer = event.target.result,
blob_part = [],
total_size = blob.size,
handled_size = 0;
while (handled_size < total_size) {
blob_part.push(array_buffer.slice(handled_size,
handled_size + UNITE));
handled_size += UNITE;
}
return waitForOpenIndexedDB(context, function (db) {
return waitForTransaction(db, ["attachment", "blob"], "readwrite",
function (tx) {
var blob_store,
promise_list,
delete_promise_list = [],
key_path = buildKeyPath([id, name]),
i;
// First write the attachment info on top of previous
promise_list = [
waitForIDBRequest(tx.objectStore("attachment").put({
"_key_path": key_path,
"_id": id,
"_attachment": name,
"info": {
"content_type": blob.type,
"length": blob.size
}
}))
];
// Then, write all blob parts on top of previous
blob_store = tx.objectStore("blob");
for (i = 0; i < blob_part.length; i += 1) {
promise_list.push(
waitForIDBRequest(blob_store.put({
"_key_path": buildKeyPath([id, name, i]),
"_id" : id,
"_attachment" : name,
"_part" : i,
"blob": blob_part[i]
}))
);
}
function deleteEntry(cursor) {
var index = parseInt(
cursor.primaryKey.slice(key_path.length + 1),
10
);
if (index >= blob_part.length) {
delete_promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
}
}
// Finally, remove all remaining blobs
promise_list.push(
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
);
return RSVP.all(promise_list)
.then(function () {
if (delete_promise_list.length) {
return RSVP.all(delete_promise_list);
}
});
});
});
});
};
IndexedDBStorage.prototype.removeAttachment = function (id, name) {
return waitForOpenIndexedDB(this, function (db) {
return waitForTransaction(db, ["attachment", "blob"], "readwrite",
function (tx) {
var promise_list = [],
attachment_store = tx.objectStore("attachment"),
blob_store = tx.objectStore("blob");
function deleteEntry(cursor) {
promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
}
return RSVP.all([
waitForIDBRequest(
attachment_store.delete(buildKeyPath([id, name]))
),
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
])
.then(function () {
return RSVP.all(promise_list);
});
});
});
};
jIO.addStorage("indexeddb", IndexedDBStorage);
}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange, IDBOpenDBRequest, DOMError,
DOMException, Set));
jio-master-src/src/jio.storage/indexstorage.js 0000664 0000000 0000000 00000064327 14230544473 0022004 0 ustar 00root root 0000000 0000000 /*
* JIO extension for resource indexing.
* Copyright (C) 2013 Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global window, exports, require, define, jIO, RSVP */
/**
* JIO Index Storage.
* Manages indexes for specified storages.
* Description:
* {
* "type": "index",
* "indices": [{
* "id": "index_title_subject.json", // doc id where to store indices
* "index": ["title", "subject"], // metadata to index
* "attachment": "youhou", // default "body"
* "metadata": { // default {}
* "type": "Dataset",
* "format": "application/json",
* "date": "yyyy-mm-ddTHH:MM:SS+HH:MM",
* "title": "My index database",
* "creator": "Me"
* },
* "sub_storage":
* (default equal to parent sub_storage field)
* }, {
* "id": "index_year.json",
* "index": "year"
* ...
* }],
* "sub_storage":
* }
*
* Sent document metadata will be:
* index_titre_subject.json
* {
* "_id": "index_title_subject.json",
* "type": "Dataset",
* "format": "application/json",
* "date": "yyyy-mm-ddTHH:MM:SS+HH:MM",
* "title": "My index database",
* "creator": "Me",
* "_attachments": {
* "youhou": {
* "length": Num,
* "digest": "XXX",
* "content_type": "application/json"
* }
* }
* }
* Attachment "youhou"
* {
* "indexing": ["title", "subject"],
* "free": [0],
* "location": {
* "foo": 1,
* "bar": 2,
* ...
* },
* "database": [
* {},
* {"_id": "foo", "title": "...", "subject": ...},
* {"_id": "bar", "title": "...", "subject": ...},
* ...
* ]
* }
*
* index_year.json
* {
* "_id": "index_year.json",
* "_attachments": {
* "body": {..}
* }
* }
* Attachment "body"
* {
* "indexing": ["year"],
* "free": [1],
* "location": {
* "foo": 0,
* "bar": 2,
* ...
* },
* "database": [
* {"_id": "foo", "year": "..."},
* {},
* {"_id": "bar", "year": "..."},
* ...
* ]
* }
*
* A put document will be indexed to the free location if exist, else it will be
* indexed at the end of the database. The document id will be indexed, also, in
* 'location' to quickly replace metadata.
*
* Only one or two loops are executed:
* - one to filter retrieved document list (no query -> no loop)
* - one to format the result to a JIO response
*/
// 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('jio'),
require('rsvp')
);
}
window.index_storage = {};
module(window.index_storage, jIO, RSVP);
}([
'exports',
'jio',
'rsvp'
], function (exports, jIO, RSVP) {
"use strict";
/**
* A JSON Index manipulator
*
* @class JSONIndex
* @constructor
*/
function JSONIndex(spec) {
var that = this;
spec = spec || {};
/**
* The document id
*
* @property _id
* @type String
*/
that._id = spec._id;
/**
* The attachment id
*
* @property _attachment
* @type String
*/
that._attachment = spec._attachment;
/**
* The array with metadata key to index
*
* @property _indexing
* @type Array
*/
that._indexing = spec.indexing || [];
/**
* The array of free location index
*
* @property _free
* @type Array
* @default []
*/
that._free = spec.free || [];
/**
* The dictionnary document id -> database index
*
* @property _location
* @type Object
* @default {}
*/
that._location = spec.location || {};
/**
* The database array containing document metadata
*
* @property _database
* @type Array
* @default []
*/
that._database = spec.database || [];
/**
* True if it has been modified
*
* @property modified
* @type Boolean
* @default false
*/
that.modified = false;
/**
* Updates the modified date
*
* @method touch
*/
that.touch = function () {
that.modified = true;
};
/**
* Adds a metadata object in the database, replace if already exist
*
* @method put
* @param {Object} meta The metadata to add
* @return {Boolean} true if added, false otherwise
*/
that.put = function (meta) {
var k, needed_meta = {}, ok = false;
if (typeof meta._id !== "string" || meta._id === "") {
throw new TypeError("Corrupted Metadata");
}
for (k in meta) {
if (meta.hasOwnProperty(k)) {
if (k[0] === "_") {
if (k === "_id") {
needed_meta[k] = meta[k];
}
} else if (that._indexing_object[k]) {
needed_meta[k] = meta[k];
ok = true;
}
}
}
if (ok) {
if (typeof that._location[meta._id] === "number") {
that._database[that._location[meta._id]] = needed_meta;
} else if (that._free.length > 0) {
k = that._free.shift();
that._database[k] = needed_meta;
that._location[meta._id] = k;
} else {
that._database.push(needed_meta);
that._location[meta._id] = that._database.length - 1;
}
that.modified = true;
return true;
}
if (typeof that._location[meta._id] === "number") {
return that.remove(meta);
}
return false;
};
/**
* Removes a metadata object from the database if exist
*
* @method remove
* @param {Object} meta The metadata to remove
* @return {Boolean} true if removed else false
*/
that.remove = function (meta) {
if (typeof meta._id !== "string") {
throw new TypeError("Corrupted Metadata");
}
if (typeof that._location[meta._id] !== "number") {
// throw new ReferenceError("Not Found");
return false;
}
that._database[that._location[meta._id]] = null;
that._free.push(that._location[meta._id]);
delete that._location[meta._id];
that.modified = true;
return true;
};
/**
* Checks if the index database document is correct
*
* @method check
*/
that.check = function () {
var id, database_meta;
if (typeof that._id !== "string" ||
that._id === "" ||
typeof that._attachment !== "string" ||
that._attachment === "" ||
!Array.isArray(that._free) ||
!Array.isArray(that._indexing) ||
typeof that._location !== 'object' ||
Array.isArray(that._location) ||
!Array.isArray(that._database) ||
that._indexing.length === 0) {
throw new TypeError("Corrupted Index");
}
for (id in that._location) {
if (that._location.hasOwnProperty(id)) {
database_meta = that._database[that._location[id]];
if (typeof database_meta !== 'object' ||
Object.getPrototypeOf(database_meta || []) !== Object.prototype ||
database_meta._id !== id) {
throw new TypeError("Corrupted Index");
}
}
}
};
that.equals = function (json_index) {
function equalsDirection(a, b) {
var k;
for (k in a._location) {
if (a._location.hasOwnProperty(k)) {
if (b._location[k] === undefined ||
JSON.stringify(b._database[b._location[k]]) !==
JSON.stringify(a._database[a._location[k]])) {
return false;
}
}
}
return true;
}
if (!equalsDirection(that, json_index)) {
return false;
}
if (!equalsDirection(json_index, that)) {
return false;
}
return true;
};
that.checkDocument = function (doc) {
var i, key, db_doc;
if (typeof that._location[doc._id] !== "number") {
throw new TypeError("Different Index");
}
db_doc = that._database(that._location[doc._id])._id;
if (db_doc !== doc._id) {
throw new TypeError("Different Index");
}
for (i = 0; i < that._indexing.length; i += 1) {
key = that._indexing[i];
if (doc[key] !== db_doc[key]) {
throw new TypeError("Different Index");
}
}
};
/**
* Recreates database indices and remove free space
*
* @method repair
*/
that.repair = function () {
var i = 0, meta;
that._free = [];
that._location = {};
if (!Array.isArray(that._database)) {
that._database = [];
}
while (i < that._database.length) {
meta = that._database[i];
if (typeof meta === 'object' &&
Object.getPrototypeOf(meta || []) === Object.prototype &&
typeof meta._id === "string" && meta._id !== "" &&
!that._location[meta._id]) {
that._location[meta._id] = i;
i += 1;
} else {
that._database.splice(i, 1);
}
}
that.modified = true;
return true;
};
/**
* Returns the serialized version of this object (not cloned)
*
* @method toJSON
* @return {Object} The serialized version
*/
that.toJSON = function () {
return {
"indexing": that._indexing,
"free": that._free,
"location": that._location,
"database": that._database
};
};
that.check();
that._indexing_object = {};
that._indexing.forEach(function (meta_key) {
that._indexing_object[meta_key] = true;
});
}
/**
* Return the similarity percentage (1 >= p >= 0) between two index lists.
*
* @param {Array} list_a An index list
* @param {Array} list_b Another index list
* @return {Number} The similarity percentage
*/
function similarityPercentage(list_a, list_b) {
var ai, bi, count = 0;
for (ai = 0; ai < list_a.length; ai += 1) {
for (bi = 0; bi < list_b.length; bi += 1) {
if (list_a[ai] === list_b[bi]) {
count += 1;
break;
}
}
}
return count / (list_a.length > list_b.length ?
list_a.length : list_b.length);
}
/**
* The JIO index storage constructor
*
* @class IndexStorage
* @constructor
*/
function IndexStorage(spec) {
var i;
if (!Array.isArray(spec.indices)) {
throw new TypeError("IndexStorage 'indices' must be an array of " +
"objects.");
}
this._indices = spec.indices;
if (typeof spec.sub_storage !== 'object' ||
Object.getPrototypeOf(spec.sub_storage || []) !== Object.prototype) {
throw new TypeError("IndexStorage 'sub_storage' must be a storage " +
"description.");
}
// check indices IDs
for (i = 0; i < this._indices.length; i += 1) {
if (typeof this._indices[i].id !== "string" ||
this._indices[i].id === "") {
throw new TypeError("IndexStorage " +
"'indices[x].id' must be a non empty string");
}
if (!Array.isArray(this._indices[i].index)) {
throw new TypeError("IndexStorage " +
"'indices[x].index' must be a string array");
}
}
this._sub_storage = spec.sub_storage;
}
/**
* Select the good index to use according to a select list.
*
* @method selectIndex
* @param {Array} select_list An array of strings
* @return {Number} The index index
*/
IndexStorage.prototype.selectIndex = function (select_list) {
var i, tmp, selector = {"index": 0, "similarity": 0};
for (i = 0; i < this._indices.length; i += 1) {
tmp = similarityPercentage(select_list, this._indices[i].index);
if (tmp > selector.similarity) {
selector.index = i;
selector.similarity = tmp;
}
}
return selector.index;
};
IndexStorage.prototype.getIndexDatabase = function (command, index) {
index = this._indices[index];
function makeNewIndex() {
var json_index = new JSONIndex({
"_id": index.id,
"_attachment": index.attachment || "body",
"indexing": index.index
});
json_index.touch();
return json_index;
}
return command.storage(
index.sub_storage || this._sub_storage
).getAttachment({
"_id": index.id,
"_attachment": index.attachment || "body"
}).then(function (response) {
return jIO.util.readBlobAsText(response.data);
}).then(function (e) {
try {
e = JSON.parse(e.target.result);
e._id = index.id;
e._attachment = index.attachment || "body";
} catch (e1) {
return makeNewIndex();
}
return new JSONIndex(e);
}, function (err) {
if (err.status === 404) {
return makeNewIndex();
// go back to fulfillment channel
}
throw err;
// propagate err
});
};
IndexStorage.prototype.getIndexDatabases = function (command) {
var i, promises = [];
for (i = 0; i < this._indices.length; i += 1) {
promises[promises.length] = this.getIndexDatabase(command, i);
}
return RSVP.all(promises);
};
IndexStorage.prototype.storeIndexDatabase = function (command, database,
index) {
var that = this;
if (!database.modified) {
return RSVP.resolve({"result": "success"});
}
index = this._indices[index];
function putAttachment() {
return command.storage(
index.sub_storage || that._sub_storage
).putAttachment({
"_id": index.id,
"_attachment": index.attachment || "body",
"_data": JSON.stringify(database),
"_content_type": "application/json"
});
}
function createDatabaseAndPutAttachmentIfPossible(err) {
var metadata;
if (err.status === 404) {
metadata = {"_id": index.id};
if (typeof index.metadata === 'object' &&
// adding metadata
index.metadata !== null &&
!Array.isArray(index.metadata)) {
metadata = jIO.util.dictUpdate(metadata, index.metadata);
}
return command.storage(
index.sub_storage || that._sub_storage
).post(metadata).then(putAttachment, null, function () {
throw null; // stop post progress propagation
});
}
throw err;
}
return putAttachment().
then(null, createDatabaseAndPutAttachmentIfPossible);
};
IndexStorage.prototype.storeIndexDatabases = function (command, databases) {
var i, promises = [];
for (i = 0; i < this._indices.length; i += 1) {
if (databases[i] !== undefined) {
promises[promises.length] =
this.storeIndexDatabase(command, databases[i], i);
}
}
return RSVP.all(promises);
};
/**
* Generic method for 'post', 'put', 'get' and 'remove'. It delegates the
* command to the sub storage and update the databases.
*
* @method genericCommand
* @param {String} method The method to use
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} option The command option
*/
IndexStorage.prototype.genericCommand = function (method, command,
metadata, option) {
var that = this, generic_response;
function updateAndStoreIndexDatabases(responses) {
var i, database_list = responses[0];
generic_response = responses[1];
if (method === 'get') {
jIO.util.dictUpdate(metadata, generic_response.data);
}
metadata._id = generic_response.id;
if (method === 'remove') {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].remove(metadata);
}
} else {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].put(metadata);
}
}
return that.storeIndexDatabases(command, database_list);
}
function allProgress(progress) {
if (progress.index === 1) {
progress.value.percentage *= 0.7; // 0 to 70%
command.notify(progress.value);
}
throw null; // stop propagation
}
function success() {
command.success(generic_response);
}
function storeProgress(progress) {
progress.percentage = (0.3 * progress.percentage) + 70; // 70 to 100%
command.notify(progress);
}
RSVP.all([
this.getIndexDatabases(command),
command.storage(this._sub_storage)[method](metadata, option)
]).then(updateAndStoreIndexDatabases, null, allProgress).
then(success, command.error, storeProgress);
};
/**
* Post the document metadata and update the index
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} option The command option
*/
IndexStorage.prototype.post = function (command, metadata, option) {
this.genericCommand('post', command, metadata, option);
};
/**
* Update the document metadata and update the index
*
* @method put
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to put
* @param {Object} option The command option
*/
IndexStorage.prototype.put = function (command, metadata, option) {
this.genericCommand('put', command, metadata, option);
};
/**
* Add an attachment to a document (no index modification)
*
* @method putAttachment
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
IndexStorage.prototype.putAttachment = function (command, param, option) {
command.storage(this._sub_storage).putAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
* Get the document metadata
*
* @method get
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
IndexStorage.prototype.get = function (command, param, option) {
this.genericCommand('get', command, param, option);
};
/**
* Get the attachment.
*
* @method getAttachment
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
IndexStorage.prototype.getAttachment = function (command, param, option) {
command.storage(this._sub_storage).getAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
* Remove document - removing documents updates index!.
*
* @method remove
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
IndexStorage.prototype.remove = function (command, param, option) {
this.genericCommand('remove', command, param, option);
};
/**
* Remove attachment
*
* @method removeAttachment
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
IndexStorage.prototype.removeAttachment = function (command, param, option) {
command.storage(this._sub_storage).removeAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
* Gets a document list from the substorage
*
* @method allDocs
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
* @param {Boolean} [option.include_docs=false] Also retrieve the actual
* document content.
*/
IndexStorage.prototype.allDocs = function (command, param, option) { // XXX
/*jslint unparam: true */
var index = this.selectIndex(option.select_list || []), delete_id, now;
option.select_list = (
Array.isArray(option.select_list) ? option.select_list : []
);
if (option.select_list.indexOf("_id") === -1) {
option.select_list.push("_id");
delete_id = true;
}
if (option.include_docs) {
now = Date.now();
option.select_list.push("_" + now);
}
this.getIndexDatabase(command, index).then(function (db) {
var i, id;
db = db._database;
if (option.include_docs) {
// XXX find another way to manage include_docs option!!
for (i = 0; i < db.length; i += 1) {
db[i]["_" + now] = db[i];
}
}
jIO.QueryFactory.create(option.query || '').
exec(db, option).then(function () {
for (i = 0; i < db.length; i += 1) {
id = db[i]._id;
if (delete_id) {
delete db[i]._id;
}
if (option.include_docs) {
db[i] = {
"id": id,
"value": db[i],
"doc": db[i]["_" + now]
};
delete db[i].doc["_" + now];
delete db[i].value["_" + now];
} else {
db[i] = {
"id": id,
"value": db[i]
};
}
}
command.success(200, {"data": {"total_rows": db.length, "rows": db}});
});
}, function (err) {
if (err.status === 404) {
return command.success(200, {"data": {"total_rows": 0, "rows": []}});
}
command.error(err);
});
};
// IndexStorage.prototype.check = function (command, param, option) { // XXX
// this.repair(command, true, param, option);
// };
// IndexStorage.prototype.repairIndexDatabase = function (
// command,
// index,
// just_check,
// param,
// option
// ) { // XXX
// var i, that = this;
// command.storage(this._sub_storage).allDocs({'include_docs': true}).then(
// function (response) {
// var db_list = [], db = new JSONIndex({
// "_id": param._id,
// "_attachment": that._indices[index].attachment || "body",
// "indexing": that._indices[index].index
// });
// for (i = 0; i < response.rows.length; i += 1) {
// db.put(response.rows[i].doc);
// }
// db_list[index] = db;
// if (just_check) {
// this.getIndexDatabase(command, option, index, function (current_db) {
// if (db.equals(current_db)) {
// return command.success({"ok": true, "id": param._id});
// }
// return command.error(
// "conflict",
// "corrupted",
// "Database is not up to date"
// );
// });
// } else {
// that.storeIndexDatabaseList(command, db_list, {}, function () {
// command.success({"ok": true, "id": param._id});
// });
// }
// },
// function (err) {
// err.message = "Unable to repair the index database";
// command.error(err);
// }
// );
// };
// IndexStorage.prototype.repairDocument = function (
// command,
// just_check,
// param,
// option
// ) { // XXX
// var i, that = this;
// command.storage(this._sub_storage).get(param, {}).then(
// function (response) {
// response._id = param._id;
// that.getIndexDatabaseList(command, option, function (database_list) {
// if (just_check) {
// for (i = 0; i < database_list.length; i += 1) {
// try {
// database_list[i].checkDocument(response);
// } catch (e) {
// return command.error(
// "conflict",
// e.message,
// "Corrupt index database"
// );
// }
// }
// command.success({"_id": param._id, "ok": true});
// } else {
// for (i = 0; i < database_list.length; i += 1) {
// database_list[i].put(response);
// }
// that.storeIndexDatabaseList(
// command,
// database_list,
// option,
// function () {
// command.success({"ok": true, "id": param._id});
// }
// );
// }
// });
// },
// function (err) {
// err.message = "Unable to repair document";
// return command.error(err);
// }
// );
// };
// IndexStorage.prototype.repair = function (command, just_check, param,
// option) { // XXX
// var database_index = -1, i, that = this;
// for (i = 0; i < this._indices.length; i += 1) {
// if (this._indices[i].id === param._id) {
// database_index = i;
// break;
// }
// }
// command.storage(this._sub_storage).repair(param, option).then(
// function () {
// if (database_index !== -1) {
// that.repairIndexDatabase(
// command,
// database_index,
// just_check,
// param,
// option
// );
// } else {
// that.repairDocument(command, just_check, param, option);
// }
// },
// function (err) {
// err.message = "Could not repair sub storage";
// command.error(err);
// }
// );
// };
jIO.addStorage("index", IndexStorage);
exports.createDescription = function () {
// XXX
return;
};
}));
jio-master-src/src/jio.storage/linsharestorage.js 0000664 0000000 0000000 00000022541 14230544473 0022472 0 ustar 00root root 0000000 0000000 /*
* Copyright 2019, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Linshare Storage. Type = "linshare".
* Linshare "database" storage.
* http://download.linshare.org/components/linshare-core/2.2.2/
* Can't set up id, implied can't put new document
*/
/*global jIO, RSVP, UriTemplate, FormData, Blob*/
/*jslint nomen: true*/
(function (jIO, RSVP, UriTemplate, FormData, Blob) {
"use strict";
function makeRequest(storage, uuid, options, download) {
if (options === undefined) {
options = {};
}
if (options.xhrFields === undefined) {
options.xhrFields = {};
}
if (options.headers === undefined) {
options.headers = {};
}
// Prefer JSON by default
if (download === true) {
options.url = storage._blob_template.expand({uuid: uuid});
options.dataType = 'blob';
} else {
options.url = storage._url_template.expand({uuid: uuid || ""});
if (!options.headers.hasOwnProperty('Accept')) {
options.headers.Accept = 'application/json';
options.dataType = 'json';
}
}
// Use cookie based auth
if (storage.hasOwnProperty('_access_token')) {
options.headers.Authorization = "Basic " + storage._access_token;
} else {
options.xhrFields.withCredentials = true;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax(options);
})
.push(function (event) {
if (download === true) {
return (
event.target.response ||
// sinon does not fill the response attribute
new Blob([event.target.responseText], {type: 'text/plain'})
);
}
return (
event.target.response ||
// sinon does not fill the response attribute
JSON.parse(event.target.responseText)
);
});
}
/**
* The JIO Linshare Storage extension
*
* @class LinshareStorage
* @constructor
*/
function LinshareStorage(spec) {
if (typeof spec.url !== "string" || !spec.url) {
throw new TypeError("Linshare 'url' must be a string " +
"which contains more than one character.");
}
this._url_template = UriTemplate.parse(
spec.url + '/linshare/webservice/rest/user/v2/documents/{uuid}'
);
this._blob_template = UriTemplate.parse(
spec.url + '/linshare/webservice/rest/user/v2/documents/{uuid}/download'
);
if (spec.hasOwnProperty('access_token')) {
this._access_token = spec.access_token;
}
}
var capacity_list = ['list', 'include'];
LinshareStorage.prototype.hasCapacity = function (name) {
return (capacity_list.indexOf(name) !== -1);
};
function sortByModificationDate(entry1, entry2) {
var date1 = entry1.modificationDate,
date2 = entry2.modificationDate;
return (date1 === date2) ? 0 : ((date1 < date2) ? 1 : -1);
}
function getDocumentList(storage, options) {
return makeRequest(storage, "", {
type: "GET"
})
.push(function (entry_list) {
// Linshare only allow to get the full list of documents
// First, sort the entries by modificationDate in order to
// drop the 'old' entries with the same 'name'
// (as linshare does not to update an existing doc)
entry_list.sort(sortByModificationDate);
// Only return one document per name
// Keep the newer document
var entry_dict = {},
i,
len = entry_list.length,
entry_name,
entry,
result_list = [];
for (i = 0; i < len; i += 1) {
entry_name = entry_list[i].name;
// If we only need one precise name, no need to check the others
if (!options.hasOwnProperty('only_id') ||
(options.only_id === entry_name)) {
if (!entry_dict.hasOwnProperty(entry_name)) {
entry = {
id: entry_name,
value: {},
_linshare_uuid: entry_list[i].uuid
};
if (options.include_docs === true) {
try {
entry.doc = JSON.parse(entry_list[i].metaData) || {};
} catch (error) {
// Metadata are not always JSON
entry.doc = {};
}
}
result_list.push(entry);
if (options.all_revision !== true) {
// If we only want to fetch 'one revision',
// ie, the latest document matching this id
entry_dict[entry_name] = null;
if (options.only_id === entry_name) {
// Document has been found, no need to check all the others
break;
}
}
}
}
}
return result_list;
});
}
LinshareStorage.prototype.buildQuery = function (options) {
return getDocumentList(this, {
include_docs: options.include_docs
});
};
LinshareStorage.prototype.get = function (id) {
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return getDocumentList(this, {
include_docs: true,
only_id: id
})
.push(function (result_list) {
if (result_list.length === 1) {
return result_list[0].doc;
}
throw new jIO.util.jIOError(
"Can't find document with id : " + id,
404
);
});
};
function createLinshareDocument(storage, id, doc, blob) {
var data = new FormData();
data.append('file', blob, id);
data.append('filesize', blob.size);
data.append('filename', id);
data.append('description', doc.title || doc.description || '');
data.append('metadata', jIO.util.stringify(doc));
return makeRequest(storage, '', {
type: 'POST',
data: data
});
}
LinshareStorage.prototype.put = function (id, doc) {
var storage = this;
return getDocumentList(storage, {
include_docs: true,
only_id: id
})
.push(function (result_list) {
if (result_list.length === 1) {
// Update existing document metadata
var data = {
uuid: result_list[0]._linshare_uuid,
metaData: jIO.util.stringify(doc),
name: id,
description: doc.title || doc.description || ''
};
return makeRequest(storage, result_list[0]._linshare_uuid, {
type: 'PUT',
headers: {'Content-Type': 'application/json'},
data: jIO.util.stringify(data)
});
}
// Create a new one
return createLinshareDocument(storage, id, doc, new Blob());
});
};
LinshareStorage.prototype.remove = function (id) {
var storage = this;
// Delete all entries matching the id
return getDocumentList(storage, {
only_id: id,
all_revision: true
})
.push(function (result_list) {
var promise_list = [],
i,
len = result_list.length;
for (i = 0; i < len; i += 1) {
promise_list.push(
makeRequest(storage, result_list[i]._linshare_uuid, {
type: "DELETE"
})
);
}
return RSVP.all(promise_list);
});
};
LinshareStorage.prototype.allAttachments = function (id) {
return this.get(id)
.push(function () {
return {enclosure: {}};
});
};
function restrictAttachmentId(name) {
if (name !== "enclosure") {
throw new jIO.util.jIOError(
"attachment name " + name + " is forbidden in linshare",
400
);
}
}
LinshareStorage.prototype.putAttachment = function (id, name, blob) {
restrictAttachmentId(name);
var storage = this;
return storage.get(id)
.push(function (doc) {
// Create a new document with the same id but a different blob content
return createLinshareDocument(storage, id, doc, blob);
});
};
LinshareStorage.prototype.getAttachment = function (id, name) {
restrictAttachmentId(name);
var storage = this;
// It is not possible to get a document by its name
// The only way is to list all of them, and find it manually
return getDocumentList(storage, {
only_id: id
})
.push(function (result_list) {
if (result_list.length === 1) {
return makeRequest(storage, result_list[0]._linshare_uuid, {
}, true);
}
throw new jIO.util.jIOError(
"Can't find document with id : " + id,
404
);
});
};
jIO.addStorage('linshare', LinshareStorage);
}(jIO, RSVP, UriTemplate, FormData, Blob));
jio-master-src/src/jio.storage/localstorage.js 0000664 0000000 0000000 00000006042 14230544473 0021755 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, sessionStorage, localStorage, RSVP */
/**
* JIO Local Storage. Type = 'local'.
* Local browser "database" storage.
*
* Storage Description:
*
* {
* "type": "local",
* "sessiononly": false
* }
*
* @class LocalStorage
*/
(function (jIO, sessionStorage, localStorage, RSVP) {
"use strict";
function LocalStorage(spec) {
if (spec.sessiononly === true) {
this._storage = sessionStorage;
} else {
this._storage = localStorage;
}
}
function restrictDocumentId(id) {
if (id !== "/") {
throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)",
400);
}
}
LocalStorage.prototype.get = function (id) {
restrictDocumentId(id);
return {};
};
LocalStorage.prototype.allAttachments = function (id) {
restrictDocumentId(id);
var attachments = {},
key;
for (key in this._storage) {
if (this._storage.hasOwnProperty(key)) {
attachments[key] = {};
}
}
return attachments;
};
LocalStorage.prototype.getAttachment = function (id, name) {
restrictDocumentId(id);
var textstring = this._storage.getItem(name);
if (textstring === null) {
throw new jIO.util.jIOError(
"Cannot find attachment " + name,
404
);
}
return jIO.util.dataURItoBlob(textstring);
};
LocalStorage.prototype.putAttachment = function (id, name, blob) {
var context = this;
restrictDocumentId(id);
// the document already exists
// download data
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (e) {
context._storage.setItem(name, e.target.result);
});
};
LocalStorage.prototype.removeAttachment = function (id, name) {
restrictDocumentId(id);
return this._storage.removeItem(name);
};
LocalStorage.prototype.hasCapacity = function (name) {
return (name === "list");
};
LocalStorage.prototype.buildQuery = function () {
return [{
id: "/",
value: {}
}];
};
jIO.addStorage('local', LocalStorage);
}(jIO, sessionStorage, localStorage, RSVP));
jio-master-src/src/jio.storage/memorystorage.js 0000664 0000000 0000000 00000010723 14230544473 0022174 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, RSVP*/
/**
* JIO Memory Storage. Type = 'memory'.
* Memory browser "database" storage.
*
* Storage Description:
*
* {
* "type": "memory"
* }
*
* @class MemoryStorage
*/
(function (jIO, JSON, RSVP) {
"use strict";
/**
* The JIO MemoryStorage extension
*
* @class MemoryStorage
* @constructor
*/
function MemoryStorage() {
this._database = {};
}
MemoryStorage.prototype.put = function (id, metadata) {
if (!this._database.hasOwnProperty(id)) {
this._database[id] = {
attachments: {}
};
}
this._database[id].doc = JSON.stringify(metadata);
return id;
};
MemoryStorage.prototype.get = function (id) {
try {
return JSON.parse(this._database[id].doc);
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError(
"Cannot find document: " + id,
404
);
}
throw error;
}
};
MemoryStorage.prototype.allAttachments = function (id) {
var key,
attachments = {};
try {
for (key in this._database[id].attachments) {
if (this._database[id].attachments.hasOwnProperty(key)) {
attachments[key] = {};
}
}
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError(
"Cannot find document: " + id,
404
);
}
throw error;
}
return attachments;
};
MemoryStorage.prototype.remove = function (id) {
delete this._database[id];
return id;
};
MemoryStorage.prototype.getAttachment = function (id, name) {
try {
var result = this._database[id].attachments[name];
if (result === undefined) {
throw new jIO.util.jIOError(
"Cannot find attachment: " + id + " , " + name,
404
);
}
return jIO.util.dataURItoBlob(result);
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError(
"Cannot find attachment: " + id + " , " + name,
404
);
}
throw error;
}
};
MemoryStorage.prototype.putAttachment = function (id, name, blob) {
var attachment_dict;
try {
attachment_dict = this._database[id].attachments;
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError("Cannot find document: " + id, 404);
}
throw error;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (evt) {
attachment_dict[name] = evt.target.result;
});
};
MemoryStorage.prototype.removeAttachment = function (id, name) {
try {
delete this._database[id].attachments[name];
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError(
"Cannot find document: " + id,
404
);
}
throw error;
}
};
MemoryStorage.prototype.hasCapacity = function (name) {
return ((name === "list") || (name === "include"));
};
MemoryStorage.prototype.buildQuery = function (options) {
var rows = [],
i;
for (i in this._database) {
if (this._database.hasOwnProperty(i)) {
if (options.include_docs === true) {
rows.push({
id: i,
value: {},
doc: JSON.parse(this._database[i].doc)
});
} else {
rows.push({
id: i,
value: {}
});
}
}
}
return rows;
};
jIO.addStorage('memory', MemoryStorage);
}(jIO, JSON, RSVP));
jio-master-src/src/jio.storage/multisplitstorage.js 0000664 0000000 0000000 00000043033 14230544473 0023072 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent:2, maxlen: 80, nomen: true */
/*global jIO, define, Blob */
/**
* Provides a split storage for JIO. This storage splits data
* and store them in the sub storages defined on the description.
*
* {
* "type": "split",
* "storage_list": [, ...]
* }
*/
// define([dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO);
}(['jio'], function (jIO) {
"use strict";
/**
* Select a storage to put the document part
*
* @method selectStorage
* @private
* @return {Object} The selected storage
*/
function selectStorage(arg, iteration) {
var step = iteration % arg.length;
return arg[step];
}
/**
* Class to merge allDocs responses from several sub storages.
*
* @class AllDocsResponseMerger
* @constructor
*/
function AllDocsResponseMerger() {
/**
* A list of allDocs response.
*
* @attribute response_list
* @type {Array} Contains allDocs responses
* @default []
*/
this.response_list = [];
}
AllDocsResponseMerger.prototype.constructor = AllDocsResponseMerger;
/**
* Add an allDocs response to the response list.
*
* @method addResponse
* @param {Object} response The allDocs response.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger.prototype.addResponse = function (response) {
this.response_list.push(response);
return this;
};
/**
* Add several allDocs responses to the response list.
*
* @method addResponseList
* @param {Array} response_list An array of allDocs responses.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger.prototype.addResponseList = function (response_list) {
var i;
for (i = 0; i < response_list.length; i += 1) {
this.response_list.push(response_list[i]);
}
return this;
};
/**
* Merge the response_list to one allDocs response.
*
* The merger will find rows with the same id in order to merge them, thanks
* to the onRowToMerge method. If no row correspond to an id, rows with the
* same id will be ignored.
*
* @method merge
* @param {Object} [option={}] The merge options
* @param {Boolean} [option.include_docs=false] Tell the merger to also
* merge metadata if true.
* @return {Object} The merged allDocs response.
*/
AllDocsResponseMerger.prototype.merge = function (option) {
var result = [], row, to_merge = [], tmp, i;
if (this.response_list.length === 0) {
return [];
}
/*jslint ass: true */
while ((row = this.response_list[0].data.rows.shift()) !== undefined) {
to_merge[0] = row;
for (i = 1; i < this.response_list.length; i += 1) {
to_merge[i] = AllDocsResponseMerger.listPopFromRowId(
this.response_list[i].data.rows,
row.id
);
if (to_merge[i] === undefined) {
break;
}
}
tmp = this.onRowToMerge(to_merge, option || {});
if (tmp !== undefined) {
result[result.length] = tmp;
}
}
this.response_list = [];
return {"total_rows": result.length, "rows": result};
};
/**
* This method is called when the merger want to merge several rows with the
* same id.
*
* @method onRowToMerge
* @param {Array} row_list An array of rows.
* @param {Object} [option={}] The merge option.
* @param {Boolean} [option.include_docs=false] Also merge the metadata if
* true
* @return {Object} The merged row
*/
AllDocsResponseMerger.prototype.onRowToMerge = function (row_list, option) {
var i, k, new_row = {"value": {}}, data = "";
option = option || {};
for (i = 0; i < row_list.length; i += 1) {
new_row.id = row_list[i].id;
if (row_list[i].key) {
new_row.key = row_list[i].key;
}
if (option.include_docs) {
new_row.doc = new_row.doc || {};
for (k in row_list[i].doc) {
if (row_list[i].doc.hasOwnProperty(k)) {
if (k[0] === "_") {
new_row.doc[k] = row_list[i].doc[k];
}
}
}
data += row_list[i].doc.data;
}
}
if (option.include_docs) {
try {
data = JSON.parse(data);
} catch (e) { return undefined; }
for (k in data) {
if (data.hasOwnProperty(k)) {
new_row.doc[k] = data[k];
}
}
}
return new_row;
};
/**
* Search for a specific row and pop it. During the search operation, all
* parsed rows are stored on a dictionnary in order to be found instantly
* later.
*
* @method listPopFromRowId
* @param {Array} rows The row list
* @param {String} doc_id The document/row id
* @return {Object/undefined} The poped row
*/
AllDocsResponseMerger.listPopFromRowId = function (rows, doc_id) {
var row;
if (!rows.dict) {
rows.dict = {};
}
if (rows.dict[doc_id]) {
row = rows.dict[doc_id];
delete rows.dict[doc_id];
return row;
}
/*jslint ass: true*/
while ((row = rows.shift()) !== undefined) {
if (row.id === doc_id) {
return row;
}
rows.dict[row.id] = row;
}
};
/**
* The split storage class used by JIO.
*
* A split storage instance is able to i/o on several sub storages with
* split documents.
*
* @class MultiSplitStorage
*/
function MultiSplitStorage(spec) {
var that = this, priv = {};
/**
* The list of sub storages we want to use to store part of documents.
*
* @attribute storage_list
* @private
* @type {Array} Array of storage descriptions
*/
priv.storage_list = spec.storage_list;
//////////////////////////////////////////////////////////////////////
// Tools
/**
* Send a command to all sub storages. All the response are returned
* in a list. The index of the response correspond to the storage_list
* index. If an error occurs during operation, the callback is called with
* `callback(err, undefined)`. The response is given with
* `callback(undefined, response_list)`.
*
* `doc` is the document informations but can also be a list of dedicated
* document informations. In this case, each document is associated to one
* sub storage.
*
* @method send
* @private
* @param {String} method The command method
* @param {Object,Array} doc The document information to send to each sub
* storages or a list of dedicated document
* @param {Object} option The command option
* @param {Function} callback Called at the end
*/
function send(command, method, doc, option, callback) {
var i, answer_list = [], failed = false, currentServer;
function onEnd() {
i += 1;
if (i === priv.storage_list.length) {
callback(undefined, answer_list);
}
}
function onSuccess(i) {
return function (response) {
if (!failed) {
answer_list[i] = response;
}
onEnd();
};
}
function onError(i) {
return function (err) {
if (!failed) {
failed = true;
err.index = i;
callback(err, undefined);
}
};
}
if (!Array.isArray(doc)) {
for (i = 0; i < doc.length; i += 1) {
currentServer = selectStorage(priv.storage_list, i, doc.length);
if (method === 'allDocs') {
command.storage(currentServer)[method](option).
then(onSuccess(i), onError(i));
} else {
command.storage(currentServer)[method](doc, option).
then(onSuccess(i), onError(i));
}
}
} else {
for (i = 0; i < doc.length; i += 1) {
currentServer = selectStorage(priv.storage_list, i, doc.length);
// we assume that alldocs is not called if the there is several docs
command.storage(priv.storage_list[i])[method](doc[i], option).
then(onSuccess(i), onError(i));
}
}
//default splitstorage method
/*
if (!Array.isArray(doc)) {
for (i = 0; i < priv.storage_list.length; i += 1) {
if (method === 'allDocs') {
command.storage(priv.storage_list[i])[method](option).
then(onSuccess(i), onError(i));
} else {
command.storage(priv.storage_list[i])[method](doc, option).
then(onSuccess(i), onError(i));
}
}
} else {
for (i = 0; i < priv.storage_list.length; i += 1) {
// we assume that alldocs is not called if the there is several docs
command.storage(priv.storage_list[i])[method](doc[i], option).
then(onSuccess(i), onError(i));
}
}
*/
//re-init
i = 0;
}
/**
* Split document metadata then store them to the sub storages.
*
* @method postOrPut
* @private
* @param {Object} doc A serialized document object
* @param {Object} option Command option properties
* @param {String} method The command method ('post' or 'put')
*/
priv.postOrPut = function (command, doc, option, method) {
var i, data, doc_list = [], doc_underscores = {};
for (i in doc) {
if (doc.hasOwnProperty(i)) {
if (i[0] === "_") {
doc_underscores[i] = doc[i];
delete doc[i];
}
}
}
data = JSON.stringify(doc);
//for (i = 0; i < priv.storage_list.length; i += 1) {
//doc_list[i] = JSON.parse(JSON.stringify(doc_underscores));
//doc_list[i].data = data.slice(
//(data.length / priv.storage_list.length) * i,
//(data.length / priv.storage_list.length) * (i + 1)
//);
//console.info('doc_list[i].data');
//console.log(doc_list[i].data);
//}
for (i = 0; i < 100; i += 1) {
doc_list[i] = JSON.parse(JSON.stringify(doc_underscores));
doc_list[i]._id = doc_list[i]._id + '_' + i;
doc_list[i].data = data.slice(
(data.length / 100) * i,
(data.length / 100) * (i + 1)
);
}
send(command, method, doc_list, option, function (err) {
if (err) {
err.message = "Unable to " + method + " document";
delete err.index;
return command.error(err);
}
command.success({"id": doc_underscores._id});
});
};
//////////////////////////////////////////////////////////////////////
// JIO commands
/**
* Split document metadata then store them to the sub storages.
*
* @method post
* @param {Object} command The JIO command
*/
that.post = function (command, metadata, option) {
priv.postOrPut(command, metadata, option, 'post');
};
/**
* Split document metadata then store them to the sub storages.
*
* @method put
* @param {Object} command The JIO command
*/
that.put = function (command, metadata, option) {
priv.postOrPut(command, metadata, option, 'put');
};
/**
* Puts an attachment to the sub storages.
*
* @method putAttachment
* @param {Object} command The JIO command
*/
that.putAttachment = function (command, param, option) {
var i, attachment_list = [], data = param._blob;
for (i = 0; i < priv.storage_list.length; i += 1) {
attachment_list[i] = jIO.util.deepClone(param);
attachment_list[i]._blob = data.slice(
data.size * i / priv.storage_list.length,
data.size * (i + 1) / priv.storage_list.length,
data.type
);
}
send(
command,
'putAttachment',
attachment_list,
option,
function (err) {
if (err) {
err.message = "Unable to put attachment";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Gets splited document metadata then returns real document.
*
* @method get
* @param {Object} command The JIO command
*/
that.get = function (command, param, option) {
var doc = param;
send(command, 'get', doc, option, function (err, response) {
var i, k;
if (err) {
err.message = "Unable to get document";
delete err.index;
return command.error(err);
}
doc = '';
for (i = 0; i < response.length; i += 1) {
response[i] = response[i].data;
doc += response[i].data;
}
doc = JSON.parse(doc);
for (i = 0; i < response.length; i += 1) {
for (k in response[i]) {
if (response[i].hasOwnProperty(k)) {
if (k[0] === "_") {
doc[k] = response[i][k];
}
}
}
}
delete doc._attachments;
for (i = 0; i < response.length; i += 1) {
if (response[i]._attachments) {
for (k in response[i]._attachments) {
if (response[i]._attachments.hasOwnProperty(k)) {
doc._attachments = doc._attachments || {};
doc._attachments[k] = doc._attachments[k] || {
"length": 0,
"content_type": ""
};
doc._attachments[k].length += response[i]._attachments[k].
length;
// if (response[i]._attachments[k].digest) {
// if (doc._attachments[k].digest) {
// doc._attachments[k].digest += " " + response[i].
// _attachments[k].digest;
// } else {
// doc._attachments[k].digest = response[i].
// _attachments[k].digest;
// }
// }
doc._attachments[k].content_type = response[i]._attachments[k].
content_type;
}
}
}
}
command.success({"data": doc});
});
};
/**
* Gets splited document attachment then returns real attachment data.
*
* @method getAttachment
* @param {Object} command The JIO command
*/
that.getAttachment = function (command, param, option) {
send(command, 'getAttachment', param, option, function (
err,
response
) {
if (err) {
err.message = "Unable to get attachment";
delete err.index;
return command.error(err);
}
command.success({"data": new Blob(response.map(function (answer) {
return answer.data;
}), {"type": response[0].data.type})});
});
};
/**
* Removes a document from the sub storages.
*
* @method remove
* @param {Object} command The JIO command
*/
that.remove = function (command, param, option) {
send(
command,
'remove',
param,
option,
function (err) {
if (err) {
err.message = "Unable to remove document";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Removes an attachment from the sub storages.
*
* @method removeAttachment
* @param {Object} command The JIO command
*/
that.removeAttachment = function (command, param, option) {
send(
command,
'removeAttachment',
param,
option,
function (err) {
if (err) {
err.message = "Unable to remove attachment";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Retreive a list of all document in the sub storages.
*
* If include_docs option is false, then it returns the document list from
* the first sub storage. Else, it will merge results and return.
*
* @method allDocs
* @param {Object} command The JIO command
*/
that.allDocs = function (command, param, option) {
option = {"include_docs": option.include_docs};
send(
command,
'allDocs',
param,
option,
function (err, response_list) {
var all_docs_merger;
if (err) {
err.message = "Unable to retrieve document list";
delete err.index;
return command.error(err);
}
all_docs_merger = new AllDocsResponseMerger();
all_docs_merger.addResponseList(response_list);
return command.success({"data": all_docs_merger.merge(option)});
}
);
};
} // end of MultiplitStorage
jIO.addStorage('multisplit', MultiSplitStorage);
}));
jio-master-src/src/jio.storage/parserstorage.js 0000664 0000000 0000000 00000021172 14230544473 0022160 0 ustar 00root root 0000000 0000000 /*
* Copyright 2017, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, DOMParser, Node */
(function (jIO, DOMParser, Node) {
"use strict";
/////////////////////////////////////////////////////////////
// OPML Parser
/////////////////////////////////////////////////////////////
function OPMLParser(txt) {
this._dom_parser = new DOMParser().parseFromString(txt, 'text/xml');
}
OPMLParser.prototype.parseHead = function () {
// fetch all children instead
var channel_element = this._dom_parser.querySelector("opml > head"),
tag_element,
i,
result = {};
for (i = channel_element.childNodes.length - 1; i >= 0; i -= 1) {
tag_element = channel_element.childNodes[i];
if (tag_element.nodeType === Node.ELEMENT_NODE) {
result[tag_element.tagName] = tag_element.textContent;
}
}
return result;
};
OPMLParser.prototype.parseOutline = function (result_list, outline_element,
prefix, include, id) {
var attribute,
i,
child,
result = {};
if ((id === prefix) || (id === undefined)) {
result_list.push({
id: prefix,
value: {}
});
if (include) {
for (i = outline_element.attributes.length - 1; i >= 0; i -= 1) {
attribute = outline_element.attributes[i];
if (attribute.value) {
result[attribute.name] = attribute.value;
}
}
result_list[result_list.length - 1].doc = result;
}
}
for (i = outline_element.childNodes.length - 1; i >= 0; i -= 1) {
child = outline_element.childNodes[i];
if (child.tagName === 'outline') {
this.parseOutline(result_list, child, prefix + '/' + i, include, id);
}
}
};
OPMLParser.prototype.getDocumentList = function (include, id) {
var result_list,
item_list = this._dom_parser.querySelectorAll("body > outline"),
i;
if ((id === '/0') || (id === undefined)) {
result_list = [{
id: '/0',
value: {}
}];
if (include) {
result_list[0].doc = this.parseHead();
}
} else {
result_list = [];
}
for (i = 0; i < item_list.length; i += 1) {
this.parseOutline(result_list, item_list[i], '/1/' + i, include, id);
}
return result_list;
};
/////////////////////////////////////////////////////////////
// ATOM Parser
/////////////////////////////////////////////////////////////
function ATOMParser(txt) {
this._dom_parser = new DOMParser().parseFromString(txt, 'text/xml');
}
ATOMParser.prototype.parseElement = function (element) {
var tag_element,
i,
j,
tag_name,
attribute,
result = {};
for (i = element.childNodes.length - 1; i >= 0; i -= 1) {
tag_element = element.childNodes[i];
if ((tag_element.nodeType === Node.ELEMENT_NODE) &&
(tag_element.tagName !== 'entry')) {
tag_name = tag_element.tagName;
// may have several links, with different rel value
// default is alternate
if (tag_name === 'link') {
tag_name += '_' + (tag_element.getAttribute('rel') || 'alternate');
} else {
result[tag_name] = tag_element.textContent;
}
for (j = tag_element.attributes.length - 1; j >= 0; j -= 1) {
attribute = tag_element.attributes[j];
if (attribute.value) {
result[tag_name + '_' + attribute.name] =
attribute.value;
}
}
}
}
return result;
};
ATOMParser.prototype.getDocumentList = function (include, id) {
var result_list,
item_list = this._dom_parser.querySelectorAll("feed > entry"),
i;
if ((id === '/0') || (id === undefined)) {
result_list = [{
id: '/0',
value: {}
}];
if (include) {
result_list[0].doc = this.parseElement(
this._dom_parser.querySelector("feed")
);
}
} else {
result_list = [];
}
for (i = 0; i < item_list.length; i += 1) {
if ((id === '/0/' + i) || (id === undefined)) {
result_list.push({
id: '/0/' + i,
value: {}
});
if (include) {
result_list[result_list.length - 1].doc =
this.parseElement(item_list[i]);
}
}
}
return result_list;
};
/////////////////////////////////////////////////////////////
// RSS Parser
/////////////////////////////////////////////////////////////
function RSSParser(txt) {
this._dom_parser = new DOMParser().parseFromString(txt, 'text/xml');
}
RSSParser.prototype.parseElement = function (element) {
var tag_element,
i,
j,
attribute,
result = {};
for (i = element.childNodes.length - 1; i >= 0; i -= 1) {
tag_element = element.childNodes[i];
if ((tag_element.nodeType === Node.ELEMENT_NODE) &&
(tag_element.tagName !== 'item')) {
result[tag_element.tagName] = tag_element.textContent;
for (j = tag_element.attributes.length - 1; j >= 0; j -= 1) {
attribute = tag_element.attributes[j];
if (attribute.value) {
result[tag_element.tagName + '_' + attribute.name] =
attribute.value;
}
}
}
}
return result;
};
RSSParser.prototype.getDocumentList = function (include, id) {
var result_list,
item_list = this._dom_parser.querySelectorAll("rss > channel > item"),
i;
if ((id === '/0') || (id === undefined)) {
result_list = [{
id: '/0',
value: {}
}];
if (include) {
result_list[0].doc = this.parseElement(
this._dom_parser.querySelector("rss > channel")
);
}
} else {
result_list = [];
}
for (i = 0; i < item_list.length; i += 1) {
if ((id === '/0/' + i) || (id === undefined)) {
result_list.push({
id: '/0/' + i,
value: {}
});
if (include) {
result_list[result_list.length - 1].doc =
this.parseElement(item_list[i]);
}
}
}
return result_list;
};
/////////////////////////////////////////////////////////////
// Helpers
/////////////////////////////////////////////////////////////
var parser_dict = {
'rss': RSSParser,
'opml': OPMLParser,
'atom': ATOMParser
};
function getParser(storage) {
return storage._sub_storage.getAttachment(storage._document_id,
storage._attachment_id,
{format: 'text'})
.push(function (txt) {
return new parser_dict[storage._parser_name](txt);
});
}
/////////////////////////////////////////////////////////////
// Storage
/////////////////////////////////////////////////////////////
function ParserStorage(spec) {
this._attachment_id = spec.attachment_id;
this._document_id = spec.document_id;
this._parser_name = spec.parser;
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
ParserStorage.prototype.hasCapacity = function (capacity) {
return (capacity === "list") || (capacity === 'include');
};
ParserStorage.prototype.buildQuery = function (options) {
if (options === undefined) {
options = {};
}
return getParser(this)
.push(function (parser) {
return parser.getDocumentList((options.include_docs || false));
});
};
ParserStorage.prototype.get = function (id) {
return getParser(this)
.push(function (parser) {
var result_list = parser.getDocumentList(true, id);
if (result_list.length) {
return result_list[0].doc;
}
throw new jIO.util.jIOError(
"Cannot find parsed document: " + id,
404
);
});
};
jIO.addStorage('parser', ParserStorage);
}(jIO, DOMParser, Node));
jio-master-src/src/jio.storage/qiniustorage.js 0000664 0000000 0000000 00000020057 14230544473 0022012 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Qiniu Storage. Type = "qiniu".
* Qiniu "database" storage.
*
* REMAINING WORK:
* - removeAttachment should support CORS
* - allAttachments should support CORS
* - disable getAttachment HTTP cache
*/
/*global JSON, FormData, btoa, Blob, jIO, RSVP, UriTemplate, crypto,
Uint8Array, TextEncoder*/
/*jslint nomen: true*/
(function (JSON, FormData, btoa, Blob, jIO, RSVP, UriTemplate, Crypto,
Uint8Array, TextEncoder) {
"use strict";
var METADATA_URL = "http://{+bucket}/{+key}{?e,token}",
metadata_template = UriTemplate.parse(METADATA_URL),
UPLOAD_URL = "http://up.qiniu.com/",
DEADLINE = 2451491200;
function urlsafe_base64_encode(string) {
return string
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_'); // Convert '/' to '_'
// .replace(/=+$/, ''); // Remove ending '='
}
function bytesToASCIIString(bytes) {
return String.fromCharCode.apply(null, new Uint8Array(bytes));
}
// http://blog.engelke.com/tag/webcrypto/
function stringToArrayBuffer(string) {
var encoder = new TextEncoder("utf-8");
return encoder.encode(string);
}
function b64_hmac_sha1(secret_key, message) {
return new RSVP.Queue()
.push(function () {
return Crypto.subtle.importKey(
"raw",
stringToArrayBuffer(secret_key),
{name: "HMAC", hash: "SHA-1"},
false,
["sign"]
);
})
.push(function (key) {
return Crypto.subtle.sign({
name: "HMAC",
hash: "SHA-1"
}, key, stringToArrayBuffer(message));
})
.push(function (signature) {
return urlsafe_base64_encode(btoa(bytesToASCIIString(signature)));
});
}
/**
* The JIO QiniuStorage extension
*
* @class QiniuStorage
* @constructor
*/
function QiniuStorage(spec) {
if (typeof spec.bucket !== 'string' && !spec.bucket) {
throw new TypeError("Qiniu 'bucket' must be a string " +
"which contains more than one character.");
}
if (typeof spec.access_key !== 'string' && !spec.access_key) {
throw new TypeError("Qiniu 'access_key' must be a string " +
"which contains more than one character.");
}
if (typeof spec.secret_key !== 'string' && !spec.secret_key) {
throw new TypeError("Qiniu 'secret_key' must be a string " +
"which contains more than one character.");
}
this._bucket = spec.bucket;
this._access_key = spec.access_key;
this._secret_key = spec.secret_key;
}
function restrictDocumentId(id) {
if (id !== "/") {
throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)",
400);
}
}
QiniuStorage.prototype.get = function (id) {
restrictDocumentId(id);
return {};
};
QiniuStorage.prototype.getAttachment = function (id, key) {
restrictDocumentId(id);
var context = this,
download_url = metadata_template.expand({
bucket: context._bucket,
key: key,
e: DEADLINE
});
return new RSVP.Queue()
.push(function () {
return b64_hmac_sha1(context._secret_key, download_url);
})
.push(function (token) {
return jIO.util.ajax({
type: "GET",
url: metadata_template.expand({
bucket: context._bucket,
key: key,
e: DEADLINE,
token: context._access_key + ':' + token
})
});
})
.push(function (result) {
return new Blob([result.target.response ||
result.target.responseText]);
}, function (error) {
if ((error.target !== undefined) &&
(error.target.status === 404)) {
throw new jIO.util.jIOError("Cannot find attachment: "
+ id + " , " + key,
404);
}
throw error;
});
};
QiniuStorage.prototype.putAttachment = function (id, key, blob) {
restrictDocumentId(id);
var data,
context = this,
put_policy,
encoded,
upload_token;
data = new FormData();
put_policy = JSON.stringify({
"scope": "bucket" + ':' + key,
"deadline": DEADLINE
});
encoded = btoa(put_policy);
return new RSVP.Queue()
.push(function () {
return b64_hmac_sha1(context._secret_key, encoded);
})
.push(function (encode_signed) {
upload_token = context._access_key + ":" + encode_signed + ":" +
encoded;
data.append("key", key);
data.append("token", upload_token);
data.append(
"file",
blob,
key
);
return jIO.util.ajax({
type: "POST",
url: UPLOAD_URL,
data: data
});
});
};
QiniuStorage.prototype.hasCapacity = function (name) {
return (name === "list");
};
QiniuStorage.prototype.buildQuery = function () {
return [{
id: "/",
value: {}
}];
};
// QiniuStorage.prototype.remove = function (command, param) {
//
// var DELETE_HOST = "http://rs.qiniu.com",
// DELETE_PREFIX = "/delete/",
// encoded_entry_uri = urlsafe_base64_encode(btoa(
// this._bucket + ':' + param._id
// )),
// delete_url = DELETE_HOST + DELETE_PREFIX + encoded_entry_uri,
// data = DELETE_PREFIX + encoded_entry_uri + '\n',
// token = b64_hmac_sha1(this._secret_key, data);
//
// jIO.util.ajax({
// "type": "POST",
// "url": delete_url,
// "headers": {
// Authorization: "QBox " + this._access_key + ':' + token,
// "Content-Type": 'application/x-www-form-urlencoded'
// }
// }).then(
// command.success
// ).fail(function (error) {
// command.error(
// "not_found",
// "missing",
// "Unable to delete doc"
// );
// });
// };
//
// QiniuStorage.prototype.allDocs = function (command, param, options) {
// var LIST_HOST = "http://rsf.qiniu.com",
// LIST_PREFIX = "/list?bucket=" + this._bucket,
// list_url = LIST_HOST + LIST_PREFIX,
// token = b64_hmac_sha1(this._secret_key, LIST_PREFIX + '\n');
//
// jIO.util.ajax({
// "type": "POST",
// "url": list_url,
// "headers": {
// Authorization: "QBox " + this._access_key + ':' + token,
// "Content-Type": 'application/x-www-form-urlencoded'
// }
// }).then(function (response) {
// var data = JSON.parse(response.target.responseText),
// count = data.items.length,
// result = [],
// item,
// i;
// for (i = 0; i < count; i += 1) {
// item = data.items[i];
// result.push({
// id: item.key,
// key: item.key,
// doc: {},
// value: {}
// });
// }
// command.success({"data": {"rows": result, "total_rows": count}});
// }).fail(function (error) {
// command.error(
// "error",
// "did not work as expected",
// "Unable to call allDocs"
// );
// });
// };
jIO.addStorage('qiniu', QiniuStorage);
}(JSON, FormData, btoa, Blob, jIO, RSVP, UriTemplate, crypto, Uint8Array,
TextEncoder));
jio-master-src/src/jio.storage/querystorage.js 0000664 0000000 0000000 00000020452 14230544473 0022031 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global RSVP, jiodate*/
(function (jIO, RSVP, jiodate) {
"use strict";
function dateType(str) {
return jiodate.JIODate(new Date(str).toISOString());
}
function initKeySchema(storage, spec) {
var property;
for (property in spec.schema) {
if (spec.schema.hasOwnProperty(property)) {
if (spec.schema[property].type === "string" &&
spec.schema[property].format === "date-time") {
storage._key_schema.key_set[property] = {
read_from: property,
cast_to: "dateType"
};
if (storage._key_schema.cast_lookup.dateType === undefined) {
storage._key_schema.cast_lookup.dateType = dateType;
}
} else {
throw new jIO.util.jIOError(
"Wrong schema for property: " + property,
400
);
}
}
}
}
/**
* The jIO QueryStorage extension
*
* @class QueryStorage
* @constructor
*/
function QueryStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._key_schema = {key_set: {}, cast_lookup: {}};
initKeySchema(this, spec);
}
QueryStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.post = function () {
return this._sub_storage.post.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.put = function () {
return this._sub_storage.put.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.getAttachment = function () {
return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.putAttachment = function () {
return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.removeAttachment = function () {
return this._sub_storage.removeAttachment.apply(this._sub_storage,
arguments);
};
QueryStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
QueryStorage.prototype.hasCapacity = function (name) {
var this_storage_capacity_list = ["limit",
"sort",
"select",
"query"];
if (this_storage_capacity_list.indexOf(name) !== -1) {
return true;
}
if (name === "list") {
return this._sub_storage.hasCapacity(name);
}
return false;
};
QueryStorage.prototype.buildQuery = function (options) {
var substorage = this._sub_storage,
context = this,
sub_options = {},
is_manual_query_needed = false,
is_manual_include_needed = false;
if (substorage.hasCapacity("list")) {
// Can substorage handle the queries if needed?
try {
if (((options.query === undefined) ||
(substorage.hasCapacity("query"))) &&
((options.sort_on === undefined) ||
(substorage.hasCapacity("sort"))) &&
((options.select_list === undefined) ||
(substorage.hasCapacity("select"))) &&
((options.limit === undefined) ||
(substorage.hasCapacity("limit")))) {
sub_options.query = options.query;
sub_options.sort_on = options.sort_on;
sub_options.select_list = options.select_list;
sub_options.limit = options.limit;
}
} catch (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 501)) {
is_manual_query_needed = true;
} else {
throw error;
}
}
// Can substorage include the docs if needed?
try {
if ((is_manual_query_needed ||
(options.include_docs === true)) &&
(substorage.hasCapacity("include"))) {
sub_options.include_docs = true;
}
} catch (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 501)) {
is_manual_include_needed = true;
} else {
throw error;
}
}
return substorage.buildQuery(sub_options)
// Include docs if needed
.push(function (result) {
var include_query_list = [result],
len,
i;
function safeGet(j) {
var id = result[j].id;
return substorage.get(id)
.push(function (doc) {
// XXX Can delete user data!
doc._id = id;
return doc;
}, function (error) {
// Document may have been dropped after listing
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return;
}
throw error;
});
}
if (is_manual_include_needed) {
len = result.length;
for (i = 0; i < len; i += 1) {
include_query_list.push(safeGet(i));
}
result = RSVP.all(include_query_list);
}
return result;
})
.push(function (result) {
var original_result,
len,
i;
if (is_manual_include_needed) {
original_result = result[0];
len = original_result.length;
for (i = 0; i < len; i += 1) {
original_result[i].doc = result[i + 1];
}
result = original_result;
}
return result;
})
// Manual query if needed
.push(function (result) {
var data_rows = [],
len,
i;
if (is_manual_query_needed) {
len = result.length;
for (i = 0; i < len; i += 1) {
result[i].doc.__id = result[i].id;
data_rows.push(result[i].doc);
}
if (options.select_list) {
options.select_list.push("__id");
}
result = jIO.QueryFactory.create(options.query || "",
context._key_schema).
exec(data_rows, options);
}
return result;
})
// reconstruct filtered rows, preserving the order from docs
.push(function (result) {
var new_result = [],
element,
len,
i;
if (is_manual_query_needed) {
len = result.length;
for (i = 0; i < len; i += 1) {
element = {
id: result[i].__id,
value: options.select_list ? result[i] : {},
doc: {}
};
if (options.select_list) {
// Does not work if user manually request __id
delete element.value.__id;
}
if (options.include_docs) {
// XXX To implement
throw new Error("QueryStorage does not support include docs");
}
new_result.push(element);
}
result = new_result;
}
return result;
});
}
};
jIO.addStorage('query', QueryStorage);
}(jIO, RSVP, jiodate));
jio-master-src/src/jio.storage/replicaterevisionstorage.js 0000664 0000000 0000000 00000056050 14230544473 0024416 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, define */
/**
* JIO Replicate Revision Storage.
* It manages storages that manage revisions and conflicts.
* Description:
* {
* "type": "replicaterevision",
* "storage_list": [
* ,
* ...
* ]
* }
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO);
}(['jio'], function (jIO) {
"use strict";
jIO.addStorage('replicaterevision', function (spec) {
var that = this, priv = {};
spec = spec || {};
priv.storage_list_key = "storage_list";
priv.storage_list = spec[priv.storage_list_key];
priv.emptyFunction = function () {
return;
};
/**
* Generate a new uuid
*
* @return {string} The new uuid
*/
priv.generateUuid = function () {
var S4 = function () {
var i, string = Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = "0" + string;
}
return string;
};
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
};
/**
* Create an array containing dictionnary keys
*
* @param {object} dict The object to convert
* @return {array} The array of keys
*/
priv.dictKeys2Array = function (dict) {
var k, newlist = [];
for (k in dict) {
if (dict.hasOwnProperty(k)) {
newlist.push(k);
}
}
return newlist;
};
/**
* Checks a revision format
*
* @param {string} revision The revision string
* @return {boolean} True if ok, else false
*/
priv.checkRevisionFormat = function (revision) {
return (/^[0-9]+-[0-9a-zA-Z_]+$/.test(revision));
};
/**
* Clones an object in deep (without functions)
*
* @param {any} object The object to clone
* @return {any} The cloned object
*/
priv.clone = function (object) {
var tmp = JSON.stringify(object);
if (tmp === undefined) {
return undefined;
}
return JSON.parse(tmp);
};
/**
* Like addJob but also return the method and the index of the storage
*
* @param {string} method The request method
* @param {number} index The storage index
* @param {object} doc The document object
* @param {object} option The request object
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv.send = function (command, method, index, doc, option, callback) {
var wrapped_callback_success, wrapped_callback_error;
callback = callback || priv.emptyFunction;
wrapped_callback_success = function (response) {
callback(method, index, undefined, response);
};
wrapped_callback_error = function (err) {
callback(method, index, err, undefined);
};
if (method === 'allDocs') {
command.storage(priv.storage_list[index]).allDocs(option).
then(wrapped_callback_success, wrapped_callback_error);
} else {
command.storage(priv.storage_list[index])[method](doc, option).
then(wrapped_callback_success, wrapped_callback_error);
}
};
/**
* Use "send" method to all sub storages.
* Calling "callback" for each storage response.
*
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv.sendToAll = function (command, method, doc, option, callback) {
var i;
for (i = 0; i < priv.storage_list.length; i += 1) {
priv.send(command, method, i, doc, option, callback);
}
};
/**
* Use "send" method to all sub storages.
* Calling "callback" only with the first response
*
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {object} The error object
* - {object} The response object
*/
priv.sendToAllFastestResponseOnly = function (command, method,
doc, option, callback) {
var i, callbackWrapper, error_count;
error_count = 0;
callbackWrapper = function (method, index, err, response) {
/*jslint unparam: true */
if (err) {
error_count += 1;
if (error_count === priv.storage_list.length) {
return callback(method, err, response);
}
}
callback(method, err, response);
};
for (i = 0; i < priv.storage_list.length; i += 1) {
priv.send(command, method, i, doc, option, callbackWrapper);
}
};
/**
* Use "sendToAll" method, calling "callback" at the last response with
* the response list
*
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @return {function} callback The callback. Parameters:
* - {string} The request method
* - {object} The error object
* - {object} The response object
*/
priv.sendToAllGetResponseList = function (command, method,
doc, option, callback) {
var wrapper, callback_count = 0, response_list = [], error_list = [];
response_list.length = priv.storage_list.length;
wrapper = function (method, index, err, response) {
/*jslint unparam: true */
error_list[index] = err;
response_list[index] = response;
callback_count += 1;
if (callback_count === priv.storage_list.length) {
callback(error_list, response_list);
}
};
priv.sendToAll(command, method, doc, option, wrapper);
};
/**
* Checks if the sub storage are identical
* @method check
* @param {object} command The JIO command
*/
that.check = function (command, param, option) {
function callback(err) {
if (err) {
return command.error(err);
}
command.success();
}
if (!param._id) {
return callback({"status": 501});
}
priv.check(command, param, option, callback);
};
/**
* Repair the sub storages to make them identical
* @method repair
* @param {object} command The JIO command
*/
that.repair = function (command, param, option) {
function callback(err) {
if (err) {
return command.error(err);
}
command.success();
}
if (!param._id) {
return callback({
"status": 501
});
}
priv.repair(
command,
param,
option,
true,
callback
);
};
priv.check = function (command, doc, option, success, error) {
priv.repair(command, doc, option, false, success, error);
};
priv.repair = function (command, doc, option, repair, callback) {
var functions = {};
callback = callback || priv.emptyFunction;
option = option || {};
functions.begin = function () {
// // XXX make revision storage check and repair
// // to enable check/repair sub storage from this storage
// // by calling this function just below
// //functions.repairAllSubStorages();
// // else we assume that sub storages are good
// functions.getAllDocuments(functions.newParam(
// doc,
// option,
// repair
// ));
// };
// functions.repairAllSubStorages = function () {
var i;
for (i = 0; i < priv.storage_list.length; i += 1) {
priv.send(
command,
repair ? "repair" : "check",
i,
doc,
option,
functions.repairAllSubStoragesCallback
);
}
};
functions.repair_sub_storages_count = 0;
functions.repairAllSubStoragesCallback = function (method,
index, err, response) {
/*jslint unparam: true */
if (err) {
return command.error(err);
}
functions.repair_sub_storages_count += 1;
if (functions.repair_sub_storages_count === priv.storage_list.length) {
functions.getAllDocuments(functions.newParam(
doc,
option,
repair
));
}
};
functions.newParam = function (doc, option, repair) {
var param = {
"doc": doc, // the document to repair
"option": option,
"repair": repair,
"responses": {
"count": 0,
"list": [
// 0: response0
// 1: response1
// 2: response2
],
"stats": {
// responseA: [0, 1]
// responseB: [2]
},
"stats_items": [
// 0: [responseA, [0, 1]]
// 1: [responseB, [2]]
],
"attachments": {
// attachmentA : {_id: attachmentA, _revs_info, _content_type: ..}
// attachmentB : {_id: attachmentB, _revs_info, _content_type: ..}
}
},
"conflicts": {
// revC: true
// revD: true
},
"deal_result_state": "ok",
"my_rev": undefined
};
param.responses.list.length = priv.storage_list.length;
return param;
};
functions.getAllDocuments = function (param) {
var i, metadata, cloned_option;
metadata = priv.clone(param.doc);
cloned_option = priv.clone(param.option);
cloned_option.conflicts = true;
cloned_option.revs = true;
cloned_option.revs_info = true;
for (i = 0; i < priv.storage_list.length; i += 1) {
// if the document is not loaded
priv.send(command, "get", i,
metadata, cloned_option, functions.dealResults(param));
}
functions.finished_count += 1;
};
functions.dealResults = function (param) {
return function (method, index, err, response) {
/*jslint unparam: true */
var response_object = {};
if (param.deal_result_state !== "ok") {
// deal result is in a wrong state, exit
return;
}
if (err) {
if (err.status !== 404) {
// get document failed, exit
param.deal_result_state = "error";
callback({
"status": 409,
"message": "An error occured on the sub storage",
"reason": err.reason
}, undefined);
return;
}
}
response = response.data;
// success to get the document
// add the response in memory
param.responses.count += 1;
param.responses.list[index] = response;
// add the conflicting revision for other synchronizations
functions.addConflicts(param, (response || {})._conflicts);
if (param.responses.count !== param.responses.list.length) {
// this is not the last response, wait for the next response
return;
}
// this is now the last response
functions.makeResponsesStats(param.responses);
//console.log(JSON.parse(JSON.stringify(param.responses)));
if (param.responses.stats_items.length === 1) {
// the responses are equals!
response_object.ok = true;
response_object.id = param.doc._id;
if (doc._rev) {
response_object.rev = doc._rev;
// "rev": (typeof param.responses.list[0] === "object" ?
// param.responses.list[0]._rev : undefined)
}
callback(undefined, response_object);
return;
}
// the responses are different
if (param.repair === false) {
// do not repair
callback({
"status": 409,
"message": "Some documents are different in the sub storages",
"reason": "Storage contents differ"
}, undefined);
return;
}
// repair
functions.getAttachments(param);
};
};
functions.addConflicts = function (param, list) {
var i;
list = list || [];
for (i = 0; i < list.length; i += 1) {
param.conflicts[list[i]] = true;
}
};
functions.makeResponsesStats = function (responses) {
var i, str_response;
for (i = 0; i < responses.count; i += 1) {
str_response = JSON.stringify(responses.list[i]);
if (responses.stats[str_response] === undefined) {
responses.stats[str_response] = [];
responses.stats_items.push([
str_response,
responses.stats[str_response]
]);
}
responses.stats[str_response].push(i);
}
};
functions.getAttachments = function (param) {
var response, parsed_response, attachment;
for (response in param.responses.stats) {
if (param.responses.stats.hasOwnProperty(response)) {
parsed_response = JSON.parse(response);
for (attachment in parsed_response._attachments) {
if ((parsed_response._attachments).hasOwnProperty(attachment)) {
functions.get_attachment_count += 1;
priv.send(
command,
"getAttachment",
param.responses.stats[response][0],
{
"_id": param.doc._id,
"_attachment": attachment,
"_rev": JSON.parse(response)._rev
},
param.option,
functions.getAttachmentsCallback(
param,
attachment,
param.responses.stats[response]
)
);
}
}
}
}
};
functions.get_attachment_count = 0;
functions.getAttachmentsCallback = function (param, attachment_id) {
return function (method, index, err, response) {
/*jslint unparam: true */
if (err) {
callback({
"status": 409,
"message": "Unable to retreive attachments",
"reason": err.reason
}, undefined);
return;
}
response = response.data;
functions.get_attachment_count -= 1;
param.responses.attachments[attachment_id] = response;
if (functions.get_attachment_count === 0) {
functions.synchronizeAllSubStorage(param);
if (param.option.synchronize_conflicts !== false) {
functions.synchronizeConflicts(param);
}
}
};
};
functions.synchronizeAllSubStorage = function (param) {
var i, j, len = param.responses.stats_items.length;
for (i = 0; i < len; i += 1) {
// browsing responses
for (j = 0; j < len; j += 1) {
// browsing storage list
if (i !== j) {
functions.synchronizeResponseToSubStorage(
param,
param.responses.stats_items[i][0],
param.responses.stats_items[j][1]
);
}
}
}
functions.finished_count -= 1;
};
functions.synchronizeResponseToSubStorage = function (
param,
response,
storage_list
) {
var i, new_doc, attachment_to_put = [];
if (response === undefined) {
// no response to sync
return;
}
new_doc = JSON.parse(response);
new_doc._revs = new_doc._revisions;
delete new_doc._rev;
delete new_doc._revisions;
delete new_doc._conflicts;
for (i in new_doc._attachments) {
if (new_doc._attachments.hasOwnProperty(i)) {
attachment_to_put.push({
"_id": i,
"_content_type": new_doc._attachments[i].content_type,
"_revs_info": new_doc._revs_info
});
}
}
for (i = 0; i < storage_list.length; i += 1) {
functions.finished_count += attachment_to_put.length || 1;
priv.send(
command,
"put",
storage_list[i],
new_doc,
param.option,
functions.putAttachments(param, attachment_to_put)
);
}
functions.finished_count += 1;
functions.finished();
};
functions.synchronizeConflicts = function (param) {
var rev, new_doc, new_option;
new_option = priv.clone(param.option);
new_option.synchronize_conflict = false;
for (rev in param.conflicts) {
if (param.conflicts.hasOwnProperty(rev)) {
new_doc = priv.clone(param.doc);
new_doc._rev = rev;
// no need to synchronize all the conflicts again, do it once
functions.getAllDocuments(functions.newParam(
new_doc,
new_option,
param.repair
));
}
}
};
functions.putAttachments = function (param, attachment_to_put) {
return function (method, index, err, response) {
/*jslint unparam: true */
var i, attachment;
if (err) {
return callback({
"status": 409,
"message": "Unable to copy attachments",
"reason": err.reason
}, undefined);
}
for (i = 0; i < attachment_to_put.length; i += 1) {
attachment = {
"_id": param.doc._id,
"_attachment": attachment_to_put[i]._id,
"_content_type": attachment_to_put[i]._content_type,
"_revs_info": attachment_to_put[i]._revs_info,
// "_revs_info": param.responses.list[index]._revs_info,
"_data": param.responses.attachments[attachment_to_put[i]._id]
};
priv.send(
command,
"putAttachment",
index,
attachment,
option,
functions.putAttachmentCallback(param)
);
}
if (attachment_to_put.length === 0) {
functions.finished();
}
};
};
functions.putAttachmentCallback = function (param) {
/*jslint unparam: true */
return function (method, index, err, response) {
if (err) {
return callback(err, undefined);
}
functions.finished();
};
};
functions.finished_count = 0;
functions.finished = function () {
var response_object = {};
functions.finished_count -= 1;
if (functions.finished_count === 0) {
response_object.ok = true;
response_object.id = doc._id;
if (doc._rev) {
response_object.rev = doc._rev;
}
callback(undefined, response_object);
}
};
functions.begin();
};
/**
* The generic method to use
* @method genericRequest
* @param {object} command The JIO command
* @param {string} method The method to use
*/
that.genericRequest = function (command, method, param, option) {
var doc = param;
doc._id = doc._id || priv.generateUuid();
priv.sendToAllFastestResponseOnly(
command,
method,
doc,
option,
function (method, err, response) {
/*jslint unparam: true */
if (err) {
return command.error(err);
}
command.success(response);
}
);
};
/**
* Post the document metadata to all sub storages
* @method post
* @param {object} command The JIO command
*/
that.post = function (command, metadata, option) {
that.genericRequest(command, "post", metadata, option);
};
/**
* Put the document metadata to all sub storages
* @method put
* @param {object} command The JIO command
*/
that.put = function (command, metadata, option) {
that.genericRequest(command, "put", metadata, option);
};
/**
* Put an attachment to a document to all sub storages
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command, param, option) {
that.genericRequest(command, "putAttachment", param, option);
};
/**
* Get the document from all sub storages, get the fastest.
* @method get
* @param {object} command The JIO command
*/
that.get = function (command, param, option) {
that.genericRequest(command, "get", param, option);
};
/**
* Get the attachment from all sub storages, get the fastest.
* @method getAttachment
* @param {object} command The JIO command
*/
that.getAttachment = function (command, param, option) {
that.genericRequest(command, "getAttachment", param, option);
};
/**
* Remove the document from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command, param, option) {
that.genericRequest(command, "remove", param, option);
};
/**
* Remove the attachment from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that.removeAttachment = function (command, param, option) {
that.genericRequest(command, "removeAttachment", param, option);
};
});
}));
jio-master-src/src/jio.storage/replicatestorage.js 0000664 0000000 0000000 00000212555 14230544473 0022643 0 ustar 00root root 0000000 0000000 /*
* JIO extension for resource replication.
* Copyright (C) 2013, 2015 Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global jIO, RSVP, Rusha*/
(function (jIO, RSVP, Rusha, stringify) {
"use strict";
var rusha = new Rusha(),
CONFLICT_THROW = 0,
CONFLICT_KEEP_LOCAL = 1,
CONFLICT_KEEP_REMOTE = 2,
CONFLICT_CONTINUE = 3,
// 0 - 99 error
LOG_UNEXPECTED_ERROR = 0,
LOG_UNRESOLVED_CONFLICT = 74,
LOG_UNEXPECTED_LOCAL_ATTACHMENT = 49,
LOG_UNEXPECTED_REMOTE_ATTACHMENT = 47,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT = 75,
// 100 - 199 solving conflict
LOG_FORCE_PUT_REMOTE = 116,
LOG_FORCE_DELETE_REMOTE = 136,
LOG_FORCE_PUT_REMOTE_ATTACHMENT = 117,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT = 137,
LOG_FORCE_PUT_LOCAL = 118,
LOG_FORCE_DELETE_LOCAL = 138,
LOG_FORCE_PUT_LOCAL_ATTACHMENT = 119,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT = 139,
// 200 - 299 pushing change
LOG_PUT_REMOTE = 216,
LOG_POST_REMOTE = 226,
LOG_DELETE_REMOTE = 236,
LOG_PUT_REMOTE_ATTACHMENT = 217,
LOG_DELETE_REMOTE_ATTACHMENT = 237,
LOG_PUT_LOCAL = 218,
LOG_POST_LOCAL = 228,
LOG_DELETE_LOCAL = 238,
LOG_PUT_LOCAL_ATTACHMENT = 219,
LOG_DELETE_LOCAL_ATTACHMENT = 239,
LOG_FALSE_CONFLICT = 284,
LOG_FALSE_CONFLICT_ATTACHMENT = 285,
// 300 - 399 nothing to do
LOG_SKIP_LOCAL_CREATION = 348,
LOG_SKIP_LOCAL_MODIFICATION = 358,
LOG_SKIP_LOCAL_DELETION = 368,
LOG_SKIP_REMOTE_CREATION = 346,
LOG_SKIP_REMOTE_MODIFICATION = 356,
LOG_SKIP_REMOTE_DELETION = 366,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION = 349,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION = 359,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION = 369,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION = 347,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION = 357,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION = 367,
LOG_SKIP_CONFLICT = 374,
LOG_SKIP_CONFLICT_ATTACHMENT = 375,
LOG_NO_CHANGE = 384,
LOG_NO_CHANGE_ATTACHMENT = 385;
function ReplicateReport(log_level, log_console) {
this._list = [];
this.name = 'ReplicateReport';
this.message = this.name;
this.has_error = false;
this._log_level = log_level;
this._log_console = log_console;
}
ReplicateReport.prototype = {
constructor: ReplicateReport,
LOG_UNEXPECTED_ERROR: LOG_UNEXPECTED_ERROR,
LOG_UNRESOLVED_CONFLICT: LOG_UNRESOLVED_CONFLICT,
LOG_UNEXPECTED_LOCAL_ATTACHMENT: LOG_UNEXPECTED_LOCAL_ATTACHMENT,
LOG_UNEXPECTED_REMOTE_ATTACHMENT: LOG_UNEXPECTED_REMOTE_ATTACHMENT,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT: LOG_UNRESOLVED_ATTACHMENT_CONFLICT,
LOG_FORCE_PUT_REMOTE: LOG_FORCE_PUT_REMOTE,
LOG_FORCE_DELETE_REMOTE: LOG_FORCE_DELETE_REMOTE,
LOG_FORCE_PUT_LOCAL: LOG_FORCE_PUT_LOCAL,
LOG_FORCE_DELETE_LOCAL: LOG_FORCE_DELETE_LOCAL,
LOG_FORCE_PUT_REMOTE_ATTACHMENT: LOG_FORCE_PUT_REMOTE_ATTACHMENT,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT: LOG_FORCE_DELETE_REMOTE_ATTACHMENT,
LOG_FORCE_PUT_LOCAL_ATTACHMENT: LOG_FORCE_PUT_LOCAL_ATTACHMENT,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT: LOG_FORCE_DELETE_LOCAL_ATTACHMENT,
LOG_PUT_REMOTE: LOG_PUT_REMOTE,
LOG_POST_REMOTE: LOG_POST_REMOTE,
LOG_DELETE_REMOTE: LOG_DELETE_REMOTE,
LOG_PUT_REMOTE_ATTACHMENT: LOG_PUT_REMOTE_ATTACHMENT,
LOG_DELETE_REMOTE_ATTACHMENT: LOG_DELETE_REMOTE_ATTACHMENT,
LOG_PUT_LOCAL: LOG_PUT_LOCAL,
LOG_DELETE_LOCAL: LOG_DELETE_LOCAL,
LOG_PUT_LOCAL_ATTACHMENT: LOG_PUT_LOCAL_ATTACHMENT,
LOG_DELETE_LOCAL_ATTACHMENT: LOG_DELETE_LOCAL_ATTACHMENT,
LOG_FALSE_CONFLICT: LOG_FALSE_CONFLICT,
LOG_FALSE_CONFLICT_ATTACHMENT: LOG_FALSE_CONFLICT_ATTACHMENT,
LOG_SKIP_LOCAL_CREATION: LOG_SKIP_LOCAL_CREATION,
LOG_SKIP_LOCAL_MODIFICATION: LOG_SKIP_LOCAL_MODIFICATION,
LOG_SKIP_LOCAL_DELETION: LOG_SKIP_LOCAL_DELETION,
LOG_SKIP_REMOTE_CREATION: LOG_SKIP_REMOTE_CREATION,
LOG_SKIP_REMOTE_MODIFICATION: LOG_SKIP_REMOTE_MODIFICATION,
LOG_SKIP_REMOTE_DELETION: LOG_SKIP_REMOTE_DELETION,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION: LOG_SKIP_LOCAL_ATTACHMENT_CREATION,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION:
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION: LOG_SKIP_LOCAL_ATTACHMENT_DELETION,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION: LOG_SKIP_REMOTE_ATTACHMENT_CREATION,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION:
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION: LOG_SKIP_REMOTE_ATTACHMENT_DELETION,
LOG_SKIP_CONFLICT: LOG_SKIP_CONFLICT,
LOG_SKIP_CONFLICT_ATTACHMENT: LOG_SKIP_CONFLICT_ATTACHMENT,
LOG_NO_CHANGE: LOG_NO_CHANGE,
LOG_NO_CHANGE_ATTACHMENT: LOG_NO_CHANGE_ATTACHMENT,
logConsole: function (code, a, b, c) {
if (!this._log_console) {
return;
}
var txt = code,
parsed_code = code,
log;
// Check severity level
if (parsed_code >= 300) {
txt += ' SKIP ';
log = console.info;
} else if (parsed_code >= 200) {
txt += ' SOLVE ';
log = console.log;
} else if (parsed_code >= 100) {
txt += ' FORCE ';
log = console.warn;
} else {
txt += ' ERROR ';
log = console.error;
}
// Check operation
parsed_code = code % 100;
if (parsed_code >= 80) {
txt += 'idem ';
} else if (parsed_code >= 70) {
txt += 'conflict ';
} else if (parsed_code >= 60) {
txt += 'deleted ';
} else if (parsed_code >= 50) {
txt += 'modified ';
} else if (parsed_code >= 40) {
txt += 'created ';
} else if (parsed_code >= 30) {
txt += 'delete ';
} else if (parsed_code >= 20) {
txt += 'post ';
} else if (parsed_code >= 10) {
txt += 'put ';
}
// Check document
parsed_code = code % 10;
if (parsed_code >= 8) {
txt += 'local ';
} else if (parsed_code >= 6) {
txt += 'remote ';
}
if (parsed_code !== 0) {
txt += (parsed_code % 2 === 0) ? 'document' : 'attachment';
}
txt += ' ' + a;
if (b !== undefined) {
txt += ' ' + b;
if (c !== undefined) {
txt += ' ' + c;
}
}
log(txt);
},
log: function (id, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id);
this._list.push([type, id]);
} else {
this.logConsole(type, id, extra);
this._list.push([type, id, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
logAttachment: function (id, name, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id, name);
this._list.push([type, id, name]);
} else {
this.logConsole(type, id, name, extra);
this._list.push([type, id, name, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
toString: function () {
return this._list.toString();
}
};
function SkipError(message) {
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Skip some asynchronous code";
}
SkipError.prototype = new Error();
SkipError.prototype.constructor = SkipError;
/****************************************************
Use a local jIO to read/write/search documents
Synchronize in background those document with a remote jIO.
Synchronization status is stored for each document as an local attachment.
****************************************************/
function generateHash(content) {
// XXX Improve performance by moving calculation to WebWorker
return rusha.digestFromString(content);
}
function generateHashFromArrayBuffer(content) {
// XXX Improve performance by moving calculation to WebWorker
return rusha.digestFromArrayBuffer(content);
}
function ReplicateStorage(spec) {
this._query_options = spec.query || {};
this._log_level = spec.report_level || 100;
this._log_console = spec.debug || false;
if (spec.signature_hash_key !== undefined) {
this._query_options.select_list = [spec.signature_hash_key];
}
this._signature_hash_key = spec.signature_hash_key;
this._local_sub_storage = jIO.createJIO(spec.local_sub_storage);
this._remote_sub_storage = jIO.createJIO(spec.remote_sub_storage);
if (spec.hasOwnProperty('signature_sub_storage')) {
this._signature_sub_storage = jIO.createJIO(spec.signature_sub_storage);
this._custom_signature_sub_storage = true;
} else {
this._signature_hash = "_replicate_" + generateHash(
stringify(spec.local_sub_storage) +
stringify(spec.remote_sub_storage) +
stringify(this._query_options)
);
this._signature_sub_storage = jIO.createJIO({
type: "query",
sub_storage: {
type: "document",
document_id: this._signature_hash,
sub_storage: spec.local_sub_storage
}
});
this._custom_signature_sub_storage = false;
}
this._use_remote_post = spec.use_remote_post || false;
// Number of request we allow browser execution for attachments
this._parallel_operation_attachment_amount =
spec.parallel_operation_attachment_amount || 1;
// Number of request we allow browser execution for documents
this._parallel_operation_amount =
spec.parallel_operation_amount || 1;
this._conflict_handling = spec.conflict_handling || 0;
// 0: no resolution (ie, throw an Error)
// 1: keep the local state
// (overwrites the remote document with local content)
// (delete remote document if local is deleted)
// 2: keep the remote state
// (overwrites the local document with remote content)
// (delete local document if remote is deleted)
// 3: keep both copies (leave documents untouched, no signature update)
if ((this._conflict_handling !== CONFLICT_THROW) &&
(this._conflict_handling !== CONFLICT_KEEP_LOCAL) &&
(this._conflict_handling !== CONFLICT_KEEP_REMOTE) &&
(this._conflict_handling !== CONFLICT_CONTINUE)) {
throw new jIO.util.jIOError("Unsupported conflict handling: " +
this._conflict_handling, 400);
}
this._check_local_modification = spec.check_local_modification;
if (this._check_local_modification === undefined) {
this._check_local_modification = true;
}
this._check_local_creation = spec.check_local_creation;
if (this._check_local_creation === undefined) {
this._check_local_creation = true;
}
this._check_local_deletion = spec.check_local_deletion;
if (this._check_local_deletion === undefined) {
this._check_local_deletion = true;
}
this._check_remote_modification = spec.check_remote_modification;
if (this._check_remote_modification === undefined) {
this._check_remote_modification = true;
}
this._check_remote_creation = spec.check_remote_creation;
if (this._check_remote_creation === undefined) {
this._check_remote_creation = true;
}
this._check_remote_deletion = spec.check_remote_deletion;
if (this._check_remote_deletion === undefined) {
this._check_remote_deletion = true;
}
this._check_local_attachment_modification =
spec.check_local_attachment_modification;
if (this._check_local_attachment_modification === undefined) {
this._check_local_attachment_modification = false;
}
this._check_local_attachment_creation =
spec.check_local_attachment_creation;
if (this._check_local_attachment_creation === undefined) {
this._check_local_attachment_creation = false;
}
this._check_local_attachment_deletion =
spec.check_local_attachment_deletion;
if (this._check_local_attachment_deletion === undefined) {
this._check_local_attachment_deletion = false;
}
this._check_remote_attachment_modification =
spec.check_remote_attachment_modification;
if (this._check_remote_attachment_modification === undefined) {
this._check_remote_attachment_modification = false;
}
this._check_remote_attachment_creation =
spec.check_remote_attachment_creation;
if (this._check_remote_attachment_creation === undefined) {
this._check_remote_attachment_creation = false;
}
this._check_remote_attachment_deletion =
spec.check_remote_attachment_deletion;
if (this._check_remote_attachment_deletion === undefined) {
this._check_remote_attachment_deletion = false;
}
}
ReplicateStorage.prototype.remove = function (id) {
if (id === this._signature_hash) {
throw new jIO.util.jIOError(this._signature_hash + " is frozen",
403);
}
return this._local_sub_storage.remove.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.post = function () {
return this._local_sub_storage.post.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.put = function (id) {
if (id === this._signature_hash) {
throw new jIO.util.jIOError(this._signature_hash + " is frozen",
403);
}
return this._local_sub_storage.put.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.get = function () {
return this._local_sub_storage.get.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.getAttachment = function () {
return this._local_sub_storage.getAttachment.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.allAttachments = function () {
return this._local_sub_storage.allAttachments.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.putAttachment = function (id) {
if (id === this._signature_hash) {
throw new jIO.util.jIOError(this._signature_hash + " is frozen",
403);
}
return this._local_sub_storage.putAttachment.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.removeAttachment = function (id) {
if (id === this._signature_hash) {
throw new jIO.util.jIOError(this._signature_hash + " is frozen",
403);
}
return this._local_sub_storage.removeAttachment.apply(
this._local_sub_storage,
arguments
);
};
ReplicateStorage.prototype.hasCapacity = function () {
return this._local_sub_storage.hasCapacity.apply(this._local_sub_storage,
arguments);
};
ReplicateStorage.prototype.buildQuery = function () {
// XXX Remove signature document?
return this._local_sub_storage.buildQuery.apply(this._local_sub_storage,
arguments);
};
function dispatchQueue(context, function_used, argument_list,
number_queue) {
var result_promise_list = [],
i;
function pushAndExecute(queue) {
queue
.push(function () {
if (argument_list.length > 0) {
var argument_array = argument_list.shift(),
sub_queue = new RSVP.Queue();
argument_array[0] = sub_queue;
function_used.apply(context, argument_array);
pushAndExecute(queue);
return sub_queue;
}
});
}
for (i = 0; i < number_queue; i += 1) {
result_promise_list.push(new RSVP.Queue());
pushAndExecute(result_promise_list[i]);
}
if (number_queue > 1) {
return RSVP.all(result_promise_list);
}
return result_promise_list[0];
}
function callAllDocsOnStorage(context, storage, cache, cache_key) {
return new RSVP.Queue()
.push(function () {
if (!cache.hasOwnProperty(cache_key)) {
return storage.allDocs(context._query_options)
.push(function (result) {
var i,
cache_entry = {};
for (i = 0; i < result.data.total_rows; i += 1) {
cache_entry[result.data.rows[i].id] = result.data.rows[i].value;
}
cache[cache_key] = cache_entry;
});
}
})
.push(function () {
return cache[cache_key];
});
}
function propagateAttachmentDeletion(context,
destination,
id, name,
conflict, from_local, report) {
if (conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_DELETE_REMOTE_ATTACHMENT :
LOG_FORCE_DELETE_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
}
return destination.removeAttachment(id, name)
.push(function () {
return context._signature_sub_storage.removeAttachment(id, name);
});
}
function propagateAttachmentModification(context,
destination,
blob, hash, id, name,
from_local, is_conflict, report) {
if (is_conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_PUT_REMOTE_ATTACHMENT :
LOG_FORCE_PUT_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
}
return destination.putAttachment(id, name, blob)
.push(function () {
return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({
hash: hash
}));
});
}
function checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, local_hash, blob,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore, from_local, report) {
// No need to check twice
skip_attachment_dict[name] = null;
var remote_blob;
return destination.getAttachment(id, name)
.push(function (result) {
remote_blob = result;
return jIO.util.readBlobAsArrayBuffer(remote_blob);
})
.push(function (evt) {
return generateHashFromArrayBuffer(
evt.target.result
);
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
remote_blob = null;
return null;
}
throw error;
})
.push(function (remote_hash) {
if (local_hash === remote_hash) {
// Same modifications on both side
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
if (local_hash === null) {
// Deleted on both side, drop signature
return context._signature_sub_storage.removeAttachment(id, name);
}
return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({
hash: local_hash
}));
}
if ((remote_hash === status_hash) || (conflict_force === true)) {
// Modified only locally. No conflict or force
if (local_hash === null) {
// Deleted locally
return propagateAttachmentDeletion(context,
destination,
id, name,
(remote_hash !== status_hash),
from_local, report);
}
return propagateAttachmentModification(context,
destination, blob,
local_hash, id, name,
from_local,
(remote_hash !== status_hash),
report);
}
// Conflict cases
if (conflict_ignore === true) {
report.logAttachment(id, name, LOG_SKIP_CONFLICT_ATTACHMENT);
return;
}
if ((conflict_revert === true) || (local_hash === null)) {
// Automatically resolve conflict or force revert
if (remote_hash === null) {
// Deleted remotely
return propagateAttachmentDeletion(context,
source, id, name,
(local_hash !== status_hash),
!from_local, report);
}
return propagateAttachmentModification(
context,
source,
remote_blob,
remote_hash,
id,
name,
!from_local,
(local_hash !== status_hash),
report
);
}
// Minimize conflict if it can be resolved
if (remote_hash === null) {
// Copy remote modification remotely
return propagateAttachmentModification(context,
destination, blob,
local_hash, id, name, from_local,
false,
report);
}
report.logAttachment(id, name, LOG_UNRESOLVED_ATTACHMENT_CONFLICT);
})
.push(undefined, function (error) {
report.logAttachment(id, name, LOG_UNEXPECTED_ERROR, error);
});
}
function checkAttachmentSignatureDifference(queue, context,
skip_attachment_dict,
source,
destination, id, name,
conflict_force,
conflict_revert,
conflict_ignore,
is_creation, is_modification,
from_local,
report) {
var blob,
status_hash;
queue
.push(function () {
// Optimisation to save a get call to signature storage
if (is_creation === true) {
return RSVP.all([
source.getAttachment(id, name),
{hash: null}
]);
}
if (is_modification === true) {
return RSVP.all([
source.getAttachment(id, name),
context._signature_sub_storage.getAttachment(
id,
name,
{format: 'json'}
)
]);
}
throw new jIO.util.jIOError("Unexpected call of"
+ " checkAttachmentSignatureDifference",
409);
})
.push(function (result_list) {
blob = result_list[0];
status_hash = result_list[1].hash;
return jIO.util.readBlobAsArrayBuffer(blob);
})
.push(function (evt) {
var array_buffer = evt.target.result,
local_hash = generateHashFromArrayBuffer(array_buffer);
if (local_hash === status_hash) {
if (!from_local) {
report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
}
return;
}
return checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, local_hash, blob,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore,
from_local,
report);
});
}
function checkAttachmentLocalDeletion(queue, context,
skip_attachment_dict,
destination, id, name, source,
conflict_force, conflict_revert,
conflict_ignore, from_local, report) {
var status_hash;
queue
.push(function () {
return context._signature_sub_storage.getAttachment(id, name,
{format: 'json'});
})
.push(function (result) {
status_hash = result.hash;
return checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, null, null,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore, from_local, report);
});
}
function pushDocumentAttachment(context,
skip_attachment_dict, id, source,
destination, signature_allAttachments,
report, options) {
var local_dict = {},
signature_dict = {},
from_local = options.from_local;
return source.allAttachments(id)
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return {};
}
throw error;
})
.push(function (source_allAttachments) {
var is_modification,
is_creation,
key,
argument_list = [];
for (key in source_allAttachments) {
if (source_allAttachments.hasOwnProperty(key)) {
if (!skip_attachment_dict.hasOwnProperty(key)) {
local_dict[key] = null;
}
}
}
for (key in signature_allAttachments) {
if (signature_allAttachments.hasOwnProperty(key)) {
if (!skip_attachment_dict.hasOwnProperty(key)) {
signature_dict[key] = null;
}
}
}
for (key in local_dict) {
if (local_dict.hasOwnProperty(key)) {
is_modification = signature_dict.hasOwnProperty(key)
&& options.check_modification;
is_creation = !signature_dict.hasOwnProperty(key)
&& options.check_creation;
if (is_modification === true || is_creation === true) {
argument_list.push([undefined,
context,
skip_attachment_dict,
source,
destination, id, key,
options.conflict_force,
options.conflict_revert,
options.conflict_ignore,
is_creation,
is_modification,
from_local,
report]);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
}
}
}
return dispatchQueue(
context,
checkAttachmentSignatureDifference,
argument_list,
context._parallel_operation_attachment_amount
);
})
.push(function () {
var key, argument_list = [];
for (key in signature_dict) {
if (signature_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) {
if (options.check_deletion === true) {
argument_list.push([undefined,
context,
skip_attachment_dict,
destination, id, key,
source,
options.conflict_force,
options.conflict_revert,
options.conflict_ignore,
from_local,
report]);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
}
}
}
}
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
});
}
function propagateFastAttachmentDeletion(queue, id, name, storage, signature,
from_local, report) {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
return queue
.push(function () {
return storage.removeAttachment(id, name);
})
.push(function () {
return signature.removeAttachment(id, name);
});
}
function propagateFastSignatureDeletion(queue, id, name, signature,
report) {
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
return queue
.push(function () {
return signature.removeAttachment(id, name);
});
}
function propagateFastAttachmentModification(queue, id, key, source,
destination, signature, hash,
from_local, report) {
return queue
.push(function () {
return signature.getAttachment(id, key, {format: 'json'})
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return {hash: null};
}
throw error;
})
.push(function (result) {
if (result.hash !== hash) {
report.logAttachment(id, key, from_local ?
LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
return source.getAttachment(id, key)
.push(function (blob) {
return destination.putAttachment(id, key, blob);
})
.push(function () {
return signature.putAttachment(id, key, JSON.stringify({
hash: hash
}));
});
}
});
});
}
function repairFastDocumentAttachment(context, id,
signature_hash,
signature_attachment_hash,
signature_from_local,
report) {
if (signature_hash === signature_attachment_hash) {
// No replication to do
return;
}
return new RSVP.Queue()
.push(function () {
return RSVP.all([
context._signature_sub_storage.allAttachments(id),
context._local_sub_storage.allAttachments(id),
context._remote_sub_storage.allAttachments(id)
]);
})
.push(function (result_list) {
var key,
source_attachment_dict,
destination_attachment_dict,
source,
destination,
push_argument_list = [],
delete_argument_list = [],
delete_signature_argument_list = [],
signature_attachment_dict = result_list[0],
local_attachment_dict = result_list[1],
remote_attachment_list = result_list[2],
check_local_modification =
context._check_local_attachment_modification,
check_local_creation = context._check_local_attachment_creation,
check_local_deletion = context._check_local_attachment_deletion,
check_remote_modification =
context._check_remote_attachment_modification,
check_remote_creation = context._check_remote_attachment_creation,
check_remote_deletion = context._check_remote_attachment_deletion,
from_local;
if (signature_from_local) {
source_attachment_dict = local_attachment_dict;
destination_attachment_dict = remote_attachment_list;
source = context._local_sub_storage;
destination = context._remote_sub_storage;
from_local = true;
} else {
source_attachment_dict = remote_attachment_list;
destination_attachment_dict = local_attachment_dict;
source = context._remote_sub_storage;
destination = context._local_sub_storage;
check_local_modification = check_remote_modification;
check_local_creation = check_remote_creation;
check_local_deletion = check_remote_deletion;
check_remote_creation = check_local_creation;
check_remote_deletion = check_local_deletion;
from_local = false;
}
// Push all source attachments
for (key in source_attachment_dict) {
if (source_attachment_dict.hasOwnProperty(key)) {
if ((check_local_creation &&
!signature_attachment_dict.hasOwnProperty(key)) ||
(check_local_modification &&
signature_attachment_dict.hasOwnProperty(key))) {
push_argument_list.push([
undefined,
id,
key,
source,
destination,
context._signature_sub_storage,
signature_hash,
from_local,
report
]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
}
}
}
// Delete remaining signature + remote attachments
for (key in signature_attachment_dict) {
if (signature_attachment_dict.hasOwnProperty(key)) {
if (check_local_deletion &&
!source_attachment_dict.hasOwnProperty(key) &&
!destination_attachment_dict.hasOwnProperty(key)) {
delete_signature_argument_list.push([
undefined,
id,
key,
context._signature_sub_storage,
report
]);
}
}
}
for (key in destination_attachment_dict) {
if (destination_attachment_dict.hasOwnProperty(key)) {
if (!source_attachment_dict.hasOwnProperty(key)) {
if ((check_local_deletion &&
signature_attachment_dict.hasOwnProperty(key)) ||
(check_remote_creation &&
!signature_attachment_dict.hasOwnProperty(key))) {
delete_argument_list.push([
undefined,
id,
key,
destination,
context._signature_sub_storage,
from_local,
report
]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
}
}
}
}
return RSVP.all([
dispatchQueue(
context,
propagateFastAttachmentModification,
push_argument_list,
context._parallel_operation_attachment_amount
),
dispatchQueue(
context,
propagateFastAttachmentDeletion,
delete_argument_list,
context._parallel_operation_attachment_amount
),
dispatchQueue(
context,
propagateFastSignatureDeletion,
delete_signature_argument_list,
context._parallel_operation_attachment_amount
)
]);
})
.push(function () {
// Mark that all attachments have been synchronized
return context._signature_sub_storage.put(id, {
hash: signature_hash,
attachment_hash: signature_hash,
from_local: signature_from_local
});
});
}
function repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash,
signature_attachment_hash,
signature_from_local) {
if (signature_hash_key !== undefined) {
return repairFastDocumentAttachment(context, id,
signature_hash,
signature_attachment_hash,
signature_from_local, report);
}
var skip_attachment_dict = {};
return new RSVP.Queue()
.push(function () {
if (context._check_local_attachment_modification ||
context._check_local_attachment_creation ||
context._check_local_attachment_deletion ||
context._check_remote_attachment_modification ||
context._check_remote_attachment_creation ||
context._check_remote_attachment_deletion) {
return context._signature_sub_storage.allAttachments(id);
}
return {};
})
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return {};
}
throw error;
})
.push(function (signature_allAttachments) {
if (context._check_local_attachment_modification ||
context._check_local_attachment_creation ||
context._check_local_attachment_deletion) {
return pushDocumentAttachment(
context,
skip_attachment_dict,
id,
context._local_sub_storage,
context._remote_sub_storage,
signature_allAttachments,
report,
{
conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL),
conflict_revert: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE),
conflict_ignore: (context._conflict_handling ===
CONFLICT_CONTINUE),
check_modification:
context._check_local_attachment_modification,
check_creation: context._check_local_attachment_creation,
check_deletion: context._check_local_attachment_deletion,
from_local: true
}
)
.push(function () {
return signature_allAttachments;
});
}
return signature_allAttachments;
})
.push(function (signature_allAttachments) {
if (context._check_remote_attachment_modification ||
context._check_remote_attachment_creation ||
context._check_remote_attachment_deletion) {
return pushDocumentAttachment(
context,
skip_attachment_dict,
id,
context._remote_sub_storage,
context._local_sub_storage,
signature_allAttachments,
report,
{
use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE),
conflict_revert: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL),
conflict_ignore: (context._conflict_handling ===
CONFLICT_CONTINUE),
check_modification:
context._check_remote_attachment_modification,
check_creation: context._check_remote_attachment_creation,
check_deletion: context._check_remote_attachment_deletion,
from_local: false
}
);
}
});
}
function propagateModification(context, source, destination, doc, hash, id,
skip_document_dict,
skip_deleted_document_dict,
report,
options) {
var result = new RSVP.Queue(),
post_id,
from_local,
conflict;
if (options === undefined) {
options = {};
}
from_local = options.from_local;
conflict = options.conflict || false;
if (doc === null) {
result
.push(function () {
return source.get(id);
})
.push(function (source_doc) {
doc = source_doc;
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
throw new SkipError(id);
}
throw error;
});
}
if (options.use_post) {
result
.push(function () {
report.log(id, from_local ? LOG_POST_REMOTE : LOG_POST_LOCAL);
return destination.post(doc);
})
.push(function (new_id) {
post_id = new_id;
return source.put(post_id, doc);
})
.push(function () {
// Copy all attachments
// This is not related to attachment replication
// It's just about not losing user data
return source.allAttachments(id);
})
.push(function (attachment_dict) {
var key,
copy_queue = new RSVP.Queue();
function copyAttachment(name) {
copy_queue
.push(function () {
return source.getAttachment(id, name);
})
.push(function (blob) {
return source.putAttachment(post_id, name, blob);
});
}
for (key in attachment_dict) {
if (attachment_dict.hasOwnProperty(key)) {
copyAttachment(key);
}
}
return copy_queue;
})
.push(function () {
return source.remove(id);
})
.push(function () {
return context._signature_sub_storage.remove(id);
})
.push(function () {
return context._signature_sub_storage.put(post_id, {
hash: hash,
from_local: from_local
});
})
.push(function () {
skip_document_dict[post_id] = null;
});
} else {
result
.push(function () {
if (conflict) {
report.log(id, from_local ? LOG_FORCE_PUT_REMOTE :
LOG_FORCE_PUT_LOCAL);
} else {
report.log(id, from_local ? LOG_PUT_REMOTE : LOG_PUT_LOCAL);
}
// Drop signature if the destination document was empty
// but a signature exists
if (options.create_new_document === true) {
delete skip_deleted_document_dict[id];
return context._signature_sub_storage.remove(id);
}
})
.push(function () {
return destination.put(id, doc);
})
.push(function () {
return context._signature_sub_storage.put(id, {
hash: hash,
from_local: from_local
});
});
}
return result
.push(undefined, function (error) {
if (error instanceof SkipError) {
return;
}
throw error;
});
}
function propagateDeletion(context, destination, id,
skip_deleted_document_dict, report, options) {
// Do not delete a document if it has an attachment
// ie, replication should prevent losing user data
// Synchronize attachments before, to ensure
// all of them will be deleted too
var result,
previous_report_length;
if (context._signature_hash_key !== undefined) {
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
result = destination.remove(id)
.push(function () {
return context._signature_sub_storage.remove(id);
});
} else {
previous_report_length = report._list.length;
result = repairDocumentAttachment(context, id, report)
.push(function () {
var next_report_length = report._list.length,
has_error = false,
i;
// Check if there was an error during attachment replication
for (i = previous_report_length; i < next_report_length; i += 1) {
if ((report._list[i][1] === id) &&
(report._list[i][0] < 100)) {
has_error = true;
}
}
if (!has_error) {
// Attachment repication has been correctly resolved
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
return destination.remove(id)
.push(function () {
return context._signature_sub_storage.remove(id);
});
}
report.log(id, options.from_local ? LOG_UNEXPECTED_REMOTE_ATTACHMENT :
LOG_UNEXPECTED_LOCAL_ATTACHMENT);
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return;
}
throw error;
});
}
return result
.push(function () {
// No need to sync attachment twice on this document
skip_deleted_document_dict[id] = null;
});
}
function checkAndPropagate(context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
status_hash, local_hash, doc,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
report,
options) {
// No need to check twice
skip_document_dict[id] = null;
var from_local = options.from_local;
return new RSVP.Queue()
.push(function () {
if (options.signature_hash_key !== undefined) {
return callAllDocsOnStorage(context, destination,
cache, destination_key)
.push(function (result) {
if (result.hasOwnProperty(id)) {
return [null, result[id][options.signature_hash_key]];
}
return [null, null];
});
}
return destination.get(id)
.push(function (remote_doc) {
return [remote_doc, generateHash(stringify(remote_doc))];
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return [null, null];
}
throw error;
});
})
.push(function (remote_list) {
var remote_doc = remote_list[0],
remote_hash = remote_list[1];
if (local_hash === remote_hash) {
// Same modifications on both side
report.log(id, LOG_FALSE_CONFLICT);
if (local_hash === null) {
// Deleted on both side, drop signature
return context._signature_sub_storage.remove(id);
}
return context._signature_sub_storage.put(id, {
hash: local_hash,
from_local: from_local
});
}
if ((remote_hash === status_hash) || (conflict_force === true)) {
// Modified only locally. No conflict or force
if (local_hash === null) {
// Deleted locally
return propagateDeletion(context, destination, id,
skip_deleted_document_dict,
report,
{from_local: from_local,
conflict: (remote_hash !== status_hash)
});
}
return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict,
skip_deleted_document_dict,
report,
{use_post: ((options.use_post) &&
(remote_hash === null)),
conflict: (remote_hash !== status_hash),
from_local: from_local,
create_new_document:
((remote_hash === null) &&
(status_hash !== null))
});
}
// Conflict cases
if (conflict_ignore === true) {
report.log(id, LOG_SKIP_CONFLICT);
return;
}
if ((conflict_revert === true) || (local_hash === null)) {
// Automatically resolve conflict or force revert
if (remote_hash === null) {
// Deleted remotely
return propagateDeletion(context, source, id,
skip_deleted_document_dict, report,
{from_local: !from_local,
conflict: (local_hash !== null)
});
}
return propagateModification(
context,
destination,
source,
remote_doc,
remote_hash,
id,
skip_document_dict,
skip_deleted_document_dict,
report,
{use_post: ((options.use_revert_post) &&
(local_hash === null)),
from_local: !from_local,
conflict: true,
create_new_document: ((local_hash === null) &&
(status_hash !== null))}
);
}
// Minimize conflict if it can be resolved
if (remote_hash === null) {
// Copy remote modification remotely
return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict,
skip_deleted_document_dict,
report,
{use_post: options.use_post,
conflict: true,
from_local: from_local,
create_new_document:
(status_hash !== null)});
}
report.log(id, LOG_UNRESOLVED_CONFLICT);
})
.push(undefined, function (error) {
report.log(id, LOG_UNEXPECTED_ERROR, error);
});
}
function checkLocalDeletion(queue, context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
destination, id, source,
conflict_force, conflict_revert,
conflict_ignore, report, options) {
var status_hash;
queue
.push(function () {
return context._signature_sub_storage.get(id);
})
.push(function (result) {
status_hash = result.hash;
return checkAndPropagate(context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
status_hash, null, null,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore, report,
options);
});
}
function checkSignatureDifference(queue, context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
local_hash, status_hash, report,
options) {
queue
.push(function () {
if (local_hash === null) {
// Hash was not provided by the allDocs query
return source.get(id);
}
return null;
})
.push(function (doc) {
if (local_hash === null) {
// Hash was not provided by the allDocs query
local_hash = generateHash(stringify(doc));
}
if (local_hash !== status_hash) {
return checkAndPropagate(context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
status_hash, local_hash, doc,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
report,
options);
}
if (!options.from_local) {
report.log(id, LOG_NO_CHANGE);
}
});
}
function pushStorage(context, skip_document_dict,
skip_deleted_document_dict,
cache, source_key, destination_key,
source, destination, signature_allDocs,
report, options) {
var argument_list = [],
argument_list_deletion = [];
if (!options.hasOwnProperty("use_post")) {
options.use_post = false;
}
if (!options.hasOwnProperty("use_revert_post")) {
options.use_revert_post = false;
}
return callAllDocsOnStorage(context, source, cache, source_key)
.push(function (source_allDocs) {
var i,
local_dict = {},
signature_dict = {},
is_modification,
is_creation,
status_hash,
local_hash,
key,
queue = new RSVP.Queue();
for (key in source_allDocs) {
if (source_allDocs.hasOwnProperty(key)) {
if (!skip_document_dict.hasOwnProperty(key)) {
local_dict[key] = source_allDocs[key];
}
}
}
/*
for (i = 0; i < source_allDocs.data.total_rows; i += 1) {
if (!skip_document_dict.hasOwnProperty(
source_allDocs.data.rows[i].id
)) {
local_dict[source_allDocs.data.rows[i].id] =
source_allDocs.data.rows[i].value;
}
}
*/
for (i = 0; i < signature_allDocs.data.total_rows; i += 1) {
if (!skip_document_dict.hasOwnProperty(
signature_allDocs.data.rows[i].id
)) {
signature_dict[signature_allDocs.data.rows[i].id] =
signature_allDocs.data.rows[i].value.hash;
}
}
for (key in local_dict) {
if (local_dict.hasOwnProperty(key)) {
is_modification = signature_dict.hasOwnProperty(key)
&& options.check_modification;
is_creation = !signature_dict.hasOwnProperty(key)
&& options.check_creation;
if (is_creation === true) {
status_hash = null;
} else if (is_modification === true) {
status_hash = signature_dict[key];
}
local_hash = null;
if (options.signature_hash_key !== undefined) {
local_hash = local_dict[key][options.signature_hash_key];
if (is_modification === true) {
// Bypass fetching all documents and calculating the sha
// Compare the select list values returned by allDocs calls
is_modification = false;
if (local_hash !== status_hash) {
is_modification = true;
}
}
}
if (is_modification === true || is_creation === true) {
argument_list.push([undefined, context, skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
source, destination,
key,
options.conflict_force,
options.conflict_revert,
options.conflict_ignore,
local_hash, status_hash,
report,
options]);
} else if (local_hash === status_hash) {
report.log(key, LOG_NO_CHANGE);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.log(key, options.from_local ?
LOG_SKIP_LOCAL_MODIFICATION :
LOG_SKIP_REMOTE_MODIFICATION);
} else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_CREATION :
LOG_SKIP_REMOTE_CREATION);
}
}
}
}
queue
.push(function () {
return dispatchQueue(
context,
checkSignatureDifference,
argument_list,
options.operation_amount
);
});
for (key in signature_dict) {
if (signature_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) {
if (options.check_deletion === true) {
argument_list_deletion.push([undefined,
context,
skip_document_dict,
skip_deleted_document_dict,
cache, destination_key,
destination, key,
source,
options.conflict_force,
options.conflict_revert,
options.conflict_ignore,
report,
options]);
} else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_DELETION :
LOG_SKIP_REMOTE_DELETION);
skip_deleted_document_dict[key] = null;
}
}
}
}
if (argument_list_deletion.length !== 0) {
queue.push(function () {
return dispatchQueue(
context,
checkLocalDeletion,
argument_list_deletion,
options.operation_amount
);
});
}
return queue;
});
}
function repairDocument(queue, context, id, report, signature_hash_key,
signature_hash, signature_attachment_hash,
signature_from_local) {
queue.push(function () {
return repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash,
signature_attachment_hash,
signature_from_local);
});
}
ReplicateStorage.prototype.repair = function () {
var context = this,
argument_list = arguments,
skip_document_dict = {},
skip_deleted_document_dict = {},
cache = {},
report = new ReplicateReport(this._log_level, this._log_console);
return new RSVP.Queue()
.push(function () {
// Ensure that the document storage is usable
if (context._custom_signature_sub_storage === false) {
// Do not sync the signature document
skip_document_dict[context._signature_hash] = null;
return context._signature_sub_storage.__storage._sub_storage
.__storage._sub_storage.get(
context._signature_hash
);
}
})
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return context._signature_sub_storage.__storage._sub_storage
.__storage._sub_storage.put(
context._signature_hash,
{}
);
}
throw error;
})
.push(function () {
return RSVP.all([
// Don't repair local_sub_storage twice
// context._signature_sub_storage.repair.apply(
// context._signature_sub_storage,
// argument_list
// ),
context._local_sub_storage.repair.apply(
context._local_sub_storage,
argument_list
),
context._remote_sub_storage.repair.apply(
context._remote_sub_storage,
argument_list
)
]);
})
.push(function () {
if (context._check_local_modification ||
context._check_local_creation ||
context._check_local_deletion ||
context._check_remote_modification ||
context._check_remote_creation ||
context._check_remote_deletion) {
return context._signature_sub_storage.allDocs({
select_list: ['hash']
});
}
})
.push(function (signature_allDocs) {
if (context._check_local_modification ||
context._check_local_creation ||
context._check_local_deletion) {
return pushStorage(context, skip_document_dict,
skip_deleted_document_dict,
cache, 'local', 'remote',
context._local_sub_storage,
context._remote_sub_storage,
signature_allDocs, report,
{
use_post: context._use_remote_post,
conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL),
conflict_revert: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE),
conflict_ignore: (context._conflict_handling ===
CONFLICT_CONTINUE),
check_modification: context._check_local_modification,
check_creation: context._check_local_creation,
check_deletion: context._check_local_deletion,
operation_amount: context._parallel_operation_amount,
signature_hash_key: context._signature_hash_key,
from_local: true
})
.push(function () {
return signature_allDocs;
});
}
return signature_allDocs;
})
.push(function (signature_allDocs) {
if (context._check_remote_modification ||
context._check_remote_creation ||
context._check_remote_deletion) {
return pushStorage(context, skip_document_dict,
skip_deleted_document_dict,
cache, 'remote', 'local',
context._remote_sub_storage,
context._local_sub_storage,
signature_allDocs,
report, {
use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE),
conflict_revert: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL),
conflict_ignore: (context._conflict_handling ===
CONFLICT_CONTINUE),
check_modification: context._check_remote_modification,
check_creation: context._check_remote_creation,
check_deletion: context._check_remote_deletion,
operation_amount: context._parallel_operation_amount,
signature_hash_key: context._signature_hash_key,
from_local: false
});
}
})
.push(function () {
if (context._check_local_attachment_modification ||
context._check_local_attachment_creation ||
context._check_local_attachment_deletion ||
context._check_remote_attachment_modification ||
context._check_remote_attachment_creation ||
context._check_remote_attachment_deletion) {
// Attachments are synchronized if and only if their parent document
// has been also marked as synchronized.
return context._signature_sub_storage.allDocs({
select_list: ['hash', 'attachment_hash', 'from_local']
})
.push(function (result) {
var i,
local_argument_list = [],
row,
len = result.data.total_rows;
for (i = 0; i < len; i += 1) {
row = result.data.rows[i];
// Do not synchronize attachment if one version of the document
// is deleted but not pushed to the other storage
if (!skip_deleted_document_dict.hasOwnProperty(row.id)) {
local_argument_list.push(
[undefined, context, row.id, report,
context._signature_hash_key,
row.value.hash, row.value.attachment_hash,
row.value.from_local, report]
);
}
}
return dispatchQueue(
context,
repairDocument,
local_argument_list,
context._parallel_operation_amount
);
});
}
})
.push(function () {
if (report.has_error) {
throw report;
}
return report;
});
};
jIO.addStorage('replicate', ReplicateStorage);
}(jIO, RSVP, Rusha, jIO.util.stringify));
jio-master-src/src/jio.storage/revisionstorage.js 0000664 0000000 0000000 00000105160 14230544473 0022522 0 ustar 00root root 0000000 0000000 /*
* Copyright 2012, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, hex_sha256, define */
/**
* JIO Revision Storage.
* It manages document version and can generate conflicts.
* Description:
* {
* "type": "revision",
* "sub_storage":
* }
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO, {hex_sha256: hex_sha256});
}(['jio', 'sha256'], function (jIO, sha256) {
"use strict";
var tool = {
"readBlobAsBinaryString": jIO.util.readBlobAsBinaryString,
"uniqueJSONStringify": jIO.util.uniqueJSONStringify
};
jIO.addStorage("revision", function (spec) {
var that = this, priv = {};
spec = spec || {};
// ATTRIBUTES //
priv.doc_tree_suffix = ".revision_tree.json";
priv.sub_storage = spec.sub_storage;
// METHODS //
/**
* Clones an object in deep (without functions)
* @method clone
* @param {any} object The object to clone
* @return {any} The cloned object
*/
priv.clone = function (object) {
var tmp = JSON.stringify(object);
if (tmp === undefined) {
return undefined;
}
return JSON.parse(tmp);
};
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv.generateUuid = function () {
var S4 = function () {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0' + string;
}
return string;
};
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
S4() + S4();
};
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
priv.hashCode = function (string) {
return sha256.hex_sha256(string);
};
/**
* Checks a revision format
* @method checkDocumentRevisionFormat
* @param {object} doc The document object
* @return {object} null if ok, else error object
*/
priv.checkDocumentRevisionFormat = function (doc) {
var send_error = function (message) {
return {
"status": 409,
"message": message,
"reason": "Wrong revision"
};
};
if (typeof doc._rev === "string") {
if (/^[0-9]+-[0-9a-zA-Z]+$/.test(doc._rev) === false) {
return send_error("The document revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$");
}
}
if (typeof doc._revs === "object") {
if (typeof doc._revs.start !== "number" ||
typeof doc._revs.ids !== "object" ||
typeof doc._revs.ids.length !== "number") {
return send_error(
"The document revision history is not well formated"
);
}
}
if (typeof doc._revs_info === "object") {
if (typeof doc._revs_info.length !== "number") {
return send_error("The document revision information " +
"is not well formated");
}
}
};
/**
* Creates a new document tree
* @method newDocTree
* @return {object} The new document tree
*/
priv.newDocTree = function () {
return {"children": []};
};
/**
* Convert revs_info to a simple revisions history
* @method revsInfoToHistory
* @param {array} revs_info The revs info
* @return {object} The revisions history
*/
priv.revsInfoToHistory = function (revs_info) {
var i, revisions = {
"start": 0,
"ids": []
};
revs_info = revs_info || [];
if (revs_info.length > 0) {
revisions.start = parseInt(revs_info[0].rev.split('-')[0], 10);
}
for (i = 0; i < revs_info.length; i += 1) {
revisions.ids.push(revs_info[i].rev.split('-')[1]);
}
return revisions;
};
/**
* Convert the revision history object to an array of revisions.
* @method revisionHistoryToList
* @param {object} revs The revision history
* @return {array} The revision array
*/
priv.revisionHistoryToList = function (revs) {
var i, start = revs.start, new_list = [];
for (i = 0; i < revs.ids.length; i += 1, start -= 1) {
new_list.push(start + "-" + revs.ids[i]);
}
return new_list;
};
/**
* Convert revision list to revs info.
* @method revisionListToRevsInfo
* @param {array} revision_list The revision list
* @param {object} doc_tree The document tree
* @return {array} The document revs info
*/
priv.revisionListToRevsInfo = function (revision_list, doc_tree) {
var revisionListToRevsInfoRec, revs_info = [], j;
for (j = 0; j < revision_list.length; j += 1) {
revs_info.push({"rev": revision_list[j], "status": "missing"});
}
revisionListToRevsInfoRec = function (index, doc_tree) {
var child, i;
if (index < 0) {
return;
}
for (i = 0; i < doc_tree.children.length; i += 1) {
child = doc_tree.children[i];
if (child.rev === revision_list[index]) {
revs_info[index].status = child.status;
revisionListToRevsInfoRec(index - 1, child);
}
}
};
revisionListToRevsInfoRec(revision_list.length - 1, doc_tree);
return revs_info;
};
/**
* Update a document metadata revision properties
* @method fillDocumentRevisionProperties
* @param {object} doc The document object
* @param {object} doc_tree The document tree
*/
priv.fillDocumentRevisionProperties = function (doc, doc_tree) {
if (doc._revs_info) {
doc._revs = priv.revsInfoToHistory(doc._revs_info);
} else if (doc._revs) {
doc._revs_info = priv.revisionListToRevsInfo(
priv.revisionHistoryToList(doc._revs),
doc_tree
);
} else if (doc._rev) {
doc._revs_info = priv.getRevisionInfo(doc._rev, doc_tree);
doc._revs = priv.revsInfoToHistory(doc._revs_info);
} else {
doc._revs_info = [];
doc._revs = {"start": 0, "ids": []};
}
if (doc._revs.start > 0) {
doc._rev = doc._revs.start + "-" + doc._revs.ids[0];
} else {
delete doc._rev;
}
};
/**
* Generates the next revision of a document.
* @methode generateNextRevision
* @param {object} doc The document metadata
* @param {boolean} deleted_flag The deleted flag
* @return {array} 0:The next revision number and 1:the hash code
*/
priv.generateNextRevision = function (doc, deleted_flag) {
var string, revision_history, revs_info;
doc = priv.clone(doc) || {};
revision_history = doc._revs;
revs_info = doc._revs_info;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
string = tool.uniqueJSONStringify(doc) +
tool.uniqueJSONStringify(revision_history) +
JSON.stringify(deleted_flag ? true : false);
revision_history.start += 1;
revision_history.ids.unshift(priv.hashCode(string));
doc._revs = revision_history;
doc._rev = revision_history.start + "-" + revision_history.ids[0];
revs_info.unshift({
"rev": doc._rev,
"status": deleted_flag ? "deleted" : "available"
});
doc._revs_info = revs_info;
return doc;
};
/**
* Gets the revs info from the document tree
* @method getRevisionInfo
* @param {string} revision The revision to search for
* @param {object} doc_tree The document tree
* @return {array} The revs info
*/
priv.getRevisionInfo = function (revision, doc_tree) {
var getRevisionInfoRec;
getRevisionInfoRec = function (doc_tree) {
var i, child, revs_info;
for (i = 0; i < doc_tree.children.length; i += 1) {
child = doc_tree.children[i];
if (child.rev === revision) {
return [{"rev": child.rev, "status": child.status}];
}
revs_info = getRevisionInfoRec(child);
if (revs_info.length > 0 || revision === undefined) {
revs_info.push({"rev": child.rev, "status": child.status});
return revs_info;
}
}
return [];
};
return getRevisionInfoRec(doc_tree);
};
priv.updateDocumentTree = function (doc, doc_tree) {
var revs_info, updateDocumentTreeRec;
doc = priv.clone(doc);
revs_info = doc._revs_info;
updateDocumentTreeRec = function (doc_tree, revs_info) {
var i, child, info;
if (revs_info.length === 0) {
return;
}
info = revs_info.pop();
for (i = 0; i < doc_tree.children.length; i += 1) {
child = doc_tree.children[i];
if (child.rev === info.rev) {
return updateDocumentTreeRec(child, revs_info);
}
}
doc_tree.children.unshift({
"rev": info.rev,
"status": info.status,
"children": []
});
updateDocumentTreeRec(doc_tree.children[0], revs_info);
};
updateDocumentTreeRec(doc_tree, priv.clone(revs_info));
};
priv.send = function (command, method, doc, option, callback) {
var storage = command.storage(priv.sub_storage);
function onSuccess(success) {
callback(undefined, success);
}
function onError(err) {
callback(err, undefined);
}
if (method === 'allDocs') {
storage.allDocs(option).then(onSuccess, onError);
} else {
storage[method](doc, option).then(onSuccess, onError);
}
};
priv.getWinnerRevsInfo = function (doc_tree) {
var revs_info = [], getWinnerRevsInfoRec;
getWinnerRevsInfoRec = function (doc_tree, tmp_revs_info) {
var i;
if (doc_tree.rev) {
tmp_revs_info.unshift({
"rev": doc_tree.rev,
"status": doc_tree.status
});
}
if (doc_tree.children.length === 0) {
if (revs_info.length === 0 ||
(revs_info[0].status !== "available" &&
tmp_revs_info[0].status === "available") ||
(tmp_revs_info[0].status === "available" &&
revs_info.length < tmp_revs_info.length)) {
revs_info = priv.clone(tmp_revs_info);
}
}
for (i = 0; i < doc_tree.children.length; i += 1) {
getWinnerRevsInfoRec(doc_tree.children[i], tmp_revs_info);
}
tmp_revs_info.shift();
};
getWinnerRevsInfoRec(doc_tree, []);
return revs_info;
};
priv.getConflicts = function (revision, doc_tree) {
var conflicts = [], getConflictsRec;
getConflictsRec = function (doc_tree) {
var i;
if (doc_tree.rev === revision) {
return;
}
if (doc_tree.children.length === 0) {
if (doc_tree.status !== "deleted") {
conflicts.push(doc_tree.rev);
}
}
for (i = 0; i < doc_tree.children.length; i += 1) {
getConflictsRec(doc_tree.children[i]);
}
};
getConflictsRec(doc_tree);
return conflicts.length === 0 ? undefined : conflicts;
};
priv.get = function (command, doc, option, callback) {
priv.send(command, "get", doc, option, callback);
};
priv.put = function (command, doc, option, callback) {
priv.send(command, "put", doc, option, callback);
};
priv.remove = function (command, doc, option, callback) {
priv.send(command, "remove", doc, option, callback);
};
priv.getAttachment = function (command, attachment, option, callback) {
priv.send(command, "getAttachment", attachment, option, callback);
};
priv.putAttachment = function (command, attachment, option, callback) {
priv.send(command, "putAttachment", attachment, option, callback);
};
priv.removeAttachment = function (command, attachment, option, callback) {
priv.send(command, "removeAttachment", attachment, option, callback);
};
priv.getDocument = function (command, doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + "." + doc._rev;
delete doc._attachment;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
priv.get(command, doc, option, callback);
};
priv.putDocument = function (command, doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + "." + doc._rev;
delete doc._attachment;
delete doc._data;
delete doc._mimetype;
delete doc._content_type;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
priv.put(command, doc, option, callback);
};
priv.getRevisionTree = function (command, doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + priv.doc_tree_suffix;
priv.get(command, doc, option, function (err, response) {
if (err) {
return callback(err, response);
}
if (response.data && response.data.children) {
response.data.children = JSON.parse(response.data.children);
}
return callback(err, response);
});
};
priv.getAttachmentList = function (command, doc, option, callback) {
var attachment_id, dealResults, state = "ok", result_list = [], count = 0;
dealResults = function (attachment_id, attachment_meta) {
return function (err, response) {
if (state !== "ok") {
return;
}
count -= 1;
if (err) {
if (err.status === 404) {
result_list.push(undefined);
} else {
state = "error";
return callback(err, undefined);
}
}
result_list.push({
"_attachment": attachment_id,
"_data": response.data,
"_content_type": attachment_meta.content_type
});
if (count === 0) {
state = "finished";
callback(undefined, {"data": result_list});
}
};
};
for (attachment_id in doc._attachments) {
if (doc._attachments.hasOwnProperty(attachment_id)) {
count += 1;
priv.getAttachment(
command,
{"_id": doc._id, "_attachment": attachment_id},
option,
dealResults(attachment_id, doc._attachments[attachment_id])
);
}
}
if (count === 0) {
callback(undefined, {"data": []});
}
};
priv.putAttachmentList = function (command, doc, option,
attachment_list, callback) {
var i, dealResults, state = "ok", count = 0, attachment;
attachment_list = attachment_list || [];
dealResults = function () {
return function (err) {
if (state !== "ok") {
return;
}
count -= 1;
if (err) {
state = "error";
return callback(err, undefined);
}
if (count === 0) {
state = "finished";
callback(undefined, {});
}
};
};
for (i = 0; i < attachment_list.length; i += 1) {
attachment = attachment_list[i];
if (attachment !== undefined) {
count += 1;
attachment._id = doc._id + "." + doc._rev;
priv.putAttachment(command, attachment, option, dealResults(i));
}
}
if (count === 0) {
return callback(undefined, {});
}
};
priv.putDocumentTree = function (command, doc, option, doc_tree, callback) {
doc_tree = priv.clone(doc_tree);
doc_tree._id = doc._id + priv.doc_tree_suffix;
if (doc_tree.children) {
doc_tree.children = JSON.stringify(doc_tree.children);
}
priv.put(command, doc_tree, option, callback);
};
priv.notFoundError = function (message, reason) {
return {
"status": 404,
"message": message,
"reason": reason
};
};
priv.conflictError = function (message, reason) {
return {
"status": 409,
"message": message,
"reason": reason
};
};
priv.revisionGenericRequest = function (command, doc, option,
specific_parameter, onEnd) {
var prev_doc, doc_tree, attachment_list, callback = {};
if (specific_parameter.doc_id) {
doc._id = specific_parameter.doc_id;
}
if (specific_parameter.attachment_id) {
doc._attachment = specific_parameter.attachment_id;
}
callback.begin = function () {
var check_error;
doc._id = doc._id || priv.generateUuid(); // XXX should not generate id
if (specific_parameter.revision_needed && !doc._rev) {
return onEnd(priv.conflictError(
"Document update conflict",
"No document revision was provided"
), undefined);
}
// check revision format
check_error = priv.checkDocumentRevisionFormat(doc);
if (check_error !== undefined) {
return onEnd(check_error, undefined);
}
priv.getRevisionTree(command, doc, option, callback.getRevisionTree);
};
callback.getRevisionTree = function (err, response) {
var winner_info, previous_revision, generate_new_revision;
previous_revision = doc._rev;
generate_new_revision = doc._revs || doc._revs_info ? false : true;
if (err) {
if (err.status !== 404) {
err.message = "Cannot get document revision tree";
return onEnd(err, undefined);
}
}
doc_tree = (response && response.data) || priv.newDocTree();
if (specific_parameter.get || specific_parameter.getAttachment) {
if (!doc._rev) {
winner_info = priv.getWinnerRevsInfo(doc_tree);
if (winner_info.length === 0) {
return onEnd(priv.notFoundError(
"Document not found",
"missing"
), undefined);
}
if (winner_info[0].status === "deleted") {
return onEnd(priv.notFoundError(
"Document not found",
"deleted"
), undefined);
}
doc._rev = winner_info[0].rev;
}
priv.fillDocumentRevisionProperties(doc, doc_tree);
return priv.getDocument(command, doc, option, callback.getDocument);
}
priv.fillDocumentRevisionProperties(doc, doc_tree);
if (generate_new_revision) {
if (previous_revision && doc._revs_info.length === 0) {
// the document history has changed, it means that the document
// revision was wrong. Add a pseudo history to the document
doc._rev = previous_revision;
doc._revs = {
"start": parseInt(previous_revision.split("-")[0], 10),
"ids": [previous_revision.split("-")[1]]
};
doc._revs_info = [{"rev": previous_revision, "status": "missing"}];
}
doc = priv.generateNextRevision(
doc,
specific_parameter.remove
);
}
if (doc._revs_info.length > 1) {
prev_doc = {
"_id": doc._id,
"_rev": doc._revs_info[1].rev
};
if (!generate_new_revision && specific_parameter.putAttachment) {
prev_doc._rev = doc._revs_info[0].rev;
}
}
// force revs_info status
doc._revs_info[0].status = (specific_parameter.remove ?
"deleted" : "available");
priv.updateDocumentTree(doc, doc_tree);
if (prev_doc) {
return priv.getDocument(command, prev_doc,
option, callback.getDocument);
}
if (specific_parameter.remove || specific_parameter.removeAttachment) {
return onEnd(priv.notFoundError(
"Unable to remove an inexistent document",
"missing"
), undefined);
}
priv.putDocument(command, doc, option, callback.putDocument);
};
callback.getDocument = function (err, res_doc) {
var k, conflicts;
if (err) {
if (err.status === 404) {
if (specific_parameter.remove ||
specific_parameter.removeAttachment) {
return onEnd(priv.conflictError(
"Document update conflict",
"Document is missing"
), undefined);
}
if (specific_parameter.get) {
return onEnd(priv.notFoundError(
"Unable to find the document",
"missing"
), undefined);
}
res_doc = {"data": {}};
} else {
err.message = "Cannot get document";
return onEnd(err, undefined);
}
}
res_doc = res_doc.data;
if (specific_parameter.get) {
res_doc._id = doc._id;
res_doc._rev = doc._rev;
if (option.conflicts === true) {
conflicts = priv.getConflicts(doc._rev, doc_tree);
if (conflicts) {
res_doc._conflicts = conflicts;
}
}
if (option.revs === true) {
res_doc._revisions = doc._revs;
}
if (option.revs_info === true) {
res_doc._revs_info = doc._revs_info;
}
return onEnd(undefined, {"data": res_doc});
}
if (specific_parameter.putAttachment ||
specific_parameter.removeAttachment) {
// copy metadata (not beginning by "_" to document
for (k in res_doc) {
if (res_doc.hasOwnProperty(k) && !k.match("^_")) {
doc[k] = res_doc[k];
}
}
}
if (specific_parameter.remove) {
priv.putDocumentTree(command, doc, option,
doc_tree, callback.putDocumentTree);
} else {
priv.getAttachmentList(command, res_doc, option,
callback.getAttachmentList);
}
};
callback.getAttachmentList = function (err, res_list) {
var i, attachment_found = false;
if (err) {
err.message = "Cannot get attachment";
return onEnd(err, undefined);
}
res_list = res_list.data;
attachment_list = res_list || [];
if (specific_parameter.getAttachment) {
// getting specific attachment
for (i = 0; i < attachment_list.length; i += 1) {
if (attachment_list[i] &&
doc._attachment ===
attachment_list[i]._attachment) {
return onEnd(undefined, {"data": attachment_list[i]._data});
}
}
return onEnd(priv.notFoundError(
"Unable to get an inexistent attachment",
"missing"
), undefined);
}
if (specific_parameter.remove_from_attachment_list) {
// removing specific attachment
for (i = 0; i < attachment_list.length; i += 1) {
if (attachment_list[i] &&
specific_parameter.remove_from_attachment_list._attachment ===
attachment_list[i]._attachment) {
attachment_found = true;
attachment_list[i] = undefined;
break;
}
}
if (!attachment_found) {
return onEnd(priv.notFoundError(
"Unable to remove an inexistent attachment",
"missing"
), undefined);
}
}
priv.putDocument(command, doc, option, callback.putDocument);
};
callback.putDocument = function (err) {
var i, attachment_found = false;
if (err) {
err.message = "Cannot post the document";
return onEnd(err, undefined);
}
if (specific_parameter.add_to_attachment_list) {
// adding specific attachment
attachment_list = attachment_list || [];
for (i = 0; i < attachment_list.length; i += 1) {
if (attachment_list[i] &&
specific_parameter.add_to_attachment_list._attachment ===
attachment_list[i]._attachment) {
attachment_found = true;
attachment_list[i] = specific_parameter.add_to_attachment_list;
break;
}
}
if (!attachment_found) {
attachment_list.unshift(specific_parameter.add_to_attachment_list);
}
}
priv.putAttachmentList(
command,
doc,
option,
attachment_list,
callback.putAttachmentList
);
};
callback.putAttachmentList = function (err) {
if (err) {
err.message = "Cannot copy attacments to the document";
return onEnd(err, undefined);
}
priv.putDocumentTree(command, doc, option,
doc_tree, callback.putDocumentTree);
};
callback.putDocumentTree = function (err) {
var response_object;
if (err) {
err.message = "Cannot update the document history";
return onEnd(err, undefined);
}
response_object = {
"id": doc._id,
"rev": doc._rev
};
if (specific_parameter.putAttachment ||
specific_parameter.removeAttachment ||
specific_parameter.getAttachment) {
response_object.attachment = doc._attachment;
}
onEnd(undefined, response_object);
// if (option.keep_revision_history !== true) {
// // priv.remove(command, prev_doc, option, function () {
// // - change "available" status to "deleted"
// // - remove attachments
// // - done, no callback
// // });
// }
};
callback.begin();
};
/**
* Post the document metadata and create or update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method post
* @param {object} command The JIO command
*/
that.post = function (command, metadata, option) {
priv.revisionGenericRequest(
command,
metadata,
option,
{},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"id": response.id, "rev": response.rev});
}
);
};
/**
* Put the document metadata and create or update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method put
* @param {object} command The JIO command
*/
that.put = function (command, metadata, option) {
priv.revisionGenericRequest(
command,
metadata,
option,
{},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"rev": response.rev});
}
);
};
that.putAttachment = function (command, param, option) {
tool.readBlobAsBinaryString(param._blob).then(function (event) {
param._content_type = param._blob.type;
param._data = event.target.result;
delete param._blob;
priv.revisionGenericRequest(
command,
param,
option,
{
"doc_id": param._id,
"attachment_id": param._attachment,
"add_to_attachment_list": {
"_attachment": param._attachment,
"_content_type": param._content_type,
"_data": param._data
},
"putAttachment": true
},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"rev": response.rev});
}
);
}, function () {
command.error("conflict", "broken blob", "Cannot read data to put");
});
};
that.remove = function (command, param, option) {
priv.revisionGenericRequest(
command,
param,
option,
{
"revision_needed": true,
"remove": true
},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"rev": response.rev});
}
);
};
that.removeAttachment = function (command, param, option) {
priv.revisionGenericRequest(
command,
param,
option,
{
"doc_id": param._id,
"attachment_id": param._attachment,
"revision_needed": true,
"removeAttachment": true,
"remove_from_attachment_list": {
"_attachment": param._attachment
}
},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"rev": response.rev});
}
);
};
that.get = function (command, param, option) {
priv.revisionGenericRequest(
command,
param,
option,
{
"get": true
},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"data": response.data});
}
);
};
that.getAttachment = function (command, param, option) {
priv.revisionGenericRequest(
command,
param,
option,
{
"doc_id": param._id,
"attachment_id": param._attachment,
"getAttachment": true
},
function (err, response) {
if (err) {
return command.error(err);
}
command.success({"data": response.data});
}
);
};
that.allDocs = function (command, param, option) {
/*jslint unparam: true */
var rows, result = {"total_rows": 0, "rows": []}, functions = {};
functions.finished = 0;
functions.falseResponseGenerator = function (response, callback) {
callback(undefined, response);
};
functions.fillResultGenerator = function (doc_id) {
return function (err, doc_tree) {
var document_revision, row, revs_info;
if (err) {
return command.error(err);
}
doc_tree = doc_tree.data;
if (typeof doc_tree.children === 'string') {
doc_tree.children = JSON.parse(doc_tree.children);
}
revs_info = priv.getWinnerRevsInfo(doc_tree);
document_revision =
rows.document_revisions[doc_id + "." + revs_info[0].rev];
if (document_revision) {
row = {
"id": doc_id,
"key": doc_id,
"value": {
"rev": revs_info[0].rev
}
};
if (document_revision.doc && option.include_docs) {
document_revision.doc._id = doc_id;
document_revision.doc._rev = revs_info[0].rev;
row.doc = document_revision.doc;
}
result.rows.push(row);
result.total_rows += 1;
}
functions.success();
};
};
functions.success = function () {
functions.finished -= 1;
if (functions.finished === 0) {
command.success({"data": result});
}
};
priv.send(command, "allDocs", null, option, function (err, response) {
var i, row, selector, selected;
if (err) {
return command.error(err);
}
response = response.data;
selector = /\.revision_tree\.json$/;
rows = {
"revision_trees": {
// id.revision_tree.json: {
// id: blabla
// doc: {...}
// }
},
"document_revisions": {
// id.rev: {
// id: blabla
// rev: 1-1
// doc: {...}
// }
}
};
while (response.rows.length > 0) {
// filling rows
row = response.rows.shift();
selected = selector.exec(row.id);
if (selected) {
selected = selected.input.substring(0, selected.index);
// this is a revision tree
rows.revision_trees[row.id] = {
"id": selected
};
if (row.doc) {
rows.revision_trees[row.id].doc = row.doc;
}
} else {
// this is a simple revision
rows.document_revisions[row.id] = {
"id": row.id.split(".").slice(0, -1),
"rev": row.id.split(".").slice(-1)
};
if (row.doc) {
rows.document_revisions[row.id].doc = row.doc;
}
}
}
functions.finished += 1;
for (i in rows.revision_trees) {
if (rows.revision_trees.hasOwnProperty(i)) {
functions.finished += 1;
if (rows.revision_trees[i].doc) {
functions.falseResponseGenerator(
{"data": rows.revision_trees[i].doc},
functions.fillResultGenerator(rows.revision_trees[i].id)
);
} else {
priv.getRevisionTree(
command,
{"_id": rows.revision_trees[i].id},
option,
functions.fillResultGenerator(rows.revision_trees[i].id)
);
}
}
}
functions.success();
});
};
// XXX
that.check = function (command) {
command.success();
};
// XXX
that.repair = function (command) {
command.success();
};
}); // end RevisionStorage
}));
jio-master-src/src/jio.storage/s3storage.js 0000664 0000000 0000000 00000071371 14230544473 0021217 0 ustar 00root root 0000000 0000000 /*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, btoa, b64_hmac_sha1, jQuery, XMLHttpRequest, XHRwrapper,
FormData*/
/**
* JIO S3 Storage. Type = "s3".
* Amazon S3 "database" storage.
*/
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO, jQuery, {b64_hmac_sha1: b64_hmac_sha1});
}(['jio', 'jquery', 'sha1'], function (jIO, $, sha1) {
"use strict";
var b64_hmac_sha1 = sha1.b64_hmac_sha1;
jIO.addStorage("s3", function (spec) {
var that, priv = {}, lastDigest, isDelete;
that = this;
//nomenclature param
// param._id,
// ._attachment,
// ._blob
// attributes
priv.username = spec.username || '';
priv.AWSIdentifier = spec.AWSIdentifier || '';
priv.password = spec.password || '';
priv.server = spec.server || '';
/*||> "private,
public-read,
public-read-write,
authenticated-read,
bucket-owner-read,
bucket-owner-full-control" <||*/
priv.acl = spec.acl || '';
priv.actionStatus = spec.actionStatus || '';
priv.contenTType = spec.contenTType || '';
/**
* Update [doc] the document object and remove [doc] keys
* which are not in [new_doc]. It only changes [doc] keys not starting
* with an underscore.
* ex: doc: {key:value1,_key:value2} with
* new_doc: {key:value3,_key:value4} updates
* doc: {key:value3,_key:value2}.
* @param {object} doc The original document object.
* @param {object} new_doc The new document object
**/
priv.secureDocId = function (string) {
var split = string.split('/'), i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i += 1) {
if (split[i] === '') {
return '';
}
}
return split.join('%2F');
};
priv.fileNameToIds = function (resourcename) {
var split, el, id = "", attmt = "", last;
split = resourcename.split('.');
function replaceAndNotLast() {
last = false;
return '.';
}
/*jslint ass: true */
while ((el = split.shift()) !== undefined) {
last = true;
el = el.replace(/__/g, '%2595');
el = el.replace(/_$/, replaceAndNotLast);
id += el.replace(/%2595/g, '_');
if (last) {
break;
}
}
attmt = split.join('.');
return [id, attmt];
};
priv.idsToFileName = function (document_id, attachment_id) {
document_id = encodeURI(document_id).
replace(/\//g, "%2F").
replace(/\?/g, "%3F");
document_id = encodeURI(document_id).
replace(/_/g, "__").
replace(/\./g, "_.");
if (attachment_id) {
attachment_id = encodeURI(attachment_id).
replace(/\//g, "%2F").
replace(/\?/g, "%3F");
return document_id + "." + attachment_id;
}
return document_id;
};
/**
* Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c"
* @method removeSlashIfLast
* @param {string} string The string to modify
* @return {string} The modified string
*/
priv.removeSlashIfLast = function (string) {
if (string[string.length - 1] === "/") {
return string.slice(0, -1);
}
return string;
};
/**
* Generate a new uuid
*
* @method generateUuid
* @private
* @return {String} The new uuid
*/
function generateUuid() {
function S4() {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0' + string;
}
return string;
}
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
S4() + S4();
}
that.documentObjectUpdate = function (doc, new_doc) {
var k;
for (k in doc) {
if (doc.hasOwnProperty(k)) {
if (k[0] !== '_') {
delete doc[k];
}
}
}
for (k in new_doc) {
if (new_doc.hasOwnProperty(k)) {
if (k[0] !== '_') {
doc[k] = new_doc[k];
}
}
}
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
that.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
};
// ===================== overrides ======================
that.specToStore = function () {
return {
"username": priv.username,
"password": priv.password,
"server": priv.server,
"acl": priv.acl
};
};
that.validateState = function () {
// xxx complete error message
// jjj completion below
if (typeof priv.AWSIdentifier === "string" && priv.AWSIdentifier === '') {
return 'Need at least one parameter "Aws login".';
}
if (typeof priv.password === "string" && priv.password === '') {
return 'Need at least one parameter "password".';
}
if (typeof priv.server === "string" && priv.server === '') {
return 'Need at least one parameter "server".';
}
return '';
};
// =================== S3 Specifics =================
/**
* Encoding the signature using a stringToSign
* Encoding the policy
* @method buildStringToSign
* @param {string} http_verb The HTTP method
* @param {string} content_md5 The md5 content
* @param {string} content_type The content type
* @param {number} expires The expires time
* @param {string} x_amz_headers The specific amazon headers
* @param {string} path_key The path of the document
* @return {string} The generated signature
*/
// xxx no need to make it public, use private -> "priv" (not "that")
priv.buildStringToSign = function (http_verb, content_md5, content_type,
expires, x_amz_headers, path_key) {
//example :
// var StringToSign = S3.buildStringToSign(S3.httpVerb,'','','',
// 'x-amz-date:'+S3.requestUTC,'/jio1st/prive.json');
var StringToSign = http_verb + '\n'
+ content_md5 + '\n'//content-md5
+ content_type + '\n'//content-type
+ expires + '\n'//expires
+ x_amz_headers + '\n'//x-amz headers
+ path_key;//path key
return StringToSign;
};
that.encodePolicy = function () {
//generates the policy
//enables the choice for the http response code
var s3_policy;
s3_policy = {
"expiration": "2020-01-01T00:00:00Z",
"conditions": [
{"bucket": priv.server },
["starts-with", "$key", ""],
{"acl": priv.acl },
["starts-with", "$Content-Type", ""]
]
};
//base64 encoding of the policy (native base64 js >>
// .btoa() = encode, .atob() = decode)
priv.b64_policy = btoa(JSON.stringify(s3_policy));
//generates the signature value using the policy and the secret access key
//use of sha1.js to generate the signature
//Signature = that.signature(priv.b64_policy);
};
that.signature = function (string) {
var Signature = b64_hmac_sha1(priv.password, string);
return Signature;
};
function xhr_onreadystatechange(docId,
command,
obj,
http,
jio,
isAttachment,
callback) {
obj.onreadystatechange = function () {
var response, err = '';
if (obj.readyState === 4) {
if (this.status === 204 || this.status === 201 ||
this.status === 200) {
switch (http) {
case "POST":
command.success(this.status, {id: docId});
break;
case 'PUT':
if (jio === true) {
command.success(this.status);
} else {
callback(this.responseText);
}
break;
case 'GET':
if (jio === true) {
if (isAttachment === true) {
//méthode that.getAttachment
response = this.response;
command.success(
this.status,
{'data': response, 'digest': lastDigest}
);
} else {
// this is not an attachment
// that.get method
response = JSON.parse(this.responseText);
command.success(this.status, {'data': response});
}
} else {
callback(this.responseText);
}
break;
case 'DELETE':
if (jio === true) {
if (isAttachment === false) {
command.success(this.status);
} else {
command.success(this.status);
}
} else {
callback(this.responseText);
}
break;
}
} else {
err = this;
if (this.status === 405) {
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err.error = "not_allowed";
command.error(err);
}
if (this.status === 404) {
if (http === 'GET') {
if (jio === true) {
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
return command.error(
404,
"Not Found",
"File does not exist"
);
}
//not jio
if (isDelete === true) {
isDelete = false;
return command.error(
404,
"Not Found",
"File does not exist"
);
}
callback('404');
} else {
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
return command.error(
404,
"Not Found",
"File does not exist"
);
}
//fin 404
}
if (this.status === 409) {
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
return command.error(
409,
"Already Exists",
"File does exist"
);
}
}
}
};
}
priv.updateMeta = function (doc, docid, attachid, action, data) {
/*jslint unparam: true */
doc._attachments = doc._attachments || {};
switch (action) {
case "add":
doc._attachments[attachid] = data;
//nothing happens
doc = JSON.stringify(doc);
break;
case "remove":
if (doc._attachments !== undefined) {
delete doc._attachments[attachid];
}
doc = JSON.stringify(doc);
break;
case "update":
doc._attachments[attachid] = data;
//update happened in the put request
doc = JSON.stringify(doc);
break;
}
return doc;
};
that.encodeAuthorization = function (key) {
//GET oriented method
var requestUTC, httpVerb, StringToSign, Signature;
requestUTC = new Date().toUTCString();
httpVerb = "GET";
StringToSign = priv.buildStringToSign(
httpVerb,
'',
'application/json',
'',
'x-amz-date:' + requestUTC,
'/' + priv.server + '/' + key
);
Signature = b64_hmac_sha1(priv.password, StringToSign);
return Signature;
};
that.XHRwrapper = function (command,
docId,
attachId,
http,
mime,
data,
jio,
is_attachment,
callback) {
var docFile, requestUTC, StringToSign, url, Signature, xhr;
if (command.method === "alldocs") {
docFile = '';
} else {
docFile = priv.idsToFileName(docId,
attachId || undefined);
}
requestUTC = new Date().toUTCString();
StringToSign = priv.buildStringToSign(
http,
'',
mime,
'',
'x-amz-date:' + requestUTC,
'/' + priv.server + '/' + docFile
);
url = 'http://s3.amazonaws.com/' + priv.server + '/' + docFile;
Signature = b64_hmac_sha1(priv.password, StringToSign);
xhr = new XMLHttpRequest();
xhr.open(http, url, true);
xhr.setRequestHeader("HTTP-status-code", "100");
xhr.setRequestHeader("x-amz-date", requestUTC);
xhr.setRequestHeader("Authorization", "AWS "
+ priv.AWSIdentifier
+ ":"
+ Signature);
xhr.setRequestHeader("Content-Type", mime);
if (http === 'GET' && jio === true && is_attachment === true) {
xhr.responseType = 'blob';
} else {
//défaut
xhr.responseType = 'text';
}
xhr_onreadystatechange(docId,
command,
xhr,
http,
jio,
is_attachment,
callback);
if (http === 'PUT') {
xhr.send(data);
} else {
xhr.send(null);
}
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
**/
that.post = function (command, metadata) {
//as S3 encoding key are directly inserted within the FormData(),
//use of XHRwrapper function ain't pertinent
var doc, doc_id, mime;
doc = metadata;
//doc_id = (!doc._id) ? generateUuid() : doc._id;
doc._id = doc._id || generateUuid();
doc_id = doc._id;
function postDocument() {
var fd, Signature, xhr;
doc_id = priv.idsToFileName(doc_id);
//Meant to deep-serialize in order to avoid
//conflicts due to the multipart enctype
doc = JSON.stringify(doc);
fd = new FormData();
//virtually builds the form fields
//filename
fd.append('key', doc_id);
//file access authorizations
priv.acl = "";
fd.append('acl', priv.acl);
//content-type
priv.contenTType = "text/plain";
fd.append('Content-Type', priv.contenTType);
//allows specification of a success url redirection
//fd.append('success_action_redirect', '');
//allows to specify the http code response if the request is successful
//fd.append('success_action_status', http_response);
//login AWS
fd.append('AWSAccessKeyId', priv.AWSIdentifier);
//exchange policy with the amazon s3 service
//can be common to all uploads or specific
//that.encodePolicy(fd);
that.encodePolicy(fd);
fd.append('policy', priv.b64_policy);
//signature through the base64.hmac.sha1(secret key, policy) method
Signature = b64_hmac_sha1(priv.password, priv.b64_policy);
fd.append('signature', Signature);
//uploaded content !!may must be a string rather than an object
fd.append('file', doc);
xhr = new XMLHttpRequest();
xhr_onreadystatechange(doc_id, command, xhr, 'POST', true, false, '');
xhr.open('POST', 'https://' + priv.server + '.s3.amazonaws.com/', true);
xhr.send(fd);
}
if (doc_id === '' || doc_id === undefined) {
doc._id = generateUuid();
}
mime = 'text/plain; charset=UTF-8';
that.XHRwrapper(command, doc_id, '', 'GET', mime, '', false, false,
function (response) {
if (response === '404') {
postDocument();
} else {
//si ce n'est pas une 404,
//alors on renvoit une erreur 405
return command.error(
409,
"Document already exists",
"Cannot create document"
);
}
});
};
/**
* Get a document or attachment
* @method get
* @param {object} command The JIO command
**/
that.get = function (command, metadata) {
var docId, isJIO, mime;
docId = metadata._id;
isJIO = true;
mime = 'text/plain; charset=UTF-8';
that.XHRwrapper(command, docId, '', 'GET', mime, '', isJIO, false);
};
that.getAttachment = function (command, param) {
var docId, attachId, isJIO, mime;
function getTheAttachment() {
docId = param._id;
attachId = param._attachment;
isJIO = true;
mime = 'text/plain; charset=UTF-8';
that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, true);
}
function getDoc() {
docId = param._id;
isJIO = false;
mime = 'text/plain; charset=UTF-8';
that.XHRwrapper(command, docId, '', 'GET', mime, '', isJIO, false,
function (response) {
var responseObj = JSON.parse(response)._attachments;
if (responseObj !== undefined) {
if (responseObj[param._attachment] !== undefined) {
lastDigest = JSON.parse(response).
_attachments[param._attachment].digest;
}
}
getTheAttachment();
});
}
getDoc();
//docId = param._id;
//attachId = param._attachment;
//isJIO = true;
//mime = 'text/plain; charset=UTF-8';
//that.XHRwrapper(command, docId, attachId, 'GET', mime,
// '', isJIO, true);
};
/**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
**/
that.put = function (command, metadata) {
var doc, docId, mime;
doc = metadata;
docId = doc._id;
mime = 'text/plain; charset=UTF-8';
//pas d'attachment dans un put simple
function putDocument() {
var attachId, data, isJIO;
attachId = '';
data = JSON.stringify(doc);
isJIO = true;
that.XHRwrapper(command,
docId,
attachId,
'PUT',
mime,
data,
isJIO,
false);
}
that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false,
function (response) {
var responseObj = JSON.parse(response);
if (responseObj._attachments !== undefined) {
doc._attachments = responseObj._attachments;
}
putDocument();
// XXX control non existing document to throw a 201 http code
});
};
that.putAttachment = function (command, param) {
var my_document,
docId,
attachId,
mime,
attachment_data,
attachment_digest,
attachment_mimetype,
attachment_length;
my_document = null;
docId = param._id;
attachId = param._attachment;
mime = param._blob.type;
attachment_data = param._blob;
jIO.util.readBlobAsBinaryString(param._blob).then(function (e) {
var binary_string = e.target.result;
attachment_digest = jIO.util.makeBinaryStringDigest(binary_string);
function putAttachment() {
that.XHRwrapper(
command,
docId,
attachId,
'PUT',
mime,
attachment_data,
false,
true,
function () {
command.success({
// response
"digest": attachment_digest
});
}
);
}
function putDocument() {
var attachment_obj, data, doc;
attachment_mimetype = param._blob.type;
attachment_length = param._blob.size;
attachment_obj = {
//"revpos": 3, // optional
"digest": attachment_digest,
"content_type": attachment_mimetype,
"length": attachment_length
};
data = JSON.parse(my_document);
doc = priv.updateMeta(data, docId, attachId, "add", attachment_obj);
that.XHRwrapper(command, docId, '', 'PUT', mime, doc, false, false,
function () {
putAttachment();
});
}
function getDocument() {
that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false,
function (response) {
if (response === '404') {
return command.error(
404,
"Document does not exist",
"Cannot find document"
);
}
my_document = response;
putDocument();
});
}
getDocument();
});
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command, param) {
var docId, mime;
docId = param._id;
mime = 'text/plain; charset=UTF-8';
isDelete = true;
function deleteDocument() {
isDelete = false;
that.XHRwrapper(command, docId, '', 'DELETE', mime, '', true, false,
function () {
command.success({
// response
"ok": true,
"id": docId
//"rev": current_revision
});
}
);
}
function myCallback() {
return;
}
that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false,
function (response) {
var attachKeys, keys;
attachKeys = (JSON.parse(response))._attachments;
for (keys in attachKeys) {
if (attachKeys.hasOwnProperty(keys)) {
that.XHRwrapper(command,
docId,
keys,
'DELETE',
mime,
'',
false,
false,
myCallback
);
}
}
deleteDocument();
}
);
};
that.removeAttachment = function (command, param) {
var my_document,
docId,
attachId,
mime;
my_document = null;
docId = param._id;
attachId = param._attachment;
mime = 'text/plain; charset=UTF-8';
//récupération des variables de l'attachement
//attachment_id = command.getAttachmentId();
//attachment_data = command.getAttachmentData();
//attachment_md5 = command.md5SumAttachmentData();
//attachment_mimetype = command.getAttachmentMimeType();
//attachment_length = command.getAttachmentLength();
function removeAttachment() {
that.XHRwrapper(command, docId, attachId, 'DELETE', mime, '', true,
true, function () {
return;
});
}
function putDocument() {
var data, doc;
//data = JSON.parse(my_document);
data = my_document;
doc = priv.updateMeta(data, docId, attachId, "remove", '');
that.XHRwrapper(command, docId, '', 'PUT', mime, doc,
false, false, function () {
removeAttachment();
});
}
function getDocument() {
that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false,
function (response) {
if (response === '404') {
return command.error(
404,
"missing document",
"This Document does not exist"
);
}
my_document = JSON.parse(response);
if (my_document._attachments === undefined) {
return command.error(
404,
"missing attachment",
"This Document has no attachments"
);
}
if (my_document._attachments[attachId] !== undefined) {
putDocument();
} else {
return command.error(
404,
"missing attachment",
"This Attachment does not exist"
);
}
});
}
getDocument();
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
**/
that.allDocs = function (command, param, options) {
/*jslint unparam: true */
var _succ, my_document, mime;
_succ = command.success;
command.success = function () {
_succ.apply(this, arguments);
};
my_document = null;
mime = 'text/plain; charset=UTF-8';
function makeJSON() {
var keys,
resultTable,
counter,
allDocResponse,
count,
countB,
dealCallback,
errCallback,
i,
keyId,
Signature,
callURL,
requestUTC;
keys = $($.parseXML(my_document)).find('Key');
if (keys.length === 0) {
return command.success({"data":
{
"total_rows": 0,
"rows": []
}
});
}
resultTable = [];
counter = 0;
keys.each(function () {
var $this, filename, docId;
$this = $(this);
filename = $this.context.textContent;
docId = priv.idsToFileName(priv.fileNameToIds(filename)[0]);
if (counter === 0) {
counter += 1;
resultTable.push(docId);
} else if (docId !== resultTable[counter - 1]) {
counter += 1;
resultTable.push(docId);
}
});
allDocResponse = {
// document content will be added to response
"total_rows": resultTable.length,
"rows": []
};
//needed to save the index within the $.ajax.success() callback
count = resultTable.length - 1;
countB = 0;
dealCallback = function (i, countB, allDoc) {
/*jslint unparam: true */
return function (doc, statustext, response) {
allDoc.rows[i].doc = JSON.parse(response.responseText);
if (count === 0) {
return command.success({
"data": allDoc
});
}
count -= 1;
};
};
errCallback = function (jQxhr) {
command.error(
jQxhr.status,
jQxhr.statusText,
"S3 Alldocs failed."
);
};
i = resultTable.length - 1;
if (options.include_docs) {
for (i; i >= 0; i -= 1) {
keyId = resultTable[i];
Signature = that.encodeAuthorization(keyId);
callURL = 'http://' + priv.server + '.s3.amazonaws.com/' + keyId;
requestUTC = new Date().toUTCString();
allDocResponse.rows[i] = {
"id": priv.fileNameToIds(keyId)[0],
"value": {}
};
$.ajax({
contentType : '',
crossdomain : true,
url : callURL,
type : 'GET',
headers : {
'Authorization' : "AWS"
+ " "
+ priv.AWSIdentifier
+ ":"
+ Signature,
'x-amz-date' : requestUTC,
'Content-Type' : 'application/json'
//'Content-MD5' : ''
//'Content-Length' : ,
//'Expect' : ,
//'x-amz-security-token' : ,
},
success : dealCallback(i, countB, allDocResponse),
error : errCallback
});
countB += 1;
}
} else {
for (i; i >= 0; i -= 1) {
keyId = resultTable[i];
allDocResponse.rows[i] = {
"id": priv.fileNameToIds(keyId)[0],
"value": {}
};
}
allDocResponse = {"data": allDocResponse};
command.success(allDocResponse);
}
}
function getXML() {
command.method = 'alldocs';
that.XHRwrapper(command, '', '', 'GET', mime, '', false, false,
function (response) {
my_document = response;
makeJSON();
}
);
}
getXML();
//fin alldocs
};
});
}));
jio-master-src/src/jio.storage/searchableencryptionstorage.js 0000664 0000000 0000000 00000042331 14230544473 0025070 0 ustar 00root root 0000000 0000000 /***********************************************************************
** Written by Abdullatif Shikfa, Alcatel Lucent Bell-Labs France **
** With the invaluable help of Tristan Cavelier, Nexedi **
** 31/01/2014 **
***********************************************************************/
/*jslint indent:2,maxlen:80,nomen:true*/
/*global jIO, define, exports, require, sjcl*/
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(
require('jio'),
require('sjcl')
);
}
module(jIO, sjcl);
}([
'jio',
'sjcl'
], function (jIO, sjcl) {
"use strict";
/**
* SearchableEncryptionStorage is a function that creates
* the searchable encryption storage
*
*keywords: a list of keywords associated with the current document, and which
* can be searched with the searchable encryption algorithm.
*nbMaxKeywords: the maximum number of keywords that any document can contain.
*encryptedIndex: an index that represents the keywords in an encrypted form.
* It is a Bloom Filter, which allows queries, without false
* negatives but with a false positive probability.
*errorRate: the false positive rate is bound to 2 to the power -errorRate.
* Increasing the value of errorRate decreases the rate of false
* positives but increases the size of the encryptedIndex.
*password: a user chosen password used to encrypt metadata as well as to
* generate the encryptedIndex.
*/
function SearchableEncryptionStorage(storage_description) {
this._password = storage_description.password; //string
this._errorRate = storage_description.errorRate || 20; //int
this._nbMaxKeywords = storage_description.nbMaxKeywords || 100; //int
this._url = storage_description.url;
this._keywords = storage_description.keywords; //string array
if (typeof this._password !== 'string') {
throw new TypeError("'password' description property is not a string");
}
}
/**
* computeBFLength is a function that computes the length of a Bloom Filter
* depending on the false positive ratio and the maximum number of elements
* (keywords) that it will represent.
* A Bloom Filter is indeed a data structure which is a bit array that
* represents a set of elements.
* The false positive rate is bound to 2 to the power -errorRate, and the
* length is then computed according to the following formula.
*/
function computeBFLength(errorRate, nbMaxKeywords) {
return Math.ceil((errorRate * nbMaxKeywords) / Math.log(2));
}
/**
* intArrayToString is a helper function that converts an array of integers to
* one big string. It basically concatenates all the integers of the array.
*/
/*function intArrayToString(arr) {
var i, result = "";
for (i = 0; i < arr.length; i += 1) {
result = result + arr[i].toString();
}
return result;
}*/
/**
* bigModulo is a helper function that computes the remainder of a large
* integer divided by an operand. The large integer is represented as several
* regular integers (of 32 bits) in big endian in an array.
* The function leverages the modulo operation on integers implemented in
* javascript to perform the modulo on the large integer : it computes the
* modulo of each integer of the array multiplied by the modulo of the
* base (2 to the power 32) to the power of the position in the array.
* However, since javascript encodes integers on 32 bits we have to add another
* trick: we do the computations on half words and we use the function
* sjcl.bitArray.bitSlice which extracts some bits out of a bit array, and we
* and we thus mutliply by half of the base.
*/
function bigModulo(arr, mod) {
var i, result = 0, base = 1, maxIter = (2 * arr.length);
for (i = 0; i < maxIter; i += 1) {
result = result + (
(sjcl.bitArray.bitSlice(arr, i * 16, (i + 1) * 16)[0]) % mod
) * base;
base = (base * Math.pow(2, 16)) % mod;
}
result = result % mod;
return result;
}
/**
* constructBloomFilter is a function that constructs an encrypted Bloom Filter
* representing a set of elements (keywords) with a given password, a given
* false positive ratio and the maximum number of elements that any Bloom
* Filter can contain in our scenario (this is useful so that all documents
* have the same size of bloom filters).
* This function follows the algorithm proposed by Goh in 2004 in his article
* about "secure indexes" and that allows to perform searchable encryption.
* The function first computes the length of the Bloom Filter depending on the
* errorRate and nbMaxKeywords using an auxiliary function computeBFLength
* previously explained.
* It then creates an array of the said length initialized with 0 at all
* positions.
* The array is then filled with ones at certain positions using the following:
* algorithm:
* For each keyword in the array keywords compute errorRate hashes:
* Each hash is the SHA256 function applied to the keyword concatenated
* with the password and the iterator of the hash function (j). The
* resulting digest is an array that is converted to a base64 string and
* concatenated with the id of the documents (to obtain different results
* if a given keyword is found in several documents). The result is then
* taken modulo the length of the Bloom Filter and indicates a position
* in the array which is set to one.
* In the end there are at most bFLength * errorRate 1s in the array (and in
* fact less because several keywords can lead to the same position for
* different hash functions).
*/
function constructBloomFilter(
password,
errorRate,
nbMaxKeywords,
keywords,
id
) {
var bFLength = computeBFLength(errorRate, nbMaxKeywords), result = [], i, j;
for (i = 0; i < bFLength; i += 1) {
result[i] = 0;
}
for (i = 0; i < keywords.length; i += 1) {
for (j = 0; j < errorRate; j += 1) {
result[bigModulo(sjcl.hash.sha256.hash(sjcl.codec.base64.fromBits(
sjcl.hash.sha256.hash(keywords[i] + password + j)
) + id), bFLength)] = 1;
}
}
return result;
}
/**
* constructEncryptedQuery is a function that constructs an encrypted query
* from a keyword and a password. It basically performs the first step of
* adding a word to a Bloom Filter.
* It hashes the keyword errorRate times using different hash functions.
* Each hash is the SHA256 function applied to the keyword concatenated
* with the password and the iterator of the hash function (j). The
* resulting digest is an array that is converted to a base64 string using the
* sjcl.codec.base64.fromBits function.
* In the end, the encrypted query corresponding to a keyword is an array of
* errorRates base64 strings. Note that the query can only be computed by the
* client as it requires knowledge of the secret key.
*/
function constructEncryptedQuery(
password,
errorRate,
keyword
) {
var result = [], j;
for (j = 0; j < errorRate; j += 1) {
result[j] = sjcl.codec.base64.fromBits(sjcl.hash.sha256.hash(
keyword + password + j
));
}
return result;
}
/* // Encrypt a message
function encrypt(plaintext, password) {
var rp = {}, ct, p;
p = {
adata: "",
iter: 1,
mode: "ccm",
ts: 64,
ks: 128,
iv: "t6vxTD/94Lk7DM87LZkPQA==",
cipher: "aes",
salt: "SdieDA4jA08="
};
ct = sjcl.encrypt(password, plaintext, p, rp);//.replace(/,/g,",\n");
return JSON.parse(ct).ct;
}
// Decrypt a message
function decrypt(ciphertext, password) {
var p, plaintext, rp = {};
p = {
adata: "",
iter: 1,
mode: "ccm",
ts: 64,
ks: 128,
iv: "t6vxTD/94Lk7DM87LZkPQA==",
cipher: "aes",
salt: "SdieDA4jA08=",
ct: ciphertext
};
plaintext = sjcl.decrypt(password, JSON.stringify(p), {}, rp);
return plaintext;
}*/
//Copied from the davstorage connector
/**
* Creates a new document if not already exists
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to put
* @param {Object} options The command options
*/
/* SearchableEncryptionStorage.prototype.post = function (
command,
metadata
) {
// this.postOrPut('post', command, metadata);
metadata.encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
this._keywords,
metadata._id
);
}; */
/**
* Creates or updates a document
*
* @method put
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.put = function (
command,
metadata
) {
// First create the associated encryptedIndex to allow encrypted queries at a
// later stage
// Then we encrypt the data using sjcl library. This step is independant of
// the searchable encryption features, however it is also related to
// confidentiality hence we added it here as an example of how to use the sjcl
// library.
var encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
metadata.keywords,
metadata._id
), data = sjcl.encrypt(this._password, JSON.stringify(metadata));
// The remainder is a classical put using the ajax method
jIO.util.ajax({
"type": "PUT",
"url": this._url + "/" + metadata._id,
"dataType": "json",
"data": {"metadata": data, "encryptedIndex": encryptedIndex}
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Creates a document if it does not already exist.
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.post = function (
command,
metadata
) {
// First create the associated encryptedIndex to allow encrypted queries at a
// later stage
// Then we encrypt the data using sjcl library. This step is independant of
// the searchable encryption features, however it is also related to
// confidentiality hence we added it here as an example of how to use the sjcl
// library.
var encryptedIndex = constructBloomFilter(
this._password,
this._errorRate,
this._nbMaxKeywords,
metadata.keywords,
metadata._id
), data = sjcl.encrypt(this._password, JSON.stringify(metadata));
// The remainder is a classical put using the ajax method
jIO.util.ajax({
"type": "POST",
"url": this._url + "/" + metadata._id,
"dataType": "json",
"data": {"metadata": data, "encryptedIndex": encryptedIndex}
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Adds attachments to a document
*
* @method putAttachment
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to putAttachment
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.putAttachment = function (
command,
param
) {
// This function adds an attachment to a document, it has nothing specific to
// searchable encryption. Optionally the attachment could be encrypted as well
// using the same primitive shown in previous methods.
jIO.util.ajax({
"type": "PUT",
"url": this._url + "/" + param._id + "/" + param._attachment,
"dataType": "blob",
"data": param._blob
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document update from server failed"
);
});
};
/**
* Retrieve metadata
*
* @method get
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
SearchableEncryptionStorage.prototype.get = function (
command,
param
) {
// This function retrieves a document given its ID. It is not specific to
// searchable encryption. Here we also have to decrypt the metadata as we
// encrypted them in the put or post methods.
var that = this;
jIO.util.ajax({
"type": "GET",
"url": this._url + "/" + param._id
}).then(function (e) {
var data = JSON.parse(sjcl.decrypt(
that._password,
e.target.responseText
));
command.success(e.target.status, {"data": data});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document retrieval from server failed"
);
});
};
SearchableEncryptionStorage.prototype.getAttachment = function (
command,
param
) {
// This function retrieves an attachment of a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "GET",
"url": this._url + "/" + param._id + "/" + param._attachment
}).then(function (e) {
command.success(e.target.status, {"data": e.target.response});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document retrieval from server failed"
);
});
};
SearchableEncryptionStorage.prototype.remove = function (
command,
param
) {
// This function removes a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "DELETE",
"url": this._url + "/" + param._id
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document removal from server failed"
);
});
};
SearchableEncryptionStorage.prototype.removeAttachment = function (
command,
param
) {
// This function removes an attachment of a document. Nothing specific to
// searchable encryption either.
jIO.util.ajax({
"type": "DELETE",
"url": this._url + "/" + param._id + "/" + param._attachment
}).then(function (e) {
command.success(e.target.status);
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Document removal from server failed"
);
});
};
/**
* AllDocs deals with encrypted queries. This is a core function of the
* searchable encryption connector.
* In this version, AllDOcs enables to retrieve all documents containing a
* keyword. However the server should not learn the keyword, and it should not
* learn anything with respect to the documents either if they are encrypted.
* The trick here is that the function encrypts the query (composed of a single
* keyword) by performing the first steps of the construction of the Bloom
* Filters: it hashes the keyword concatenated with the password and an
* iterator (which takes values between 0 and errorRate) and thus obtains an
* array of errorRate rows, each row is converted to a base64 string.
* Using this encrypted query the servers tests all documents it has stored with
* their respective encrypted indexes, and it returns the list of documents
* that match the query (without understanding the query though!). AllDocs
* simply has to decrypt all documents at this stage (since we encrypted them in
* the put and post steps): the user gets the documents he searched for with a
* high level of confidentiality against the server.
*/
SearchableEncryptionStorage.prototype.allDocs = function (
command,
param,
option
) {
/*jslint unparam: true */
var query, that = this;
query = constructEncryptedQuery(
this._password,
this._errorRate,
option.query
);
jIO.util.ajax({
"type": "POST",
"url": this._url,
"dataType": "json",
"data": {"query": query}
}).then(function (e) {
var document_list = e.target.response;
document_list = document_list.map(function (param) {
param = JSON.parse(sjcl.decrypt(that._password, param));
var row = {
"id": param._id,
"value": {}
};
if (option.include_docs === true) {
row.doc = param;
}
return row;
});
command.success(e.target.status, {"data": {
"total_rows": document_list.length,
"rows": document_list
}});
}, function (e) {
var xhr = e.target;
command.reject(
xhr.status,
xhr.statusText,
"Documents retrieval from server failed"
);
});
};
jIO.addStorage("searchableencryption", SearchableEncryptionStorage);
}));
// Methods remaining to be defined: only check and repair
jio-master-src/src/jio.storage/shastorage.js 0000664 0000000 0000000 00000005001 14230544473 0021430 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global Rusha*/
/**
* JIO Sha Storage. Type = 'sha'.
*/
(function (Rusha) {
"use strict";
var rusha = new Rusha();
function ShaStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
ShaStorage.prototype.post = function (param) {
return this._sub_storage.put(
rusha.digestFromString(JSON.stringify(param)),
param
);
};
ShaStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.hasCapacity = function () {
return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.buildQuery = function () {
return this._sub_storage.buildQuery.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.getAttachment = function () {
return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.putAttachment = function () {
return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.removeAttachment = function () {
return this._sub_storage.removeAttachment.apply(this._sub_storage,
arguments);
};
ShaStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
};
ShaStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
jIO.addStorage('sha', ShaStorage);
}(Rusha));
jio-master-src/src/jio.storage/splitstorage.js 0000664 0000000 0000000 00000041172 14230544473 0022021 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint indent:2, maxlen: 80, nomen: true */
/*global jIO, define, Blob */
/**
* Provides a split storage for JIO. This storage splits data
* and store them in the sub storages defined on the description.
*
* {
* "type": "split",
* "storage_list": [, ...]
* }
*/
// define([dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO);
}(['jio'], function (jIO) {
"use strict";
/**
* Generate a new uuid
*
* @method generateUuid
* @private
* @return {String} The new uuid
*/
function generateUuid() {
function S4() {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0' + string;
}
return string;
}
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
S4() + S4();
}
/**
* Class to merge allDocs responses from several sub storages.
*
* @class AllDocsResponseMerger
* @constructor
*/
function AllDocsResponseMerger() {
/**
* A list of allDocs response.
*
* @attribute response_list
* @type {Array} Contains allDocs responses
* @default []
*/
this.response_list = [];
}
AllDocsResponseMerger.prototype.constructor = AllDocsResponseMerger;
/**
* Add an allDocs response to the response list.
*
* @method addResponse
* @param {Object} response The allDocs response.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger.prototype.addResponse = function (response) {
this.response_list.push(response);
return this;
};
/**
* Add several allDocs responses to the response list.
*
* @method addResponseList
* @param {Array} response_list An array of allDocs responses.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger.prototype.addResponseList = function (response_list) {
var i;
for (i = 0; i < response_list.length; i += 1) {
this.response_list.push(response_list[i]);
}
return this;
};
/**
* Merge the response_list to one allDocs response.
*
* The merger will find rows with the same id in order to merge them, thanks
* to the onRowToMerge method. If no row correspond to an id, rows with the
* same id will be ignored.
*
* @method merge
* @param {Object} [option={}] The merge options
* @param {Boolean} [option.include_docs=false] Tell the merger to also
* merge metadata if true.
* @return {Object} The merged allDocs response.
*/
AllDocsResponseMerger.prototype.merge = function (option) {
var result = [], row, to_merge = [], tmp, i;
if (this.response_list.length === 0) {
return [];
}
/*jslint ass: true */
while ((row = this.response_list[0].data.rows.shift()) !== undefined) {
to_merge[0] = row;
for (i = 1; i < this.response_list.length; i += 1) {
to_merge[i] = AllDocsResponseMerger.listPopFromRowId(
this.response_list[i].data.rows,
row.id
);
if (to_merge[i] === undefined) {
break;
}
}
tmp = this.onRowToMerge(to_merge, option || {});
if (tmp !== undefined) {
result[result.length] = tmp;
}
}
this.response_list = [];
return {"total_rows": result.length, "rows": result};
};
/**
* This method is called when the merger want to merge several rows with the
* same id.
*
* @method onRowToMerge
* @param {Array} row_list An array of rows.
* @param {Object} [option={}] The merge option.
* @param {Boolean} [option.include_docs=false] Also merge the metadata if
* true
* @return {Object} The merged row
*/
AllDocsResponseMerger.prototype.onRowToMerge = function (row_list, option) {
var i, k, new_row = {"value": {}}, data = "";
option = option || {};
for (i = 0; i < row_list.length; i += 1) {
new_row.id = row_list[i].id;
if (row_list[i].key) {
new_row.key = row_list[i].key;
}
if (option.include_docs) {
new_row.doc = new_row.doc || {};
for (k in row_list[i].doc) {
if (row_list[i].doc.hasOwnProperty(k)) {
if (k[0] === "_") {
new_row.doc[k] = row_list[i].doc[k];
}
}
}
data += row_list[i].doc.data;
}
}
if (option.include_docs) {
try {
data = JSON.parse(data);
} catch (e) { return undefined; }
for (k in data) {
if (data.hasOwnProperty(k)) {
new_row.doc[k] = data[k];
}
}
}
return new_row;
};
/**
* Search for a specific row and pop it. During the search operation, all
* parsed rows are stored on a dictionnary in order to be found instantly
* later.
*
* @method listPopFromRowId
* @param {Array} rows The row list
* @param {String} doc_id The document/row id
* @return {Object/undefined} The poped row
*/
AllDocsResponseMerger.listPopFromRowId = function (rows, doc_id) {
var row;
if (!rows.dict) {
rows.dict = {};
}
if (rows.dict[doc_id]) {
row = rows.dict[doc_id];
delete rows.dict[doc_id];
return row;
}
/*jslint ass: true*/
while ((row = rows.shift()) !== undefined) {
if (row.id === doc_id) {
return row;
}
rows.dict[row.id] = row;
}
};
/**
* The split storage class used by JIO.
*
* A split storage instance is able to i/o on several sub storages with
* split documents.
*
* @class SplitStorage
*/
function SplitStorage(spec) {
var that = this, priv = {};
/**
* The list of sub storages we want to use to store part of documents.
*
* @attribute storage_list
* @private
* @type {Array} Array of storage descriptions
*/
priv.storage_list = spec.storage_list;
//////////////////////////////////////////////////////////////////////
// Tools
/**
* Send a command to all sub storages. All the response are returned
* in a list. The index of the response correspond to the storage_list
* index. If an error occurs during operation, the callback is called with
* `callback(err, undefined)`. The response is given with
* `callback(undefined, response_list)`.
*
* `doc` is the document informations but can also be a list of dedicated
* document informations. In this case, each document is associated to one
* sub storage.
*
* @method send
* @private
* @param {String} method The command method
* @param {Object,Array} doc The document information to send to each sub
* storages or a list of dedicated document
* @param {Object} option The command option
* @param {Function} callback Called at the end
*/
priv.send = function (command, method, doc, option, callback) {
var i, answer_list = [], failed = false;
function onEnd() {
i += 1;
if (i === priv.storage_list.length) {
callback(undefined, answer_list);
}
}
function onSuccess(i) {
return function (response) {
if (!failed) {
answer_list[i] = response;
}
onEnd();
};
}
function onError(i) {
return function (err) {
if (!failed) {
failed = true;
err.index = i;
callback(err, undefined);
}
};
}
if (!Array.isArray(doc)) {
for (i = 0; i < priv.storage_list.length; i += 1) {
if (method === 'allDocs') {
command.storage(priv.storage_list[i])[method](option).
then(onSuccess(i), onError(i));
} else {
command.storage(priv.storage_list[i])[method](doc, option).
then(onSuccess(i), onError(i));
}
}
} else {
for (i = 0; i < priv.storage_list.length; i += 1) {
// we assume that alldocs is not called if the there is several docs
command.storage(priv.storage_list[i])[method](doc[i], option).
then(onSuccess(i), onError(i));
}
}
i = 0;
};
/**
* Split document metadata then store them to the sub storages.
*
* @method postOrPut
* @private
* @param {Object} doc A serialized document object
* @param {Object} option Command option properties
* @param {String} method The command method ('post' or 'put')
*/
priv.postOrPut = function (command, doc, option, method) {
var i, data, doc_list = [], doc_underscores = {};
if (!doc._id) {
doc._id = generateUuid(); // XXX should let gidstorage guess uid
}
for (i in doc) {
if (doc.hasOwnProperty(i)) {
if (i[0] === "_") {
doc_underscores[i] = doc[i];
delete doc[i];
}
}
}
data = JSON.stringify(doc);
for (i = 0; i < priv.storage_list.length; i += 1) {
doc_list[i] = JSON.parse(JSON.stringify(doc_underscores));
doc_list[i].data = data.slice(
(data.length / priv.storage_list.length) * i,
(data.length / priv.storage_list.length) * (i + 1)
);
}
priv.send(command, method, doc_list, option, function (err) {
if (err) {
err.message = "Unable to " + method + " document";
delete err.index;
return command.error(err);
}
command.success({"id": doc_underscores._id});
});
};
//////////////////////////////////////////////////////////////////////
// JIO commands
/**
* Split document metadata then store them to the sub storages.
*
* @method post
* @param {Object} command The JIO command
*/
that.post = function (command, metadata, option) {
priv.postOrPut(command, metadata, option, 'post');
};
/**
* Split document metadata then store them to the sub storages.
*
* @method put
* @param {Object} command The JIO command
*/
that.put = function (command, metadata, option) {
priv.postOrPut(command, metadata, option, 'put');
};
/**
* Puts an attachment to the sub storages.
*
* @method putAttachment
* @param {Object} command The JIO command
*/
that.putAttachment = function (command, param, option) {
var i, attachment_list = [], data = param._blob;
for (i = 0; i < priv.storage_list.length; i += 1) {
attachment_list[i] = jIO.util.deepClone(param);
attachment_list[i]._blob = data.slice(
data.size * i / priv.storage_list.length,
data.size * (i + 1) / priv.storage_list.length,
data.type
);
}
priv.send(
command,
'putAttachment',
attachment_list,
option,
function (err) {
if (err) {
err.message = "Unable to put attachment";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Gets splited document metadata then returns real document.
*
* @method get
* @param {Object} command The JIO command
*/
that.get = function (command, param, option) {
var doc = param;
priv.send(command, 'get', doc, option, function (err, response) {
var i, k;
if (err) {
err.message = "Unable to get document";
delete err.index;
return command.error(err);
}
doc = '';
for (i = 0; i < response.length; i += 1) {
response[i] = response[i].data;
doc += response[i].data;
}
doc = JSON.parse(doc);
for (i = 0; i < response.length; i += 1) {
for (k in response[i]) {
if (response[i].hasOwnProperty(k)) {
if (k[0] === "_") {
doc[k] = response[i][k];
}
}
}
}
delete doc._attachments;
for (i = 0; i < response.length; i += 1) {
if (response[i]._attachments) {
for (k in response[i]._attachments) {
if (response[i]._attachments.hasOwnProperty(k)) {
doc._attachments = doc._attachments || {};
doc._attachments[k] = doc._attachments[k] || {
"length": 0,
"content_type": ""
};
doc._attachments[k].length += response[i]._attachments[k].
length;
// if (response[i]._attachments[k].digest) {
// if (doc._attachments[k].digest) {
// doc._attachments[k].digest += " " + response[i].
// _attachments[k].digest;
// } else {
// doc._attachments[k].digest = response[i].
// _attachments[k].digest;
// }
// }
doc._attachments[k].content_type = response[i]._attachments[k].
content_type;
}
}
}
}
command.success({"data": doc});
});
};
/**
* Gets splited document attachment then returns real attachment data.
*
* @method getAttachment
* @param {Object} command The JIO command
*/
that.getAttachment = function (command, param, option) {
priv.send(command, 'getAttachment', param, option, function (
err,
response
) {
if (err) {
err.message = "Unable to get attachment";
delete err.index;
return command.error(err);
}
command.success({"data": new Blob(response.map(function (answer) {
return answer.data;
}), {"type": response[0].data.type})});
});
};
/**
* Removes a document from the sub storages.
*
* @method remove
* @param {Object} command The JIO command
*/
that.remove = function (command, param, option) {
priv.send(
command,
'remove',
param,
option,
function (err) {
if (err) {
err.message = "Unable to remove document";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Removes an attachment from the sub storages.
*
* @method removeAttachment
* @param {Object} command The JIO command
*/
that.removeAttachment = function (command, param, option) {
priv.send(
command,
'removeAttachment',
param,
option,
function (err) {
if (err) {
err.message = "Unable to remove attachment";
delete err.index;
return command.error(err);
}
command.success();
}
);
};
/**
* Retreive a list of all document in the sub storages.
*
* If include_docs option is false, then it returns the document list from
* the first sub storage. Else, it will merge results and return.
*
* @method allDocs
* @param {Object} command The JIO command
*/
that.allDocs = function (command, param, option) {
option = {"include_docs": option.include_docs};
priv.send(
command,
'allDocs',
param,
option,
function (err, response_list) {
var all_docs_merger;
if (err) {
err.message = "Unable to retrieve document list";
delete err.index;
return command.error(err);
}
all_docs_merger = new AllDocsResponseMerger();
all_docs_merger.addResponseList(response_list);
return command.success({"data": all_docs_merger.merge(option)});
}
);
};
} // end of splitStorage
jIO.addStorage('split', SplitStorage);
}));
jio-master-src/src/jio.storage/splitstorage.tests.js 0000664 0000000 0000000 00000060241 14230544473 0023160 0 ustar 00root root 0000000 0000000 /*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, test_util, RSVP, test, ok, deepEqual, module, stop,
start, hex_sha256 */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO, test_util, RSVP);
}([
'jio',
'test_util',
'rsvp',
'localstorage',
'splitstorage'
], function (jIO, util, RSVP) {
"use strict";
var tool = {
"readBlobAsBinaryString": jIO.util.readBlobAsBinaryString
};
function reverse(promise) {
return new RSVP.Promise(function (resolve, reject, notify) {
promise.then(reject, resolve, notify);
}, function () {
promise.cancel();
});
}
/**
* sequence(thens): Promise
*
* Executes a sequence of *then* callbacks. It acts like
* `smth().then(callback).then(callback)...`. The first callback is called
* with no parameter.
*
* Elements of `thens` array can be a function or an array contaning at most
* three *then* callbacks: *onFulfilled*, *onRejected*, *onNotified*.
*
* When `cancel()` is executed, each then promises are cancelled at the same
* time.
*
* @param {Array} thens An array of *then* callbacks
* @return {Promise} A new promise
*/
function sequence(thens) {
var promises = [];
return new RSVP.Promise(function (resolve, reject, notify) {
var i;
promises[0] = new RSVP.Promise(function (resolve) {
resolve();
});
for (i = 0; i < thens.length; i += 1) {
if (Array.isArray(thens[i])) {
promises[i + 1] = promises[i].
then(thens[i][0], thens[i][1], thens[i][2]);
} else {
promises[i + 1] = promises[i].then(thens[i]);
}
}
promises[i].then(resolve, reject, notify);
}, function () {
var i;
for (i = 0; i < promises.length; i += 1) {
promises[i].cancel();
}
});
}
function unexpectedError(error) {
if (error instanceof Error) {
deepEqual([
error.name + ": " + error.message,
error
], "UNEXPECTED ERROR", "Unexpected error");
} else {
deepEqual(error, "UNEXPECTED ERROR", "Unexpected error");
}
}
module("SplitStorage + LocalStorage");
test("Post", function () {
var shared = {}, jio, jio_local_list = [];
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "post1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "post2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
jio_local_list[0] = jIO.createJIO(shared.local_storage_description1, {
"workspace": shared.workspace
});
jio_local_list[1] = jIO.createJIO(shared.local_storage_description2, {
"workspace": shared.workspace
});
jio_local_list.run = function (method, argument) {
var i, promises = [];
for (i = 0; i < this.length; i += 1) {
promises[i] = this[i][method].apply(this[i], argument);
}
return RSVP.all(promises);
};
jio_local_list.get = function () {
return this.run("get", arguments);
};
stop();
// post without id
jio.post({
"_underscored_meta": "uvalue",
"meta": "data"
}).then(function (answer) {
shared.uuid = answer.id;
answer.id = "";
ok(util.isUuid(shared.uuid), "Uuid should look like " +
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + shared.uuid);
deepEqual(answer, {
"id": "",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post document without id");
// check uploaded documents
return jio_local_list.get({"_id": shared.uuid});
}).then(function (answers) {
var i;
for (i = 0; i < answers.length; i += 1) {
deepEqual(answers[i].data, {
"_id": shared.uuid,
"_underscored_meta": "uvalue",
"data": i === 0 ? "{\"meta\"" : ":\"data\"}"
}, "Check uploaded document in sub storage " + (i + 1));
}
// post with id
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data",
"hello": "world"
});
}).then(function (answer) {
deepEqual(answer, {
"id": "one",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post document with id");
// check uploaded documents
return jio_local_list.get({"_id": "one"});
}).then(function (answers) {
deepEqual(answers[0].data, {
"_id": "one",
"_underscored_meta": "uvalue",
"data": "{\"meta\":\"data\","
}, "Check uploaded document in sub storage 1");
deepEqual(answers[1].data, {
"_id": "one",
"_underscored_meta": "uvalue",
"data": "\"hello\":\"world\"}"
}, "Check uploaded document in sub storage 2");
// post with id
return reverse(jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data",
"hello": "world"
}));
}).then(function (answer) {
deepEqual(answer, {
"error": "conflict",
"id": "one",
"message": "Unable to post document",
"method": "post",
"reason": "document exists",
"result": "error",
"status": 409,
"statusText": "Conflict"
}, "Post document with same id -> 409 Conflict");
}).fail(unexpectedError).always(start);
});
test("PutAttachment", function () {
var shared = {}, jio, jio_local_list = [];
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "putAttachment1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "putAttachment2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
jio_local_list[0] = jIO.createJIO(shared.local_storage_description1, {
"workspace": shared.workspace
});
jio_local_list[1] = jIO.createJIO(shared.local_storage_description2, {
"workspace": shared.workspace
});
jio_local_list.run = function (method, argument) {
var i, promises = [];
for (i = 0; i < this.length; i += 1) {
promises[i] = this[i][method].apply(this[i], argument);
}
return RSVP.all(promises);
};
jio_local_list.get = function () {
return this.run("get", arguments);
};
jio_local_list.getAttachmentAsBinaryString = function () {
return this.run("getAttachment", arguments).then(function (answers) {
var i, promises = [];
for (i = 0; i < answers.length; i += 1) {
promises[i] = tool.readBlobAsBinaryString(answers[i].data);
}
return RSVP.all(promises);
});
};
stop();
return reverse(jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_content_type": "text/plain"
})).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to put attachment",
"method": "putAttachment",
"reason": "missing",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Put attachment on a inexistent document -> 404 Not Found");
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
});
}).then(function () {
return jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_mimetype": "text/plain"
});
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"id": "one",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Put attachment on a document");
// check uploaded documents
return jio_local_list.get({"_id": "one"});
}).then(function (answers) {
deepEqual(answers[0].data, {
"_attachments": {
"my_attachment": {
"content_type": "text/plain",
"digest": "sha256-ebf2d770a6a2dfa135f6c81431f22fc3cbcde9ae" +
"e52759ca9e520d4d964c1322", // sha256("My ")
"length": 3
}
},
"_id": "one",
"_underscored_meta": "uvalue",
"data": "{\"meta\""
}, "Check uploaded document in sub storage 1");
deepEqual(answers[1].data, {
"_attachments": {
"my_attachment": {
"content_type": "text/plain",
"digest": "sha256-cec3a9b89b2e391393d0f68e4bc12a9fa6cf358b" +
"3cdf79496dc442d52b8dd528", // sha256("Data")
"length": 4
}
},
"_id": "one",
"_underscored_meta": "uvalue",
"data": ":\"data\"}"
}, "Check uploaded document in sub storage 2");
return jio_local_list.getAttachmentAsBinaryString({
"_id": "one",
"_attachment": "my_attachment"
});
}).then(function (events) {
deepEqual(events[0].target.result, "My ",
"Check uploaded document in sub storage 1");
deepEqual(events[1].target.result, "Data",
"Check uploaded document in sub storage 1");
}).fail(unexpectedError).always(start);
});
test("Get", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "get1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "get2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
reverse(jio.get({"_id": "one"})).then(function (answer) {
deepEqual(answer, {
"error": "not_found",
"id": "one",
"message": "Unable to get document",
"method": "get",
"reason": "missing",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get missing document");
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
});
}).then(function () {
return jio.get({"_id": "one"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
}, "Get posted document");
return jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_content_type": "text/plain"
});
}).then(function () {
return jio.get({"_id": "one"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data",
"_attachments": {
"my_attachment": {
"length": 7,
"content_type": "text/plain"
}
}
}, "Get document with attachment informations");
}).fail(unexpectedError).always(start);
});
test("GetAttachment", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "getAttachment1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "getAttachment2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
reverse(jio.getAttachment({
"_id": "one",
"_attachment": "my_attachment"
})).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to get attachment",
"method": "getAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get attachment from missing document -> 404 Not Found");
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
});
}).then(function () {
return reverse(jio.getAttachment({
"_id": "one",
"_attachment": "my_attachment"
}));
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to get attachment",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get missing attachment from document");
return jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_mimetype": "text/plain"
});
}).then(function () {
return jio.getAttachment({"_id": "one", "_attachment": "my_attachment"});
}).then(function (answer) {
return tool.readBlobAsBinaryString(answer.data);
}).then(function (event) {
deepEqual(event.target.result, "My Data", "Get attachment");
}).fail(unexpectedError).always(start);
});
test("RemoveAttachment", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "removeAttachment1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "removeAttachment2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
reverse(jio.removeAttachment({
"_id": "one",
"_attachment": "my_attachment"
})).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to remove attachment",
"method": "removeAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove attachment from inexistent document -> 404 Not Found");
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
});
}).then(function () {
return reverse(jio.removeAttachment({
"_id": "one",
"_attachment": "my_attachment"
}));
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to remove attachment",
"method": "removeAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove inexistent attachment -> 404 Not Found");
return jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_mimetype": "text/plain"
});
}).then(function () {
return jio.removeAttachment({
"_id": "one",
"_attachment": "my_attachment"
});
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"id": "one",
"method": "removeAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove attachment");
return jio.get({"_id": "one"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
}, "Check document");
return reverse(jio.getAttachment({
"_id": "one",
"_attachment": "my_attachment"
}));
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to get attachment",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Check attachment -> 404 Not Found");
}).fail(unexpectedError).always(start);
});
test("Remove", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "remove1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "remove2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
reverse(jio.remove({"_id": "one"})).then(function (answer) {
deepEqual(answer, {
"error": "not_found",
"id": "one",
"message": "Unable to remove document",
"method": "remove",
"reason": "missing",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove missing document -> 404 Not Found");
return jio.post({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
});
}).then(function () {
return jio.putAttachment({
"_id": "one",
"_attachment": "my_attachment",
"_data": "My Data",
"_mimetype": "text/plain"
});
}).then(function () {
return jio.remove({"_id": "one"});
}).then(function (answer) {
deepEqual(answer, {
"id": "one",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove document");
return reverse(jio.getAttachment({
"_id": "one",
"_attachment": "my_attachment"
}));
}).then(function (answer) {
deepEqual(answer, {
"attachment": "my_attachment",
"error": "not_found",
"id": "one",
"message": "Unable to get attachment",
"method": "getAttachment",
"reason": "missing document",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Check attachment -> 404 Not Found");
return reverse(jio.get({"_id": "one"}));
}).then(function (answer) {
deepEqual(answer, {
"error": "not_found",
"id": "one",
"message": "Unable to get document",
"method": "get",
"reason": "missing",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Check document -> 404 Not Found");
}).fail(unexpectedError).always(start);
});
test("Put", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "put1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "put2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
jio.put({
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
}).then(function (answer) {
deepEqual(answer, {
"id": "one",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Put new document");
return jio.get({"_id": "one"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "one",
"_underscored_meta": "uvalue",
"meta": "data"
}, "Check document");
return jio.put({
"_id": "one",
"_underscored_meta": "uvalue",
"meow": "dog"
});
}).then(function (answer) {
deepEqual(answer, {
"id": "one",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Put same document again");
return jio.get({"_id": "one"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "one",
"_underscored_meta": "uvalue",
"meow": "dog"
}, "Get document for check");
}).fail(unexpectedError).always(start);
});
test("AllDocs", function () {
var shared = {}, jio;
shared.workspace = {};
shared.local_storage_description1 = {
"type": "local",
"username": "splitstorage",
"application_name": "alldocs1",
"mode": "memory"
};
shared.local_storage_description2 = {
"type": "local",
"username": "splitstorage",
"application_name": "alldocs2",
"mode": "memory"
};
jio = jIO.createJIO({
"type": "split",
"storage_list": [
shared.local_storage_description1,
shared.local_storage_description2
]
}, {"workspace": shared.workspace});
stop();
function prepareDatabase() {
var i, do_list = [];
function post(i) {
return function () {
return jio.post({
"_id": "doc" + i,
"_underscored_meta": "uvalue" + i,
"meta": "data" + i
});
};
}
function putAttachment(i) {
return function () {
return jio.putAttachment({
"_id": "doc" + i,
"_attachment": "my_attachment" + i,
"_data": "My Data" + i,
"_content_type": "text/plain"
});
};
}
for (i = 0; i < 5; i += 1) {
do_list.push(post(i));
}
for (i = 0; i < 2; i += 1) {
do_list.push(putAttachment(i));
}
return sequence(do_list);
}
prepareDatabase().then(function () {
return jio.get({"_id": "doc1"});
}).then(function (answer) {
deepEqual(answer.data, {
"_id": "doc1",
"_underscored_meta": "uvalue1",
"meta": "data1",
"_attachments": {
"my_attachment1": {
"length": 8,
"content_type": "text/plain"
}
}
}, "Check document");
return jio.allDocs();
}).then(function (answer) {
answer.data.rows.sort(function (a, b) {
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
});
deepEqual(answer.data, {
"total_rows": 5,
"rows": [{
"id": "doc0",
"key": "doc0",
"value": {}
}, {
"id": "doc1",
"key": "doc1",
"value": {}
}, {
"id": "doc2",
"key": "doc2",
"value": {}
}, {
"id": "doc3",
"key": "doc3",
"value": {}
}, {
"id": "doc4",
"key": "doc4",
"value": {}
}]
}, "AllDocs with document ids only");
}).fail(unexpectedError).always(start);
});
}));
jio-master-src/src/jio.storage/unionstorage.js 0000664 0000000 0000000 00000017017 14230544473 0022017 0 ustar 00root root 0000000 0000000 /*
* Copyright 2014, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true */
/*global RSVP*/
/**
* JIO Union Storage. Type = 'union'.
* This provide a unified access other multiple storage.
* New document are created in the first sub storage.
* Document are searched in each sub storage until it is found.
*
*
* Storage Description:
*
* {
* "type": "union",
* "storage_list": [
* sub_storage_description_1,
* sub_storage_description_2,
*
* sub_storage_description_X,
* ]
* }
*
* @class UnionStorage
*/
(function (jIO, RSVP) {
"use strict";
/**
* The JIO UnionStorage extension
*
* @class UnionStorage
* @constructor
*/
function UnionStorage(spec) {
if (!Array.isArray(spec.storage_list)) {
throw new jIO.util.jIOError("storage_list is not an Array", 400);
}
var i;
this._storage_list = [];
for (i = 0; i < spec.storage_list.length; i += 1) {
this._storage_list.push(jIO.createJIO(spec.storage_list[i]));
}
}
UnionStorage.prototype._getWithStorageIndex = function () {
var i,
index = 0,
context = this,
arg = arguments,
result = this._storage_list[0].get.apply(this._storage_list[0], arg);
function handle404(j) {
result
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return context._storage_list[j].get.apply(context._storage_list[j],
arg)
.push(function (doc) {
index = j;
return doc;
});
}
throw error;
});
}
for (i = 1; i < this._storage_list.length; i += 1) {
handle404(i);
}
return result
.push(function (doc) {
return [index, doc];
});
};
/*
* Get a document
* Try on each substorage on after the other
*/
UnionStorage.prototype.get = function () {
return this._getWithStorageIndex.apply(this, arguments)
.push(function (result) {
return result[1];
});
};
/*
* Get attachments list
* Try on each substorage on after the other
*/
UnionStorage.prototype.allAttachments = function () {
var argument_list = arguments,
context = this;
return this._getWithStorageIndex.apply(this, arguments)
.push(function (result) {
var sub_storage = context._storage_list[result[0]];
return sub_storage.allAttachments.apply(sub_storage, argument_list);
});
};
/*
* Post a document
* Simply store on the first substorage
*/
UnionStorage.prototype.post = function () {
return this._storage_list[0].post.apply(this._storage_list[0], arguments);
};
/*
* Put a document
* Search the document location, and modify it in its storage.
*/
UnionStorage.prototype.put = function () {
var arg = arguments,
context = this;
return this._getWithStorageIndex(arg[0])
.push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
// Document does not exist, create in first substorage
return [0];
}
throw error;
})
.push(function (result) {
// Storage found, modify in it directly
var sub_storage = context._storage_list[result[0]];
return sub_storage.put.apply(sub_storage, arg);
});
};
/*
* Remove a document
* Search the document location, and remove it from its storage.
*/
UnionStorage.prototype.remove = function () {
var arg = arguments,
context = this;
return this._getWithStorageIndex(arg[0])
.push(function (result) {
// Storage found, remove from it directly
var sub_storage = context._storage_list[result[0]];
return sub_storage.remove.apply(sub_storage, arg);
});
};
UnionStorage.prototype.buildQuery = function () {
var promise_list = [],
i,
id_dict = {},
len = this._storage_list.length,
sub_storage;
for (i = 0; i < len; i += 1) {
sub_storage = this._storage_list[i];
promise_list.push(sub_storage.buildQuery.apply(sub_storage, arguments));
}
return new RSVP.Queue()
.push(function () {
return RSVP.all(promise_list);
})
.push(function (result_list) {
var result = [],
sub_result,
sub_result_len,
j;
len = result_list.length;
for (i = 0; i < len; i += 1) {
sub_result = result_list[i];
sub_result_len = sub_result.length;
for (j = 0; j < sub_result_len; j += 1) {
if (!id_dict.hasOwnProperty(sub_result[j].id)) {
id_dict[sub_result[j].id] = null;
result.push(sub_result[j]);
}
}
}
return result;
});
};
UnionStorage.prototype.hasCapacity = function (name) {
var i,
len,
result,
sub_storage;
if ((name === "list") ||
(name === "query") ||
(name === "select")) {
result = true;
len = this._storage_list.length;
for (i = 0; i < len; i += 1) {
sub_storage = this._storage_list[i];
result = result && sub_storage.hasCapacity(name);
}
return result;
}
return false;
};
UnionStorage.prototype.repair = function () {
var i,
promise_list = [];
for (i = 0; i < this._storage_list.length; i += 1) {
promise_list.push(this._storage_list[i].repair.apply(
this._storage_list[i],
arguments
));
}
return RSVP.all(promise_list);
};
UnionStorage.prototype.getAttachment = function () {
var argument_list = arguments,
context = this;
return this._getWithStorageIndex.apply(this, arguments)
.push(function (result) {
var sub_storage = context._storage_list[result[0]];
return sub_storage.getAttachment.apply(sub_storage, argument_list);
});
};
UnionStorage.prototype.putAttachment = function () {
var argument_list = arguments,
context = this;
return this._getWithStorageIndex.apply(this, arguments)
.push(function (result) {
var sub_storage = context._storage_list[result[0]];
return sub_storage.putAttachment.apply(sub_storage, argument_list);
});
};
UnionStorage.prototype.removeAttachment = function () {
var argument_list = arguments,
context = this;
return this._getWithStorageIndex.apply(this, arguments)
.push(function (result) {
var sub_storage = context._storage_list[result[0]];
return sub_storage.removeAttachment.apply(sub_storage, argument_list);
});
};
jIO.addStorage('union', UnionStorage);
}(jIO, RSVP));
jio-master-src/src/jio.storage/uuidstorage.js 0000664 0000000 0000000 00000005517 14230544473 0021637 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
(function (jIO) {
"use strict";
/**
* The jIO UUIDStorage extension
*
* @class UUIDStorage
* @constructor
*/
function UUIDStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
UUIDStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.post = function (param) {
function S4() {
return ('0000' + Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16)).slice(-4);
}
var id = S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
return this.put(id, param);
};
UUIDStorage.prototype.put = function () {
return this._sub_storage.put.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.getAttachment = function () {
return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.putAttachment = function () {
return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.removeAttachment = function () {
return this._sub_storage.removeAttachment.apply(this._sub_storage,
arguments);
};
UUIDStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
UUIDStorage.prototype.hasCapacity = function (name) {
return this._sub_storage.hasCapacity(name);
};
UUIDStorage.prototype.buildQuery = function () {
return this._sub_storage.buildQuery.apply(this._sub_storage,
arguments);
};
jIO.addStorage('uuid', UUIDStorage);
}(jIO));
jio-master-src/src/jio.storage/websqlstorage.js 0000664 0000000 0000000 00000026515 14230544473 0022167 0 ustar 00root root 0000000 0000000 /*
* Copyright 2013, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/**
* JIO Websql Storage. Type = "websql".
* websql "database" storage.
*/
/*global Blob, jIO, RSVP, openDatabase*/
/*jslint nomen: true*/
(function (jIO, RSVP, Blob, openDatabase) {
"use strict";
/**
* The JIO Websql Storage extension
*
* @class WebSQLStorage
* @constructor
*/
function queueSql(db, query_list, argument_list) {
return new RSVP.Promise(function (resolve, reject) {
/*jslint unparam: true*/
db.transaction(function (tx) {
var len = query_list.length,
result_list = [],
i;
function resolveTransaction(tx, result) {
result_list.push(result);
if (result_list.length === len) {
resolve(result_list);
}
}
function rejectTransaction(tx, error) {
reject(error);
return true;
}
for (i = 0; i < len; i += 1) {
tx.executeSql(query_list[i], argument_list[i], resolveTransaction,
rejectTransaction);
}
}, function (tx, error) {
reject(error);
});
/*jslint unparam: false*/
});
}
function initDatabase(db) {
var query_list = [
"CREATE TABLE IF NOT EXISTS document" +
"(id VARCHAR PRIMARY KEY NOT NULL, data TEXT)",
"CREATE TABLE IF NOT EXISTS attachment" +
"(id VARCHAR, attachment VARCHAR, part INT, blob TEXT)",
"CREATE TRIGGER IF NOT EXISTS removeAttachment " +
"BEFORE DELETE ON document FOR EACH ROW " +
"BEGIN DELETE from attachment WHERE id = OLD.id;END;",
"CREATE INDEX IF NOT EXISTS index_document ON document (id);",
"CREATE INDEX IF NOT EXISTS index_attachment " +
"ON attachment (id, attachment);"
];
return new RSVP.Queue()
.push(function () {
return queueSql(db, query_list, []);
});
}
function WebSQLStorage(spec) {
if (typeof spec.database !== 'string' || !spec.database) {
throw new TypeError("database must be a string " +
"which contains more than one character.");
}
this._database = openDatabase("jio:" + spec.database,
'1.0', '', 2 * 1024 * 1024);
if (spec.blob_length &&
(typeof spec.blob_length !== "number" ||
spec.blob_length < 20)) {
throw new TypeError("blob_len parameter must be a number >= 20");
}
this._blob_length = spec.blob_length || 2000000;
this._init_db_promise = initDatabase(this._database);
}
WebSQLStorage.prototype.put = function (id, param) {
var db = this._database,
that = this,
data_string = JSON.stringify(param);
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["INSERT OR REPLACE INTO " +
"document(id, data) VALUES(?,?)"],
[[id, data_string]]);
})
.push(function () {
return id;
});
};
WebSQLStorage.prototype.remove = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["DELETE FROM document WHERE id = ?"], [[id]]);
})
.push(function (result_list) {
if (result_list[0].rowsAffected === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return id;
});
};
WebSQLStorage.prototype.get = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["SELECT data FROM document WHERE id = ?"],
[[id]]);
})
.push(function (result_list) {
if (result_list[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return JSON.parse(result_list[0].rows[0].data);
});
};
WebSQLStorage.prototype.allAttachments = function (id) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, [
"SELECT id FROM document WHERE id = ?",
"SELECT DISTINCT attachment FROM attachment WHERE id = ?"
], [[id], [id]]);
})
.push(function (result_list) {
if (result_list[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
var len = result_list[1].rows.length,
obj = {},
i;
for (i = 0; i < len; i += 1) {
obj[result_list[1].rows[i].attachment] = {};
}
return obj;
});
};
function sendBlobPart(blob, argument_list, index, queue) {
queue.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (strBlob) {
argument_list[index + 2].push(strBlob.target.result);
return;
});
}
WebSQLStorage.prototype.putAttachment = function (id, name, blob) {
var db = this._database,
that = this,
part_size = this._blob_length;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["SELECT id FROM document WHERE id = ?"], [[id]]);
})
.push(function (result) {
var query_list = [],
argument_list = [],
blob_size = blob.size,
queue = new RSVP.Queue(),
i,
index;
if (result[0].rows.length === 0) {
throw new jIO.util.jIOError("Cannot access subdocument", 404);
}
query_list.push("DELETE FROM attachment WHERE id = ? " +
"AND attachment = ?");
argument_list.push([id, name]);
query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
"VALUES(?, ?, ?, ?)");
argument_list.push([id, name, -1,
blob.type || "application/octet-stream"]);
for (i = 0, index = 0; i < blob_size; i += part_size, index += 1) {
query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
"VALUES(?, ?, ?, ?)");
argument_list.push([id, name, index]);
sendBlobPart(blob.slice(i, i + part_size), argument_list, index,
queue);
}
queue.push(function () {
return queueSql(db, query_list, argument_list);
});
return queue;
});
};
WebSQLStorage.prototype.getAttachment = function (id, name, options) {
var db = this._database,
that = this,
part_size = this._blob_length,
start,
end,
start_index,
end_index;
if (options === undefined) { options = {}; }
start = options.start || 0;
end = options.end || -1;
if (start < 0 || (options.end !== undefined && options.end < 0)) {
throw new jIO.util.jIOError("_start and _end must be positive",
400);
}
if (start > end && end !== -1) {
throw new jIO.util.jIOError("_start is greater than _end",
400);
}
start_index = Math.floor(start / part_size);
if (start === 0) { start_index -= 1; }
end_index = Math.floor(end / part_size);
if (end % part_size === 0) {
end_index -= 1;
}
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
var command = "SELECT part, blob FROM attachment WHERE id = ? AND " +
"attachment = ? AND part >= ?",
argument_list = [id, name, start_index];
if (end !== -1) {
command += " AND part <= ?";
argument_list.push(end_index);
}
return queueSql(db, [command], [argument_list]);
})
.push(function (response_list) {
var i,
response,
blob_array = [],
blob,
type;
response = response_list[0].rows;
if (response.length === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
for (i = 0; i < response.length; i += 1) {
if (response[i].part === -1) {
type = response[i].blob;
start_index += 1;
} else {
blob_array.push(jIO.util.dataURItoBlob(response[i].blob));
}
}
if ((start === 0) && (options.end === undefined)) {
return new Blob(blob_array, {type: type});
}
blob = new Blob(blob_array, {});
return blob.slice(start - (start_index * part_size),
end === -1 ? blob.size :
end - (start_index * part_size),
"application/octet-stream");
});
};
WebSQLStorage.prototype.removeAttachment = function (id, name) {
var db = this._database,
that = this;
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
return queueSql(db, ["DELETE FROM attachment WHERE " +
"id = ? AND attachment = ?"], [[id, name]]);
})
.push(function (result) {
if (result[0].rowsAffected === 0) {
throw new jIO.util.jIOError("Cannot find document", 404);
}
return name;
});
};
WebSQLStorage.prototype.hasCapacity = function (name) {
return (name === "list" || (name === "include"));
};
WebSQLStorage.prototype.buildQuery = function (options) {
var db = this._database,
that = this,
query = "SELECT id";
return new RSVP.Queue()
.push(function () {
return that._init_db_promise;
})
.push(function () {
if (options === undefined) { options = {}; }
if (options.include_docs === true) {
query += ", data AS doc";
}
query += " FROM document";
return queueSql(db, [query], [[]]);
})
.push(function (result) {
var array = [],
len = result[0].rows.length,
i;
for (i = 0; i < len; i += 1) {
array.push(result[0].rows[i]);
array[i].value = {};
if (array[i].doc !== undefined) {
array[i].doc = JSON.parse(array[i].doc);
}
}
return array;
});
};
jIO.addStorage('websql', WebSQLStorage);
}(jIO, RSVP, Blob, openDatabase));
jio-master-src/src/jio.storage/xwikistorage.js 0000664 0000000 0000000 00000101222 14230544473 0022012 0 ustar 00root root 0000000 0000000 /*jslint
indent: 2,
maxlen: 80,
plusplus: true,
nomen: true,
regexp: true
*/
/*global
define: true,
exports: true,
require: true,
jIO: true,
jQuery: true,
window: true,
XMLHttpRequest,
FormData
*/
/**
* JIO XWiki Storage. Type = 'xwiki'.
* XWiki Document/Attachment storage.
*/
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(require('jio'), require('jquery'));
}
module(jIO, jQuery);
}([
'jio',
'jquery'
], function (jIO, $) {
"use strict";
function detectWiki() {
// try first the meta tag, then look for js,
// then finally fail over to 'xwiki'...
return $('meta[name="wiki"]').attr('content') ||
(window.XWiki || {}).currentWiki ||
'xwiki';
}
function detectXWikiURL(wiki) {
var loc, action, idx;
loc = window.location.href;
action = (window.XWiki || {}).contextAction || 'view';
idx = loc.indexOf('/wiki/' + wiki + '/' + action + '/');
if (idx !== -1) {
return loc.substring(0, idx);
}
idx = loc.indexOf('/bin/' + action + '/');
if (idx !== -1) {
// single wiki host:port/xwiki/bin/view/Main/WebHome
return loc.substring(0, idx);
}
throw new Error("Unable to detect XWiki URL");
}
/**
* Checks if an object has no enumerable keys
*
* @param {Object} obj The object
* @return {Boolean} true if empty, else false
*/
function objectIsEmpty(obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
}
function detectIsPathBasedMultiwiki(xwikiurl) {
var loc = window.location.href;
if (loc.indexOf(xwikiurl + '/wiki/') === 0) { return true; }
if (loc.indexOf(xwikiurl + '/bin/') === 0) { return false; }
// warn the user that we're unusure?
return false;
}
/**
* The JIO XWikiStorage extension
*
* @class XWikiStorage
* @constructor
*/
function XWikiStorage(spec) {
spec = spec || {};
var priv = {};
// the wiki to store stuff in
priv.wiki = spec.wiki || detectWiki();
// URL location of the wiki
// XWiki doesn't currently allow cross-domain requests.
priv.xwikiurl = (spec.xwikiurl !== undefined)
? spec.xwikiurl : detectXWikiURL(priv.wiki);
// Which URL to load for getting the Anti-CSRF form token, used for testing.
priv.formTokenPath = spec.formTokenPath || priv.xwikiurl;
priv.pathBasedMultiwiki = (spec.pathBasedMultiwiki !== undefined)
? spec.pathBasedMultiwiki : detectIsPathBasedMultiwiki(priv.xwikiurl);
/**
* Get the Space and Page components of a documkent ID.
*
* @param id the document id.
* @return a map of { 'space':, 'page': }
*/
priv.getParts = function (id) {
if (id.indexOf('/') === -1) {
return {
space: 'Main',
page: id
};
}
return {
space: id.substring(0, id.indexOf('/')),
page: id.substring(id.indexOf('/') + 1)
};
};
/**
* Get the Anti-CSRF token and do something with it.
*
* @param andThen function which is called with (formToken, err)
* as parameters.
*/
priv.doWithFormToken = function (andThen) {
$.ajax({
url: priv.formTokenPath,
type: "GET",
async: true,
dataType: 'text',
success: function (html) {
var m, token;
// this is unreliable
//var token = $('meta[name=form_token]', html).attr("content");
m = html.match(//);
token = (m && m[1]) || null;
if (!token) {
andThen(null, {
"status": 404,
"statusText": "Not Found",
"error": "err_form_token_not_found",
"message": "Anti-CSRF form token was not found in page",
"reason": "XWiki main page did not contain expected " +
"Anti-CSRF form token"
});
} else {
andThen(token, null);
}
},
error: function (jqxhr, err, cause) {
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Could not get Anti-CSRF form token from [" +
priv.xwikiurl + "]",
"reason": cause
});
}
});
};
/**
* Get the REST read URL for a document.
*
* @param docId the id of the document.
* @return the REST URL for accessing this document.
*/
priv.getDocRestURL = function (docId) {
var parts = priv.getParts(docId);
return priv.xwikiurl + '/rest/wikis/'
+ priv.wiki + '/spaces/' + parts.space + '/pages/' + parts.page;
};
priv.getURL = function (action, space, page) {
var out = [priv.xwikiurl];
if (!priv.pathBasedMultiwiki) {
out.push('bin');
} else {
out.push('wiki', priv.wiki);
}
out.push(action, space, page);
return out.join('/');
};
/*
* Wrapper for the xwikistorage based on localstorage JiO store.
*/
priv._storage = this._storage = {
/**
* Get content of an XWikiDocument.
*
* @param docId the document ID.
* @param andThen a callback taking (doc, err), doc being the document
* json object and err being the error if any.
*/
getItem: function (docId, andThen) {
function attachSuccess(doc, jqxhr) {
var out = {}, xd;
if (jqxhr.status !== 200) {
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": "",
"message": "Failed to get attachments for document [" +
docId + "]",
"reason": ""
});
return;
}
try {
xd = $(jqxhr.responseText);
xd.find('attachment').each(function () {
var attach = {}, attachXML = $(this);
attachXML.find('mimeType').each(function () {
attach.content_type = $(this).text();
});
attachXML.find('size').each(function () {
attach.length = Number($(this).text());
});
attach.digest = "unknown-0";
attachXML.find('name').each(function () {
out[$(this).text()] = attach;
});
});
doc._attachments = out;
andThen(doc, null);
} catch (err) {
andThen(null, {
status: 500,
statusText: "internal error",
error: err,
message: err.message,
reason: ""
});
}
}
function getAttachments(doc) {
$.ajax({
url: priv.getDocRestURL(docId) + '/attachments',
type: "GET",
async: true,
dataType: 'xml',
complete: function (jqxhr) {
attachSuccess(doc, jqxhr);
}
});
}
function success(jqxhr) {
var out, xd;
out = {};
try {
xd = $(jqxhr.responseText);
xd.find('modified').each(function () {
out._last_modified = Date.parse($(this).text());
});
xd.find('created').each(function () {
out._creation_date = Date.parse($(this).text());
});
xd.find('title').each(function () { out.title = $(this).text(); });
xd.find('parent').each(function () {
out.parent = $(this).text();
});
xd.find('syntax').each(function () {
out.syntax = $(this).text();
});
xd.find('content').each(function () {
out.content = $(this).text();
});
out._id = docId;
getAttachments(out);
} catch (err) {
andThen(null, {
status: 500,
statusText: "internal error",
error: err,
message: err.message,
reason: ""
});
}
}
$.ajax({
url: priv.getDocRestURL(docId),
type: "GET",
async: true,
dataType: 'xml',
// Use complete instead of success and error because phantomjs
// sometimes causes error to be called with http return code 200.
complete: function (jqxhr) {
if (jqxhr.status === 404) {
andThen(null, null);
return;
}
if (jqxhr.status !== 200) {
andThen(null, {
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": "",
"message": "Failed to get document [" + docId + "]",
"reason": ""
});
return;
}
success(jqxhr);
}
});
},
/**
* Get content of an XWikiAttachment.
*
* @param attachId the attachment ID.
* @param andThen a callback taking (attach, err), attach being the
* attachment blob and err being the error if any.
*/
getAttachment: function (docId, fileName, andThen) {
var xhr, parts, url;
// need to do this manually, jquery doesn't support returning blobs.
xhr = new XMLHttpRequest();
parts = priv.getParts(docId);
url = priv.getURL('download', parts.space, parts.page) +
'/' + fileName + '?cb=' + Math.random();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (xhr.status === 200) {
var contentType = xhr.getResponseHeader("Content-Type");
if (contentType.indexOf(';') > -1) {
contentType = contentType.substring(0, contentType.indexOf(';'));
}
andThen(xhr.response);
} else {
andThen(null, {
"status": xhr.status,
"statusText": xhr.statusText,
"error": "err_network_error",
"message": "Failed to get attachment ["
+ docId + "/" + fileName + "]",
"reason": "Error getting data from network"
});
}
};
xhr.send();
},
/**
* Store an XWikiDocument.
*
* @param id the document identifier.
* @param doc the document JSON object containing
* "parent", "title", "content", and/or "syntax" keys.
* @param andThen a callback taking (err), err being the error if any.
*/
setItem: function (id, doc, andThen) {
priv.doWithFormToken(function (formToken, err) {
if (err) {
andThen(err);
return;
}
var parts = priv.getParts(id);
$.ajax({
url: priv.getURL('preview', parts.space, parts.page),
type: "POST",
async: true,
dataType: 'text',
data: {
parent: doc.parent || '',
title: doc.title || '',
xredirect: '',
language: 'en',
// RequiresHTMLConversion: 'content',
// content_syntax: doc.syntax || 'xwiki/2.1',
content: doc.content || '',
xeditaction: 'edit',
comment: 'Saved by JiO',
action_saveandcontinue: 'Save & Continue',
syntaxId: doc.syntax || 'xwiki/2.1',
xhidden: 0,
minorEdit: 0,
ajax: true,
form_token: formToken
},
success: function () {
andThen(null);
},
error: function (jqxhr, err, cause) {
andThen({
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to store document [" + id + "]",
"reason": cause
});
}
});
});
},
/**
* Store an XWikiAttachment.
*
* @param docId the ID of the document to attach to.
* @param fileName the attachment file name.
* @param mimeType the MIME type of the attachment content.
* @param blob the attachment content.
* @param andThen a callback taking one parameter, the error if any.
*/
setAttachment: function (docId, fileName, blob, andThen) {
priv.doWithFormToken(function (formToken, err) {
var parts, fd, xhr;
if (err) {
andThen(err);
return;
}
parts = priv.getParts(docId);
fd = new FormData();
fd.append("filepath", blob, fileName);
fd.append("form_token", formToken);
xhr = new XMLHttpRequest();
xhr.open(
'POST',
priv.getURL('upload', parts.space, parts.page),
true
);
xhr.onload = function () {
if (xhr.status === 302 || xhr.status === 200) {
andThen(null);
} else {
andThen({
"status": xhr.status,
"statusText": xhr.statusText,
"error": "err_network_error",
"message": "Failed to store attachment ["
+ docId + "/" + fileName + "]",
"reason": "Error posting data"
});
}
};
xhr.send(fd);
});
},
removeItem: function (id, andThen) {
priv.doWithFormToken(function (formToken, err) {
if (err) {
andThen(err);
return;
}
var parts = priv.getParts(id);
$.ajax({
url: priv.getURL('delete', parts.space, parts.page),
type: "POST",
async: true,
dataType: 'text',
data: {
confirm: '1',
form_token: formToken
},
success: function () {
andThen(null);
},
error: function (jqxhr, err, cause) {
andThen({
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to delete document [" + id + "]",
"reason": cause
});
}
});
});
},
removeAttachment: function (docId, fileName, andThen) {
var parts = priv.getParts(docId);
priv.doWithFormToken(function (formToken, err) {
if (err) {
andThen(err);
return;
}
$.ajax({
url: priv.getURL('delattachment', parts.space, parts.page) +
'/' + fileName,
type: "POST",
async: true,
dataType: 'text',
data: {
ajax: '1',
form_token: formToken
},
success: function () {
andThen(null);
},
error: function (jqxhr, err, cause) {
andThen({
"status": jqxhr.status,
"statusText": jqxhr.statusText,
"error": err,
"message": "Failed to delete attachment ["
+ docId + '/' + fileName + "]",
"reason": cause
});
}
});
});
},
/**
* Gets a document list from the xwiki storage.
* It will retreive an array containing files meta data owned by
* the user.
* @method allDocs
*/
allDocs: function (includeDocs, andThen) {
var getData = function (callback) {
$.ajax({
url: priv.xwikiurl + '/rest/wikis/xwiki/pages?cb=' + Date.now(),
type: "GET",
async: true,
dataType: 'xml',
success: function (xmlData) {
var data = [];
$(xmlData).find('fullName').each(function () {
data[data.length] = $(this).text();
});
callback(data);
},
error: function (error) {
andThen(null, error);
}
});
};
getData(function (rows, err) {
var i, next;
next = function (i) {
priv._storage.getItem(rows[i].id, function (doc, err) {
if (err) {
andThen(null, err);
return;
}
rows[i].doc = doc;
if (i < rows.length) {
next(i + 1);
} else {
andThen(rows);
}
});
};
if (err) {
return andThen(null, err);
}
for (i = 0; i < rows.length; i++) {
rows[i] = {
id: rows[i],
key: rows[i],
value: {}
};
}
if (includeDocs) {
next(0);
} else {
andThen(rows);
}
});
}
};
}
/**
* Create a document in local storage.
*
* @method post
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/
XWikiStorage.prototype.post = function (command, metadata) {
var doc_id = metadata._id, that = this;
if (!doc_id) {
doc_id = jIO.util.generateUuid();
}
that._storage.getItem(doc_id, function (doc, err) {
if (err) {
command.error(err);
return;
}
if (doc === null) {
// the document does not exist
doc = jIO.util.deepClone(metadata);
doc._id = doc_id;
delete doc._attachments;
that._storage.setItem(doc_id, doc, function (err) {
if (err) {
command.error(
"failed",
"failed to upload document",
String(err)
);
} else {
command.success({"id": doc_id});
}
});
} else {
// the document already exists
command.error(
"conflict",
"document exists",
"Cannot create a new document"
);
}
});
};
/**
* Create or update a document in local storage.
*
* @method put
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to store
* @param {Object} options The command options
*/
XWikiStorage.prototype.put = function (command, metadata) {
var tmp, status, that = this;
that._storage.getItem(metadata._id, function (doc, err) {
if (err) {
command.error(err);
return;
}
if (doc === null || doc === undefined) {
// the document does not exist
doc = jIO.util.deepClone(metadata);
delete doc._attachments;
status = "created";
} else {
// the document already exists
tmp = jIO.util.deepClone(metadata);
tmp._attachments = doc._attachments;
doc = tmp;
status = "no_content";
}
// write
that._storage.setItem(metadata._id, doc, function (err) {
if (err) { command.error(err); return; }
command.success(status);
});
});
};
/**
* Add an attachment to a document
*
* @method putAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.putAttachment = function (command, param) {
var that = this, status = "created";
that._storage.getItem(param._id, function (doc, err) {
if (err) {
return command.error(err);
}
if (doc === null) {
// the document does not exist
return command.error(
"not_found",
"missing",
"Impossible to add attachment"
);
}
// the document already exists
// download data
if ((doc._attachments || {})[param._attachment]) {
status = "no_content";
}
that._storage.setAttachment(param._id,
param._attachment,
param._blob,
function (err) {
if (err) {
command.error(err);
} else {
// XWiki doesn't do digests of attachments
// so we'll calculate it on the client side.
jIO.util.readBlobAsBinaryString(param._blob).then(function (e) {
command.success(status,
{"digest": jIO.util.makeBinaryStringDigest(e.target.result)}
);
});
}
});
});
};
/**
* Get a document
*
* @method get
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.get = function (command, param) {
this._storage.getItem(param._id, function (ret, err) {
if (err) { command.error(err); return; }
if (ret === null) {
command.error(
"not_found",
"missing",
"Cannot find document"
);
} else {
command.success({"data": ret});
}
});
};
/**
* Get an attachment
*
* @method getAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.getAttachment = function (command, param) {
var that = this;
that._storage.getItem(param._id, function (doc, err) {
if (err) {
return command.error(err);
}
if (doc === null) {
return command.error(
"not_found",
"missing document",
"Cannot find document"
);
}
if (typeof doc._attachments !== 'object' ||
typeof doc._attachments[param._attachment] !== 'object') {
return command.error(
"not_found",
"missing attachment",
"Cannot find attachment"
);
}
that._storage.getAttachment(param._id, param._attachment,
function (blob, err) {
var attach = doc._attachments[param._attachment];
if (err) {
return command.error(err);
}
if (blob.size !== attach.length) {
return command.error(
"incomplete",
"attachment size incorrect",
"expected [" + attach.size + "] bytes, got [" + blob.size + "]"
);
}
command.success({
"data": blob,
"digest": attach.digest || "",
"content_type": attach.content_type || ""
});
});
});
};
/**
* Remove a document
*
* @method remove
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.remove = function (command, param) {
this._storage.removeItem(param._id, function (err) {
if (err) {
command.error(err);
} else {
command.success();
}
});
};
/**
* Remove an attachment
*
* @method removeAttachment
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.removeAttachment = function (command, param) {
var that = this;
that._storage.getItem(param._id, function (doc, err) {
if (err) {
return command.error(err);
}
if (typeof doc !== 'object' || doc === null) {
return command.error(
"not_found",
"missing document",
"Document not found"
);
}
if (typeof doc._attachments !== "object" ||
typeof doc._attachments[param._attachment] !== "object") {
return command.error(
"not_found",
"missing attachment",
"Attachment not found"
);
}
that._storage.removeAttachment(param._id, param._attachment,
function (err) {
if (err) {
command.error(err);
} else {
command.success();
}
});
});
};
/**
* Get all filenames belonging to a user from the document index
*
* @method allDocs
* @param {Object} command The JIO command
* @param {Object} param The given parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.allDocs = function (command, param, options) {
var i, document_list, document_object, delete_id, that = this;
param.unused = true;
document_list = [];
if (options.query === undefined && options.sort_on === undefined &&
options.select_list === undefined &&
options.include_docs === undefined) {
that._storage.allDocs(options.include_docs, function (rows, err) {
if (err) {
return command.error(err);
}
command.success({"data": {"rows": rows, "total_rows": rows.length}});
});
} else {
that._storage.allDocs(true, function (rows, err) {
if (err) {
return command.error(err);
}
for (i = 0; i < rows.length; i++) {
document_list.push(rows[i].doc);
}
});
options.select_list = options.select_list || [];
if (options.select_list.indexOf("_id") === -1) {
options.select_list.push("_id");
delete_id = true;
}
if (options.include_docs === true) {
document_object = {};
document_list.forEach(function (meta) {
document_object[meta._id] = meta;
});
}
jIO.QueryFactory.create(options.query || "", this._key_schema).
exec(document_list, options).then(function () {
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (options.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
if (delete_id) {
delete value._id;
}
o.value = value;
return o;
});
command.success({"data": {
"total_rows": document_list.length,
"rows": document_list
}});
});
}
};
/**
* Check the storage or a specific document
*
* @method check
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.check = function (command, param) {
this.genericRepair(command, param, false);
};
/**
* Repair the storage or a specific document
*
* @method repair
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
*/
XWikiStorage.prototype.repair = function (command, param) {
this.genericRepair(command, param, true);
};
/**
* A generic method that manage check or repair command
*
* @method genericRepair
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Boolean} repair If true then repair else just check
*/
XWikiStorage.prototype.genericRepair = function (command, param, repair) {
var that = this, final_result;
function referenceAttachment(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
var i = param.unreferenced_attachments.indexOf(attachment);
if (i !== -1) {
param.unreferenced_attachments.splice(i, 1);
}
param.referenced_attachments[param.referenced_attachments.length] =
attachment;
}
function attachmentFound(param, attachment) {
if (param.referenced_attachments.indexOf(attachment) !== -1) {
return;
}
if (param.unreferenced_attachments.indexOf(attachment) !== -1) {
return;
}
param.unreferenced_attachments[param.unreferenced_attachments.length] =
attachment;
}
function repairOne(param, repair) {
var i, doc, modified;
doc = that._storage.getItem(param._id);
if (doc === null) {
return; // OK
}
// check document type
if (typeof doc !== 'object' || doc === null) {
// wrong document
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Document is unrecoverable"
]};
}
// delete the document
that._storage.removeItem(param._id);
return; // OK
}
// good document type
// repair json document
if (!repair) {
if (!(new jIO.Metadata(doc).check())) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Some metadata might be lost"
]};
}
} else {
modified = jIO.util.uniqueJSONStringify(doc) !==
jIO.util.uniqueJSONStringify(new jIO.Metadata(doc).format()._dict);
}
if (doc._attachments !== undefined) {
if (typeof doc._attachments !== 'object') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachments are unrecoverable"
]};
}
delete doc._attachments;
that._storage.setItem(param._id, doc);
return; // OK
}
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
// check attachment existence
if (that._storage.getItem(param._id + "/" + i) !== 'string') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"missing attachment",
"Attachment \"" + i + "\" of \"" + param._id + "\" is missing"
]};
}
delete doc._attachments[i];
if (objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
modified = true;
} else {
// attachment exists
// check attachment metadata
// check length
referenceAttachment(param, param._id + "/" + doc._attachments[i]);
if (doc._attachments[i].length !== undefined &&
typeof doc._attachments[i].length !== 'number') {
if (!repair) {
return {"error": true, "answers": [
"conflict",
"corrupted",
"Attachment metadata length corrupted"
]};
}
// It could take a long time to get the length, no repair.
// length can be omited
delete doc._attachments[i].length;
}
// It could take a long time to regenerate the hash, no check.
// Impossible to discover the attachment content type.
}
}
}
}
if (modified) {
that._storage.setItem(param._id, doc);
}
// OK
}
function repairAll(param, repair) {
var i, result;
for (i in that._database) {
if (that._database.hasOwnProperty(i)) {
// browsing every entry
// is part of the user space
if (/^[^\/]+\/[^\/]+$/.test(i)) {
// this is an attachment
attachmentFound(param, i);
} else if (/^[^\/]+$/.test(i)) {
// this is a document
param._id = i;
result = repairOne(param, repair);
if (result) {
return result;
}
} else {
// this is pollution
that._storage.removeItem(i);
}
}
}
// remove unreferenced attachments
for (i = 0; i < param.unreferenced_attachments.length; i += 1) {
that._storage.removeItem(param.unreferenced_attachments[i]);
}
}
param.referenced_attachments = [];
param.unreferenced_attachments = [];
if (typeof param._id === 'string') {
final_result = repairOne(param, repair) || {};
} else {
final_result = repairAll(param, repair) || {};
}
if (final_result.error) {
return command.error.apply(command, final_result.answers || []);
}
command.success.apply(command, final_result.answers || []);
};
jIO.addStorage('xwiki', XWikiStorage);
}));
jio-master-src/src/jio.storage/zipstorage.js 0000664 0000000 0000000 00000010472 14230544473 0021467 0 ustar 00root root 0000000 0000000 /*
* Copyright 2015, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*jslint nomen: true*/
/*global RSVP, Blob, LZString, DOMException*/
(function (RSVP, Blob, LZString, DOMException) {
"use strict";
/**
* The jIO ZipStorage extension
*
* @class ZipStorage
* @constructor
*/
var MIME_TYPE = "application/x-jio-utf16_lz_string";
function ZipStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
}
ZipStorage.prototype.get = function () {
return this._sub_storage.get.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.post = function () {
return this._sub_storage.post.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.put = function () {
return this._sub_storage.put.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.remove = function () {
return this._sub_storage.remove.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.hasCapacity = function () {
return this._sub_storage.hasCapacity.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.buildQuery = function () {
return this._sub_storage.buildQuery.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.getAttachment = function (id, name) {
var that = this;
return that._sub_storage.getAttachment(id, name)
.push(function (blob) {
if (blob.type !== MIME_TYPE) {
return blob;
}
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsText(blob, 'utf16');
})
.push(function (evt) {
var result =
LZString.decompressFromUTF16(evt.target.result);
if (result === '') {
return blob;
}
try {
return jIO.util.dataURItoBlob(
result
);
} catch (error) {
if (error instanceof DOMException) {
return blob;
}
throw error;
}
});
});
};
function myEndsWith(str, query) {
return (str.indexOf(query) === str.length - query.length);
}
ZipStorage.prototype.putAttachment = function (id, name, blob) {
var that = this;
if ((blob.type.indexOf("text/") === 0) || myEndsWith(blob.type, "xml") ||
myEndsWith(blob.type, "json")) {
return new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (data) {
var result = LZString.compressToUTF16(data.target.result);
blob = new Blob([result],
{type: MIME_TYPE});
return that._sub_storage.putAttachment(id, name, blob);
});
}
return this._sub_storage.putAttachment.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.removeAttachment = function () {
return this._sub_storage.removeAttachment.apply(this._sub_storage,
arguments);
};
ZipStorage.prototype.allAttachments = function () {
return this._sub_storage.allAttachments.apply(this._sub_storage,
arguments);
};
jIO.addStorage('zip', ZipStorage);
}(RSVP, Blob, LZString, DOMException));
jio-master-src/src/node/ 0000775 0000000 0000000 00000000000 14230544473 0015437 5 ustar 00root root 0000000 0000000 jio-master-src/src/node/jio-compat.js 0000664 0000000 0000000 00000013644 14230544473 0020047 0 ustar 00root root 0000000 0000000 /*
* Copyright 2018, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*global window, WeakMap, ArrayBuffer, Uint8Array */
(function (window, WeakMap, ArrayBuffer, Uint8Array) {
"use strict";
var html5weakmap = new WeakMap();
function EventTarget() {
html5weakmap.set(this, Object.create(null));
}
EventTarget.prototype.addEventListener = function (type, listener) {
if (typeof listener !== 'function') {
return;
}
var em = html5weakmap.get(this);
type = type.toString();
if (em[type]) {
em[type].push(listener);
} else {
em[type] = [listener];
}
};
EventTarget.prototype.removeEventListener = function (type, listener) {
if (typeof listener !== 'function') {
return;
}
var em = html5weakmap.get(this),
i,
listeners = em[type];
type = type.toString();
if (listeners) {
for (i = 0; i < listeners.length; i += 1) {
if (listeners[i] === listener) {
if (listeners.length === 1) {
delete em[type];
return;
}
listeners.splice(i, 1);
return;
}
}
}
};
EventTarget.prototype.dispatchEvent = function (event) {
var type = event.type.toString(),
em = html5weakmap.get(this),
ontype = 'on' + type,
i,
listeners;
if (typeof this[ontype] === 'function') {
try {
this[ontype](event);
} catch (ignore) {}
}
listeners = em[type];
if (listeners) {
for (i = 0; i < listeners.length; i += 1) {
try {
listeners[i](event);
} catch (ignore) {}
}
}
};
window.EventTarget = window.EventTarget || EventTarget;
function Blob(blobParts, options) {
// https://developer.mozilla.org/en-US/docs/Web/API/Blob
var i,
priv = {},
buffers = [];
html5weakmap.set(this, priv);
if (blobParts) {
for (i = 0; i < blobParts.length; i += 1) {
if (Buffer.isBuffer(blobParts[i])) {
buffers.push(blobParts[i]);
} else if (blobParts[i] instanceof Blob) {
buffers.push(html5weakmap.get(blobParts[i]).data);
} else if (blobParts[i] instanceof ArrayBuffer) {
buffers.push(new Buffer(new Uint8Array(blobParts[i])));
} else {
buffers.push(new Buffer(String(blobParts[i])));
}
}
}
priv.data = Buffer.concat(buffers);
Object.defineProperty(this, 'size', {
enumerable: true,
value: priv.data.length
});
Object.defineProperty(this, 'type', {
enumerable: true,
value: options ? String(options.type || '') : ''
});
}
Blob.prototype.size = 0;
Blob.prototype.type = '';
Blob.prototype.slice = function (start, end, contentType) {
return new Blob([html5weakmap.get(this).data.slice(start, end)], {
type: contentType
});
};
window.Blob = Blob;//window.Blob || Blob;
function FileReader() {
EventTarget.call(this);
}
FileReader.prototype = Object.create(EventTarget.prototype);
Object.defineProperty(FileReader, 'constructor', {
value: FileReader
});
FileReader.prototype.readAsText = function (blob) {
var target = this,
priv = html5weakmap.get(blob),
result = priv.data.toString(),
event = Object.freeze({
type: 'load',
target: target
});
process.nextTick(function () {
target.result = result;
target.dispatchEvent(event);
});
};
FileReader.prototype.readAsArrayBuffer = function (blob) {
var target = this,
priv = html5weakmap.get(blob),
result = new Uint8Array(priv.data).buffer,
event = Object.freeze({
type: 'load',
target: target
});
process.nextTick(function () {
target.result = result;
target.dispatchEvent(event);
});
};
FileReader.prototype.readAsDataURL = function (blob) {
var target = this,
priv = html5weakmap.get(blob),
result = 'data:' + blob.type + ';base64,' + priv.data.toString('base64'),
event = Object.freeze({
type: 'load',
target: target
});
process.nextTick(function () {
target.result = result;
target.dispatchEvent(event);
});
};
window.FileReader = window.FileReader || FileReader;
function atob(str) {
try {
return window.atob(str);
} catch (err) {
var buffer = Buffer.from(str.toString(), 'base64');
// Provide the same behaviour than the browser atob
if (buffer.toString('base64') !== str) {
throw new Error('The string to be decoded is not correctly encoded.');
}
return buffer.toString('binary');
}
}
window.atob = window.atob || atob;
function btoa(str) {
try {
return window.btoa(str);
} catch (err) {
return Buffer.from(str.toString(), 'binary').toString('base64');
}
}
window.btoa = window.btoa || btoa;
}(window, WeakMap, ArrayBuffer, Uint8Array));
global.XMLHttpRequest = module.exports;
var Blob = window.Blob,
atob = window.atob,
btoa = window.btoa,
FileReader = window.FileReader,
QueryFactory = window.QueryFactory,
Query = window.Query,
SimpleQuery = window.SimpleQuery,
ComplexQuery = window.ComplexQuery;
jio-master-src/src/node/jio-end.js 0000664 0000000 0000000 00000000124 14230544473 0017317 0 ustar 00root root 0000000 0000000
module = node_module;
jIO.node_env = window;
module.exports = jIO;
} ({})); jio-master-src/src/node/jio-external.js 0000664 0000000 0000000 00000000257 14230544473 0020402 0 ustar 00root root 0000000 0000000 var RSVP = window.RSVP,
moment = global.moment,
UriTemplate = window.UriTemplate,
Rusha = window.Rusha;
// Allow xhr2 to export XMLHttpRequest
module = {};
jio-master-src/src/node/jio-start.js 0000664 0000000 0000000 00000000170 14230544473 0017707 0 ustar 00root root 0000000 0000000 (function (define, exports) {
var navigator = null,
window = {},
node_module = module;
module = undefined;
jio-master-src/src/node/jio.js 0000664 0000000 0000000 00000007307 14230544473 0016565 0 ustar 00root root 0000000 0000000 /*
* Copyright 2018, Nexedi SA
*
* This program is free software: you can Use, Study, Modify and Redistribute
* it under the terms of the GNU General Public License version 3, or (at your
* option) any later version, as published by the Free Software Foundation.
*
* You can also Link and Combine this program with other software covered by
* the terms of any of the Free Software licenses or any of the Open Source
* Initiative approved licenses and Convey the resulting work. Corresponding
* source of such a combination shall include the source code for all other
* software used.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See COPYING file for full licensing terms.
* See https://www.nexedi.com/licensing for rationale and options.
*/
/*global window */
(function (window, jIO, Blob, RSVP) {
"use strict";
var FormData,
originalAjax;
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/
// Using_XMLHttpRequest#Submitting_forms_and_uploading_files
FormData = function FormData() {
this.boundary = "---------------------------" + Date.now().toString(16);
this.body = '';
};
FormData.prototype.append = function (name, value, filename) {
this.body += '--' + this.boundary +
'\r\nContent-Disposition: form-data; name="' + name;
if (filename !== undefined) {
this.body += '"; filename="' + filename;
}
this.body += '"\r\n\r\n' + value + '\r\n';
};
window.FormData = FormData;
function convertToBlob(promise, convert) {
if (!convert) {
return promise;
}
var result;
if (promise instanceof RSVP.Queue) {
result = promise;
} else {
result = new RSVP.Queue()
.push(function () {
return promise;
});
}
return result
.push(function (evt) {
evt.target.response = new Blob(
[evt.target.response || evt.target.responseText],
{type: evt.target.getResponseHeader('Content-Type')}
);
return evt;
});
}
originalAjax = jIO.util.ajax;
jIO.util.ajax = function ajax(param) {
var result,
need_convertion = (param.dataType === 'blob');
// Copy the param dict document (no need for deep copy) to
// allow tests to check them
param = Object.assign({}, param);
if (need_convertion) {
param.dataType = 'arraybuffer';
}
if (param.data instanceof Blob) {
// Blob is not supported by xhr2, so convert to ArrayBuffer instead
result = new RSVP.Queue()
.push(function () {
return jIO.util.readBlobAsArrayBuffer(param.data);
})
.push(function (evt) {
param.data = evt.target.result;
return originalAjax(param);
});
} else if (param.data instanceof FormData) {
// Implement minimal FormData for erp5storage
if (!param.hasOwnProperty('headers')) {
param.headers = {};
} else {
// Copy the param dict document (no need for deep copy) to
// allow tests to check them
param.headers = Object.assign({}, param.headers);
}
param.headers["Content-Type"] = "multipart\/form-data; boundary=" +
param.data.boundary;
param.data.body += '--' + param.data.boundary + '--\r\n';
param.data = param.data.body;
result = originalAjax(param);
} else {
result = originalAjax(param);
}
return convertToBlob(result, need_convertion);
};
}(window, window.jIO, window.Blob, window.RSVP));
// Define a global variable to allow storages to access jIO
var jIO = window.jIO,
FormData = window.FormData,
jiodate = window.jiodate;
jio-master-src/src/queries/ 0000775 0000000 0000000 00000000000 14230544473 0016167 5 ustar 00root root 0000000 0000000 jio-master-src/src/queries/build/ 0000775 0000000 0000000 00000000000 14230544473 0017266 5 ustar 00root root 0000000 0000000 jio-master-src/src/queries/build/parser.js 0000664 0000000 0000000 00000054472 14230544473 0021134 0 ustar 00root root 0000000 0000000 /* parser generated by jison 0.4.16 */
/*
Returns a Parser object of the following structure:
Parser: {
yy: {}
}
Parser.prototype: {
yy: {},
trace: function(),
symbols_: {associative list: name ==> number},
terminals_: {associative list: number ==> name},
productions_: [...],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
table: [...],
defaultActions: {...},
parseError: function(str, hash),
parse: function(input),
lexer: {
EOF: 1,
parseError: function(str, hash),
setInput: function(input),
input: function(),
unput: function(str),
more: function(),
less: function(n),
pastInput: function(),
upcomingInput: function(),
showPosition: function(),
test_match: function(regex_match_array, rule_index),
next: function(),
lex: function(),
begin: function(condition),
popState: function(),
_currentRules: function(),
topState: function(),
pushState: function(condition),
options: {
ranges: boolean (optional: true ==> token location info will include a .range[] member)
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
rules: [...],
conditions: {associative list: name ==> set},
}
}
token location info (@$, _$, etc.): {
first_line: n,
last_line: n,
first_column: n,
last_column: n,
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
}
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
text: (matched text)
token: (the produced terminal token, if any)
line: (yylineno)
}
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
loc: (yylloc)
expected: (string describing the set of expected tokens)
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
}
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,7],$V2=[1,8],$V3=[1,10],$V4=[1,12],$V5=[1,6,7,15],$V6=[1,6,7,9,12,14,15,16,19,21],$V7=[1,6,7,9,11,12,14,15,16,19,21],$V8=[2,17];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"begin":3,"search_text":4,"end":5,"EOF":6,"NEWLINE":7,"and_expression":8,"OR":9,"boolean_expression":10,"AND":11,"NOT":12,"expression":13,"LEFT_PARENTHESE":14,"RIGHT_PARENTHESE":15,"WORD":16,"DEFINITION":17,"value":18,"OPERATOR":19,"string":20,"QUOTE":21,"QUOTED_STRING":22,"$accept":0,"$end":1},
terminals_: {2:"error",6:"EOF",7:"NEWLINE",9:"OR",11:"AND",12:"NOT",14:"LEFT_PARENTHESE",15:"RIGHT_PARENTHESE",16:"WORD",17:"DEFINITION",19:"OPERATOR",21:"QUOTE",22:"QUOTED_STRING"},
productions_: [0,[3,2],[5,0],[5,1],[5,1],[4,1],[4,2],[4,3],[8,1],[8,3],[10,2],[10,1],[13,3],[13,3],[13,1],[18,2],[18,1],[20,1],[20,3]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 1:
return $$[$0-1];
break;
case 5: case 8: case 11: case 14: case 16:
this.$ = $$[$0];
break;
case 6:
this.$ = mkComplexQuery('AND', [$$[$0-1], $$[$0]]);
break;
case 7:
this.$ = mkComplexQuery('OR', [$$[$0-2], $$[$0]]);
break;
case 9:
this.$ = mkComplexQuery('AND', [$$[$0-2], $$[$0]]);
break;
case 10:
this.$ = mkNotQuery($$[$0]);
break;
case 12:
this.$ = $$[$0-1];
break;
case 13:
querySetKey($$[$0], $$[$0-2]); this.$ = $$[$0];
break;
case 15:
$$[$0].operator = $$[$0-1] ; this.$ = $$[$0];
break;
case 17:
this.$ = mkSimpleQuery('', $$[$0]);
break;
case 18:
this.$ = mkSimpleQuery('', parseQuotedString($$[$0-1]));
break;
}
},
table: [{3:1,4:2,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{1:[3]},{1:[2,2],5:13,6:[1,14],7:[1,15]},o($V5,[2,5],{8:3,10:4,13:6,18:9,20:11,4:16,9:[1,17],12:$V0,14:$V1,16:$V2,19:$V3,21:$V4}),o($V6,[2,8],{11:[1,18]}),{13:19,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,11]),{4:20,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,$V8,{17:[1,21]}),o($V7,[2,14]),{16:[1,23],20:22,21:$V4},o($V7,[2,16]),{22:[1,24]},{1:[2,1]},{1:[2,3]},{1:[2,4]},o($V5,[2,6]),{4:25,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{8:26,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,10]),{15:[1,27]},{13:28,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,15]),o($V7,$V8),{21:[1,29]},o($V5,[2,7]),o($V6,[2,9]),o($V7,[2,12]),o($V7,[2,13]),o($V7,[2,18])],
defaultActions: {13:[2,1],14:[2,3],15:[2,4]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);
} else {
function _parseError (msg, hash) {
this.message = msg;
this.hash = hash;
}
_parseError.prototype = new Error();
throw new _parseError(str, hash);
}
},
parse: function parse(input) {
var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
var args = lstack.slice.call(arguments, 1);
var lexer = Object.create(this.lexer);
var sharedState = { yy: {} };
for (var k in this.yy) {
if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
sharedState.yy[k] = this.yy[k];
}
}
lexer.setInput(input, sharedState.yy);
sharedState.yy.lexer = lexer;
sharedState.yy.parser = this;
if (typeof lexer.yylloc == 'undefined') {
lexer.yylloc = {};
}
var yyloc = lexer.yylloc;
lstack.push(yyloc);
var ranges = lexer.options && lexer.options.ranges;
if (typeof sharedState.yy.parseError === 'function') {
this.parseError = sharedState.yy.parseError;
} else {
this.parseError = Object.getPrototypeOf(this).parseError;
}
function popStack(n) {
stack.length = stack.length - 2 * n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
_token_stack:
var lex = function () {
var token;
token = lexer.lex() || EOF;
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
};
var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
while (true) {
state = stack[stack.length - 1];
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol === null || typeof symbol == 'undefined') {
symbol = lex();
}
action = table[state] && table[state][symbol];
}
if (typeof action === 'undefined' || !action.length || !action[0]) {
var errStr = '';
expected = [];
for (p in table[state]) {
if (this.terminals_[p] && p > TERROR) {
expected.push('\'' + this.terminals_[p] + '\'');
}
}
if (lexer.showPosition) {
errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
} else {
errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
}
this.parseError(errStr, {
text: lexer.match,
token: this.terminals_[symbol] || symbol,
line: lexer.yylineno,
loc: yyloc,
expected: expected
});
}
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
}
switch (action[0]) {
case 1:
stack.push(symbol);
vstack.push(lexer.yytext);
lstack.push(lexer.yylloc);
stack.push(action[1]);
symbol = null;
if (!preErrorSymbol) {
yyleng = lexer.yyleng;
yytext = lexer.yytext;
yylineno = lexer.yylineno;
yyloc = lexer.yylloc;
if (recovering > 0) {
recovering--;
}
} else {
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2:
len = this.productions_[action[1]][1];
yyval.$ = vstack[vstack.length - len];
yyval._$ = {
first_line: lstack[lstack.length - (len || 1)].first_line,
last_line: lstack[lstack.length - 1].last_line,
first_column: lstack[lstack.length - (len || 1)].first_column,
last_column: lstack[lstack.length - 1].last_column
};
if (ranges) {
yyval._$.range = [
lstack[lstack.length - (len || 1)].range[0],
lstack[lstack.length - 1].range[1]
];
}
r = this.performAction.apply(yyval, [
yytext,
yyleng,
yylineno,
sharedState.yy,
action[1],
vstack,
lstack
].concat(args));
if (typeof r !== 'undefined') {
return r;
}
if (len) {
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
lstack = lstack.slice(0, -1 * len);
}
stack.push(this.productions_[action[1]][0]);
vstack.push(yyval.$);
lstack.push(yyval._$);
newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
stack.push(newState);
break;
case 3:
return true;
}
}
return true;
}};
/* generated by jison-lex 0.3.4 */
var lexer = (function(){
var lexer = ({
EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parser) {
this.yy.parser.parseError(str, hash);
} else {
throw new Error(str);
}
},
// resets the lexer, sets new input
setInput:function (input, yy) {
this.yy = yy || this.yy || {};
this._input = input;
this._more = this._backtrack = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 0
};
if (this.options.ranges) {
this.yylloc.range = [0,0];
}
this.offset = 0;
return this;
},
// consumes and returns one char from the input
input:function () {
var ch = this._input[0];
this.yytext += ch;
this.yyleng++;
this.offset++;
this.match += ch;
this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno++;
this.yylloc.last_line++;
} else {
this.yylloc.last_column++;
}
if (this.options.ranges) {
this.yylloc.range[1]++;
}
this._input = this._input.slice(1);
return ch;
},
// unshifts one char (or a string) into the input
unput:function (ch) {
var len = ch.length;
var lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input;
this.yytext = this.yytext.substr(0, this.yytext.length - len);
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length - 1);
this.matched = this.matched.substr(0, this.matched.length - 1);
if (lines.length - 1) {
this.yylineno -= lines.length - 1;
}
var r = this.yylloc.range;
this.yylloc = {
first_line: this.yylloc.first_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.first_column,
last_column: lines ?
(lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ oldLines[oldLines.length - lines.length].length - lines[0].length :
this.yylloc.first_column - len
};
if (this.options.ranges) {
this.yylloc.range = [r[0], r[0] + this.yyleng - len];
}
this.yyleng = this.yytext.length;
return this;
},
// When called from action, caches matched text and appends it on next action
more:function () {
this._more = true;
return this;
},
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject:function () {
if (this.options.backtrack_lexer) {
this._backtrack = true;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
return this;
},
// retain first n characters of the match
less:function (n) {
this.unput(this.match.slice(n));
},
// displays already matched input, i.e. for error messages
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
// displays upcoming input, i.e. for error messages
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
},
// displays the character position where the lexing error occurred, i.e. for error messages
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c + "^";
},
// test the lexed token: return FALSE when not a match, otherwise return token
test_match:function (match, indexed_rule) {
var token,
lines,
backup;
if (this.options.backtrack_lexer) {
// save context
backup = {
yylineno: this.yylineno,
yylloc: {
first_line: this.yylloc.first_line,
last_line: this.last_line,
first_column: this.yylloc.first_column,
last_column: this.yylloc.last_column
},
yytext: this.yytext,
match: this.match,
matches: this.matches,
matched: this.matched,
yyleng: this.yyleng,
offset: this.offset,
_more: this._more,
_input: this._input,
yy: this.yy,
conditionStack: this.conditionStack.slice(0),
done: this.done
};
if (this.options.ranges) {
backup.yylloc.range = this.yylloc.range.slice(0);
}
}
lines = match[0].match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno += lines.length;
}
this.yylloc = {
first_line: this.yylloc.last_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.last_column,
last_column: lines ?
lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
this.yylloc.last_column + match[0].length
};
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
if (this.options.ranges) {
this.yylloc.range = [this.offset, this.offset += this.yyleng];
}
this._more = false;
this._backtrack = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
if (this.done && this._input) {
this.done = false;
}
if (token) {
return token;
} else if (this._backtrack) {
// recover context
for (var k in backup) {
this[k] = backup[k];
}
return false; // rule action called reject() implying the next rule should be tested instead.
}
return false;
},
// return next match in input
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) {
this.done = true;
}
var token,
match,
tempMatch,
index;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i = 0; i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (this.options.backtrack_lexer) {
token = this.test_match(tempMatch, rules[i]);
if (token !== false) {
return token;
} else if (this._backtrack) {
match = false;
continue; // rule action called reject() implying a rule MISmatch.
} else {
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
} else if (!this.options.flex) {
break;
}
}
}
if (match) {
token = this.test_match(match, rules[index]);
if (token !== false) {
return token;
}
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
if (this._input === "") {
return this.EOF;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
},
// return next match that has a token
lex:function lex() {
var r = this.next();
if (r) {
return r;
} else {
return this.lex();
}
},
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin:function begin(condition) {
this.conditionStack.push(condition);
},
// pop the previously active lexer condition state off the condition stack
popState:function popState() {
var n = this.conditionStack.length - 1;
if (n > 0) {
return this.conditionStack.pop();
} else {
return this.conditionStack[0];
}
},
// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules:function _currentRules() {
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
} else {
return this.conditions["INITIAL"].rules;
}
},
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState:function topState(n) {
n = this.conditionStack.length - 1 - Math.abs(n || 0);
if (n >= 0) {
return this.conditionStack[n];
} else {
return "INITIAL";
}
},
// alias for begin(condition)
pushState:function pushState(condition) {
this.begin(condition);
},
// return the number of states currently on the stack
stateStackSize:function stateStackSize() {
return this.conditionStack.length;
},
options: {},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:this.begin("letsquote"); return "QUOTE";
break;
case 1:this.popState(); this.begin("endquote"); return "QUOTED_STRING";
break;
case 2:this.popState(); return "QUOTE";
break;
case 3:/* skip whitespace */
break;
case 4:return "LEFT_PARENTHESE";
break;
case 5:return "RIGHT_PARENTHESE";
break;
case 6:return "AND";
break;
case 7:return "OR";
break;
case 8:return "NOT";
break;
case 9:return "DEFINITION";
break;
case 10:return 19;
break;
case 11:return 16;
break;
case 12:return 6;
break;
}
},
rules: [/^(?:")/,/^(?:(\\"|[^"])*)/,/^(?:")/,/^(?:[^\S]+)/,/^(?:\()/,/^(?:\))/,/^(?:AND\b)/,/^(?:OR\b)/,/^(?:NOT\b)/,/^(?::)/,/^(?:(!?=|<=?|>=?))/,/^(?:[^\s\n"():>