Commit e6c460b4 authored by Romain Courteaud's avatar Romain Courteaud

Release version 3.6.0

Include new google drive storage and performance improvement for QueryStorage's allDocs.
parent d005d845
...@@ -3889,229 +3889,6 @@ if (typeof define === 'function' && define.amd) { ...@@ -3889,229 +3889,6 @@ if (typeof define === 'function' && define.amd) {
makeGlobal(); makeGlobal();
} }
}).call(this); }).call(this);
;/*jslint indent: 2, maxlen: 80, sloppy: true */
var query_class_dict = {};
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, window, stringEscapeRegexpCharacters: true,
deepClone, RSVP*/
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseEnd = emptyFunction;
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
var i, promises = [];
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
for (i = 0; i < item_list.length; i += 1) {
if (!item_list[i]) {
promises.push(RSVP.resolve(false));
} else {
promises.push(this.match(item_list[i]));
}
}
return new RSVP.Queue()
.push(function () {
return RSVP.all(promises);
})
.push(function (answers) {
var j;
for (j = answers.length - 1; j >= 0; j -= 1) {
if (!answers[j]) {
item_list.splice(j, 1);
}
}
if (option.sort_on) {
return sortOn(option.sort_on, item_list);
}
})
.push(function () {
if (option.limit) {
return limit(option.limit, item_list);
}
})
.push(function () {
return select(option.select_list || [], item_list);
})
.push(function () {
return item_list;
});
};
/**
* Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function () {
return RSVP.resolve(true);
};
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
Query.prototype.parse = function (option) {
var that = this,
object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var query = object.parsed,
queue = new RSVP.Queue(),
i;
function enqueue(j) {
queue
.push(function () {
object.parsed = query.query_list[j];
return recParse(object, option);
})
.push(function () {
query.query_list[j] = object.parsed;
});
}
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
enqueue(i);
}
return queue
.push(function () {
object.parsed = query;
return that.onParseComplexQuery(object, option);
});
}
if (query.type === "simple") {
return that.onParseSimpleQuery(object, option);
}
}
object = {
parsed: JSON.parse(JSON.stringify(that.serialized()))
};
return new RSVP.Queue()
.push(function () {
return that.onParseStart(object, option);
})
.push(function () {
return recParse(object, option);
})
.push(function () {
return that.onParseEnd(object, option);
})
.push(function () {
return object.parsed;
});
};
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
Query.prototype.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
window.Query = Query;
;/** ;/**
* Parse a text request to a json query object tree * Parse a text request to a json query object tree
* *
...@@ -4815,72 +4592,486 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) ...@@ -4815,72 +4592,486 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
; return result; ; return result;
} // parseStringToObject } // parseStringToObject
Query.parseStringToObject = parseStringToObject; ;/*global RSVP, window, parseStringToObject*/
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ /*jslint nomen: true, maxlen: 90*/
/*global Query: true, query_class_dict: true, inherits: true, (function (RSVP, window, parseStringToObject) {
window, QueryFactory, RSVP */ "use strict";
/** var query_class_dict = {},
* The ComplexQuery inherits from Query, and compares one or several metadata regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g,
* values. regexp_percent = /%/g,
* regexp_underscore = /_/g,
* @class ComplexQuery regexp_operator = /^(?:AND|OR|NOT)$/i,
* @extends Query regexp_comparaison = /^(?:!?=|<=?|>=?)$/i;
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec, key_schema) {
Query.call(this);
/** /**
* Logical operator to use to compare object values * Convert metadata values to array of strings. ex:
* *
* @attribute operator * "a" -> ["a"],
* @type String * {"content": "a"} -> ["a"]
* @default "AND" *
* @optional * @param {Any} value The metadata value
* @return {Array} The value in string array format
*/ */
this.operator = spec.operator; function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
/** /**
* The sub Query list which are used to query an item. * A sort function to sort items by key
* *
* @attribute query_list * @param {String} key The key to sort on
* @type Array * @param {String} [way="ascending"] 'ascending' or 'descending'
* @default [] * @return {Function} The sort function
* @optional
*/
this.query_list = spec.query_list || [];
/*jslint unparam: true*/
this.query_list = this.query_list.map(
// decorate the map to avoid sending the index as key_schema argument
function (o, i) { return QueryFactory.create(o, key_schema); }
);
/*jslint unparam: false*/
}
inherits(ComplexQuery, Query);
ComplexQuery.prototype.operator = "AND";
ComplexQuery.prototype.type = "complex";
/**
* #crossLink "Query/match:method"
*/ */
ComplexQuery.prototype.match = function (item) { function sortFunction(key, way) {
var operator = this.operator; var result;
if (!(/^(?:AND|OR|NOT)$/i.test(operator))) { if (way === 'descending') {
operator = "AND"; result = 1;
} } else if (way === 'ascending') {
return this[operator.toUpperCase()](item); result = -1;
}; } else {
throw new TypeError("Query.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return result;
}
if (b[i] === undefined) {
return -result;
}
if (a[i] > b[i]) {
return -result;
}
if (a[i] < b[i]) {
return result;
}
}
return 0;
};
}
/** /**
* Sort a list of items, according to keys and directions.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("jioquery.sortOn(): " +
"Argument 1 is not of type 'array'");
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
/**
* Limit a list of items, according to index and length.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @return {Array} The filtered list
*/
function limit(limit_option, list) {
if (!Array.isArray(limit_option)) {
throw new TypeError("jioquery.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.limit(): " +
"Argument 2 is not of type 'array'");
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
/**
* Filter a list of items, modifying them to select only wanted keys.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @return {Array} The filtered list
*/
function select(select_option, list) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("jioquery.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.select(): " +
"Argument 2 is not of type Array");
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
if (list[i].hasOwnProperty([select_option[j]])) {
new_item[select_option[j]] = list[i][select_option[j]];
}
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
return list;
}
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseEnd = emptyFunction;
return;
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
var context = this,
i;
for (i = item_list.length - 1; i >= 0; i -= 1) {
if (!context.match(item_list[i])) {
item_list.splice(i, 1);
}
}
if (option.sort_on) {
sortOn(option.sort_on, item_list);
}
if (option.limit) {
limit(option.limit, item_list);
}
select(option.select_list || [], item_list);
return new RSVP.Queue()
.push(function () {
return item_list;
});
};
/**
* Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function () {
return true;
};
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
Query.prototype.parse = function (option) {
var that = this,
object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var query = object.parsed,
queue = new RSVP.Queue(),
i;
function enqueue(j) {
queue
.push(function () {
object.parsed = query.query_list[j];
return recParse(object, option);
})
.push(function () {
query.query_list[j] = object.parsed;
});
}
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
enqueue(i);
}
return queue
.push(function () {
object.parsed = query;
return that.onParseComplexQuery(object, option);
});
}
if (query.type === "simple") {
return that.onParseSimpleQuery(object, option);
}
}
object = {
parsed: JSON.parse(JSON.stringify(that.serialized()))
};
return new RSVP.Queue()
.push(function () {
return that.onParseStart(object, option);
})
.push(function () {
return recParse(object, option);
})
.push(function () {
return that.onParseEnd(object, option);
})
.push(function () {
return object.parsed;
});
};
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
Query.prototype.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {
return;
}
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
return string.replace(regexp_escape, "\\$&");
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
}
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_"
* @return {RegExp} The search text regexp
*/
function searchTextToRegExp(string, use_wildcard_characters) {
if (typeof string !== 'string') {
throw new TypeError("jioquery.searchTextToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (use_wildcard_characters === false) {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string)
.replace(regexp_percent, '.*')
.replace(regexp_underscore, '.') + "$");
}
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec, key_schema) {
Query.call(this);
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator;
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(
// decorate the map to avoid sending the index as key_schema argument
function (o) { return QueryFactory.create(o, key_schema); }
);
}
inherits(ComplexQuery, Query);
ComplexQuery.prototype.operator = "AND";
ComplexQuery.prototype.type = "complex";
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item) {
var operator = this.operator;
if (!(regexp_operator.test(operator))) {
operator = "AND";
}
return this[operator.toUpperCase()](item);
};
/**
* #crossLink "Query/toString:method" * #crossLink "Query/toString:method"
*/ */
ComplexQuery.prototype.toString = function () { ComplexQuery.prototype.toString = function () {
var str_list = [], this_operator = this.operator; var str_list = [], this_operator = this.operator;
if (this.operator === "NOT") { if (this.operator === "NOT") {
str_list.push("NOT ("); str_list.push("NOT (");
...@@ -4896,12 +5087,12 @@ ComplexQuery.prototype.toString = function () { ...@@ -4896,12 +5087,12 @@ ComplexQuery.prototype.toString = function () {
}); });
str_list.length -= 1; str_list.length -= 1;
return str_list.join(" "); return str_list.join(" ");
}; };
/** /**
* #crossLink "Query/serialized:method" * #crossLink "Query/serialized:method"
*/ */
ComplexQuery.prototype.serialized = function () { ComplexQuery.prototype.serialized = function () {
var s = { var s = {
"type": "complex", "type": "complex",
"operator": this.operator, "operator": this.operator,
...@@ -4913,10 +5104,10 @@ ComplexQuery.prototype.serialized = function () { ...@@ -4913,10 +5104,10 @@ ComplexQuery.prototype.serialized = function () {
); );
}); });
return s; return s;
}; };
ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized; ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
/** /**
* Comparison operator, test if all sub queries match the * Comparison operator, test if all sub queries match the
* item value * item value
* *
...@@ -4924,34 +5115,19 @@ ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized; ...@@ -4924,34 +5115,19 @@ ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
* @param {Object} item The item to match * @param {Object} item The item to match
* @return {Boolean} true if all match, false otherwise * @return {Boolean} true if all match, false otherwise
*/ */
ComplexQuery.prototype.AND = function (item) { ComplexQuery.prototype.AND = function (item) {
var queue = new RSVP.Queue(), var result = true,
context = this,
i = 0; i = 0;
function executeNextIfNotFalse(result) { while (result && (i !== this.query_list.length)) {
if (result === false) { result = this.query_list[i].match(item);
// No need to evaluate the other elements, as one is false
return result;
}
if (context.query_list.length === i) {
// No new element to loop on
return true;
}
queue
.push(function () {
var sub_result = context.query_list[i].match(item);
i += 1; i += 1;
return sub_result;
})
.push(executeNextIfNotFalse);
} }
return result;
executeNextIfNotFalse(true); };
return queue;
};
/** /**
* Comparison operator, test if one of the sub queries matches the * Comparison operator, test if one of the sub queries matches the
* item value * item value
* *
...@@ -4959,68 +5135,31 @@ ComplexQuery.prototype.AND = function (item) { ...@@ -4959,68 +5135,31 @@ ComplexQuery.prototype.AND = function (item) {
* @param {Object} item The item to match * @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise * @return {Boolean} true if one match, false otherwise
*/ */
ComplexQuery.prototype.OR = function (item) { ComplexQuery.prototype.OR = function (item) {
var queue = new RSVP.Queue(), var result = false,
context = this,
i = 0; i = 0;
function executeNextIfNotTrue(result) { while ((!result) && (i !== this.query_list.length)) {
if (result === true) { result = this.query_list[i].match(item);
// No need to evaluate the other elements, as one is true
return result;
}
if (context.query_list.length === i) {
// No new element to loop on
return false;
}
queue
.push(function () {
var sub_result = context.query_list[i].match(item);
i += 1; i += 1;
return sub_result;
})
.push(executeNextIfNotTrue);
} }
executeNextIfNotTrue(false); return result;
return queue; };
};
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item) {
return new RSVP.Queue()
.push(function () {
return this.query_list[0].match(item);
})
.push(function (answer) {
return !answer;
});
};
query_class_dict.complex = ComplexQuery;
window.ComplexQuery = ComplexQuery;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global window, ComplexQuery, SimpleQuery, Query, parseStringToObject,
query_class_dict */
/** /**
* Provides static methods to create Query object * Comparison operator, test if the sub query does not match the
* item value
* *
* @class QueryFactory * @method NOT
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/ */
function QueryFactory() { ComplexQuery.prototype.NOT = function (item) {
return; return !this.query_list[0].match(item);
} };
/** /**
* Creates Query object from a search text string or a serialized version * Creates Query object from a search text string or a serialized version
* of a Query. * of a Query.
* *
...@@ -5030,7 +5169,7 @@ function QueryFactory() { ...@@ -5030,7 +5169,7 @@ function QueryFactory() {
* of a Query * of a Query
* @return {Query} A Query object * @return {Query} A Query object
*/ */
QueryFactory.create = function (object, key_schema) { QueryFactory.create = function (object, key_schema) {
if (object === "") { if (object === "") {
return new Query(); return new Query();
} }
...@@ -5043,13 +5182,9 @@ QueryFactory.create = function (object, key_schema) { ...@@ -5043,13 +5182,9 @@ QueryFactory.create = function (object, key_schema) {
} }
throw new TypeError("QueryFactory.create(): " + throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object"); "Argument 1 is not a search text or a parsable object");
}; };
window.QueryFactory = QueryFactory;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, exports */
function objectToSearchText(query) { function objectToSearchText(query) {
var str_list = []; var str_list = [];
if (query.type === "complex") { if (query.type === "complex") {
str_list.push("("); str_list.push("(");
...@@ -5066,13 +5201,9 @@ function objectToSearchText(query) { ...@@ -5066,13 +5201,9 @@ function objectToSearchText(query) {
(query.operator || "") + ' "' + query.value + '"'; (query.operator || "") + ' "' + query.value + '"';
} }
throw new TypeError("This object is not a query"); throw new TypeError("This object is not a query");
} }
Query.objectToSearchText = objectToSearchText;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, inherits, query_class_dict, window,
searchTextToRegExp, RSVP */
var checkKeySchema = function (key_schema) { function checkKeySchema(key_schema) {
var prop; var prop;
if (key_schema !== undefined) { if (key_schema !== undefined) {
...@@ -5099,10 +5230,9 @@ var checkKeySchema = function (key_schema) { ...@@ -5099,10 +5230,9 @@ var checkKeySchema = function (key_schema) {
} }
} }
} }
}; }
/** /**
* The SimpleQuery inherits from Query, and compares one metadata value * The SimpleQuery inherits from Query, and compares one metadata value
* *
* @class SimpleQuery * @class SimpleQuery
...@@ -5112,7 +5242,7 @@ var checkKeySchema = function (key_schema) { ...@@ -5112,7 +5242,7 @@ var checkKeySchema = function (key_schema) {
* @param {String} spec.key The metadata key * @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare * @param {String} spec.value The value of the metadata to compare
*/ */
function SimpleQuery(spec, key_schema) { function SimpleQuery(spec, key_schema) {
Query.call(this); Query.call(this);
checkKeySchema(key_schema); checkKeySchema(key_schema);
...@@ -5144,12 +5274,12 @@ function SimpleQuery(spec, key_schema) { ...@@ -5144,12 +5274,12 @@ function SimpleQuery(spec, key_schema) {
*/ */
this.value = spec.value; this.value = spec.value;
} }
inherits(SimpleQuery, Query); inherits(SimpleQuery, Query);
SimpleQuery.prototype.type = "simple"; SimpleQuery.prototype.type = "simple";
var checkKey = function (key) { function checkKey(key) {
var prop; var prop;
if (key.read_from === undefined) { if (key.read_from === undefined) {
...@@ -5169,13 +5299,12 @@ var checkKey = function (key) { ...@@ -5169,13 +5299,12 @@ var checkKey = function (key) {
} }
} }
} }
}; }
/** /**
* #crossLink "Query/match:method" * #crossLink "Query/match:method"
*/ */
SimpleQuery.prototype.match = function (item) { SimpleQuery.prototype.match = function (item) {
var object_value = null, var object_value = null,
equal_match = null, equal_match = null,
cast_to = null, cast_to = null,
...@@ -5184,10 +5313,9 @@ SimpleQuery.prototype.match = function (item) { ...@@ -5184,10 +5313,9 @@ SimpleQuery.prototype.match = function (item) {
value = null, value = null,
key = this.key; key = this.key;
/*jslint regexp: true */ if (!(regexp_comparaison.test(operator))) {
if (!(/^(?:!?=|<=?|>=?)$/i.test(operator))) {
// `operator` is not correct, we have to change it to "like" or "=" // `operator` is not correct, we have to change it to "like" or "="
if (/%/.test(this.value)) { if (regexp_percent.test(this.value)) {
// `value` contains a non escaped `%` // `value` contains a non escaped `%`
operator = "like"; operator = "like";
} else { } else {
...@@ -5246,23 +5374,23 @@ SimpleQuery.prototype.match = function (item) { ...@@ -5246,23 +5374,23 @@ SimpleQuery.prototype.match = function (item) {
value = this.value; value = this.value;
} }
if (object_value === undefined || value === undefined) { if (object_value === undefined || value === undefined) {
return RSVP.resolve(false); return false;
} }
return matchMethod(object_value, value); return matchMethod(object_value, value);
}; };
/** /**
* #crossLink "Query/toString:method" * #crossLink "Query/toString:method"
*/ */
SimpleQuery.prototype.toString = function () { SimpleQuery.prototype.toString = function () {
return (this.key ? this.key + ":" : "") + return (this.key ? this.key + ":" : "") +
(this.operator ? " " + this.operator : "") + ' "' + this.value + '"'; (this.operator ? " " + this.operator : "") + ' "' + this.value + '"';
}; };
/** /**
* #crossLink "Query/serialized:method" * #crossLink "Query/serialized:method"
*/ */
SimpleQuery.prototype.serialized = function () { SimpleQuery.prototype.serialized = function () {
var object = { var object = {
"type": "simple", "type": "simple",
"key": this.key, "key": this.key,
...@@ -5272,10 +5400,10 @@ SimpleQuery.prototype.serialized = function () { ...@@ -5272,10 +5400,10 @@ SimpleQuery.prototype.serialized = function () {
object.operator = this.operator; object.operator = this.operator;
} }
return object; return object;
}; };
SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized; SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
/** /**
* Comparison operator, test if this query value matches the item value * Comparison operator, test if this query value matches the item value
* *
* @method = * @method =
...@@ -5283,7 +5411,7 @@ SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized; ...@@ -5283,7 +5411,7 @@ SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
* @param {String} comparison_value The comparison value * @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise * @return {Boolean} true if match, false otherwise
*/ */
SimpleQuery.prototype["="] = function (object_value, comparison_value) { SimpleQuery.prototype["="] = function (object_value, comparison_value) {
var value, i; var value, i;
if (!Array.isArray(object_value)) { if (!Array.isArray(object_value)) {
object_value = [object_value]; object_value = [object_value];
...@@ -5294,19 +5422,16 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value) { ...@@ -5294,19 +5422,16 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value) {
value = value.content; value = value.content;
} }
if (typeof value.cmp === "function") { if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) === 0); return (value.cmp(comparison_value) === 0);
} }
if ( if (comparison_value.toString() === value.toString()) {
searchTextToRegExp(comparison_value.toString(), false). return true;
test(value.toString())
) {
return RSVP.resolve(true);
} }
} }
return RSVP.resolve(false); return false;
}; };
/** /**
* Comparison operator, test if this query value matches the item value * Comparison operator, test if this query value matches the item value
* *
* @method like * @method like
...@@ -5314,7 +5439,7 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value) { ...@@ -5314,7 +5439,7 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value) {
* @param {String} comparison_value The comparison value * @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise * @return {Boolean} true if match, false otherwise
*/ */
SimpleQuery.prototype.like = function (object_value, comparison_value) { SimpleQuery.prototype.like = function (object_value, comparison_value) {
var value, i; var value, i;
if (!Array.isArray(object_value)) { if (!Array.isArray(object_value)) {
object_value = [object_value]; object_value = [object_value];
...@@ -5325,18 +5450,18 @@ SimpleQuery.prototype.like = function (object_value, comparison_value) { ...@@ -5325,18 +5450,18 @@ SimpleQuery.prototype.like = function (object_value, comparison_value) {
value = value.content; value = value.content;
} }
if (typeof value.cmp === "function") { if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) === 0); return (value.cmp(comparison_value) === 0);
} }
if ( if (
searchTextToRegExp(comparison_value.toString()).test(value.toString()) searchTextToRegExp(comparison_value.toString()).test(value.toString())
) { ) {
return RSVP.resolve(true); return true;
} }
} }
return RSVP.resolve(false); return false;
}; };
/** /**
* Comparison operator, test if this query value does not match the item value * Comparison operator, test if this query value does not match the item value
* *
* @method != * @method !=
...@@ -5344,7 +5469,7 @@ SimpleQuery.prototype.like = function (object_value, comparison_value) { ...@@ -5344,7 +5469,7 @@ SimpleQuery.prototype.like = function (object_value, comparison_value) {
* @param {String} comparison_value The comparison value * @param {String} comparison_value The comparison value
* @return {Boolean} true if not match, false otherwise * @return {Boolean} true if not match, false otherwise
*/ */
SimpleQuery.prototype["!="] = function (object_value, comparison_value) { SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
var value, i; var value, i;
if (!Array.isArray(object_value)) { if (!Array.isArray(object_value)) {
object_value = [object_value]; object_value = [object_value];
...@@ -5355,19 +5480,16 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value) { ...@@ -5355,19 +5480,16 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
value = value.content; value = value.content;
} }
if (typeof value.cmp === "function") { if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) !== 0); return (value.cmp(comparison_value) !== 0);
} }
if ( if (comparison_value.toString() === value.toString()) {
searchTextToRegExp(comparison_value.toString(), false). return false;
test(value.toString())
) {
return RSVP.resolve(false);
} }
} }
return RSVP.resolve(true); return true;
}; };
/** /**
* Comparison operator, test if this query value is lower than the item value * Comparison operator, test if this query value is lower than the item value
* *
* @method < * @method <
...@@ -5375,7 +5497,7 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value) { ...@@ -5375,7 +5497,7 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
* @param {Number, String} comparison_value The comparison value * @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if lower, false otherwise * @return {Boolean} true if lower, false otherwise
*/ */
SimpleQuery.prototype["<"] = function (object_value, comparison_value) { SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
var value; var value;
if (!Array.isArray(object_value)) { if (!Array.isArray(object_value)) {
object_value = [object_value]; object_value = [object_value];
...@@ -5385,326 +5507,95 @@ SimpleQuery.prototype["<"] = function (object_value, comparison_value) { ...@@ -5385,326 +5507,95 @@ SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
value = value.content; value = value.content;
} }
if (typeof value.cmp === "function") { if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) < 0); return (value.cmp(comparison_value) < 0);
} }
return RSVP.resolve(value < comparison_value); return (value < comparison_value);
}; };
/** /**
* Comparison operator, test if this query value is equal or lower than the * Comparison operator, test if this query value is equal or lower than the
* item value * item value
* *
* @method <= * @method <=
* @param {Number, String} object_value The value to compare * @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value * @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or lower, false otherwise * @return {Boolean} true if equal or lower, false otherwise
*/ */
SimpleQuery.prototype["<="] = function (object_value, comparison_value) { SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
var value; var value;
if (!Array.isArray(object_value)) { if (!Array.isArray(object_value)) {
object_value = [object_value]; object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) <= 0);
}
return RSVP.resolve(value <= comparison_value);
};
/**
* Comparison operator, test if this query value is greater than the item
* value
*
* @method >
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if greater, false otherwise
*/
SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) > 0);
}
return RSVP.resolve(value > comparison_value);
};
/**
* Comparison operator, test if this query value is equal or greater than the
* item value
*
* @method >=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or greater, false otherwise
*/
SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) >= 0);
}
return RSVP.resolve(value >= comparison_value);
};
query_class_dict.simple = SimpleQuery;
window.SimpleQuery = SimpleQuery;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, RSVP, deepClone */
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("Query.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
}
Query.stringEscapeRegexpCharacters = stringEscapeRegexpCharacters;
/**
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @param {Any} value The metadata value
* @return {Array} The value in string array format
*/
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
/**
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
function sortFunction(key, way) {
var result;
if (way === 'descending') {
result = 1;
} else if (way === 'ascending') {
result = -1;
} else {
throw new TypeError("Query.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return result;
}
if (b[i] === undefined) {
return -result;
}
if (a[i] > b[i]) {
return -result;
}
if (a[i] < b[i]) {
return result;
}
}
return 0;
};
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
}
/**
* Does nothing
*/
function emptyFunction() {
return;
}
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("jioquery.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
if (list[i].hasOwnProperty([select_option[j]])) {
new_item[select_option[j]] = list[i][select_option[j]];
}
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
} }
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
} }
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) <= 0);
} }
return list; return (value <= comparison_value);
} };
Query.select = select;
/** /**
* Sort a list of items, according to keys and directions. If `clone` is true, * Comparison operator, test if this query value is greater than the item
* then the method will act on a cloned list. * value
* *
* @param {Array} sort_on_option List of couples [key, direction] * @method >
* @param {Array} list The item list to sort * @param {Number, String} object_value The value to compare
* @param {Boolean} [clone=false] If true, modifies a clone of the list * @param {Number, String} comparison_value The comparison value
* @return {Array} The filtered list * @return {Boolean} true if greater, false otherwise
*/ */
function sortOn(sort_on_option, list, clone) { SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
var sort_index; var value;
if (!Array.isArray(sort_on_option)) { if (!Array.isArray(object_value)) {
throw new TypeError("jioquery.sortOn(): " + object_value = [object_value];
"Argument 1 is not of type 'array'");
} }
if (clone) { value = object_value[0];
list = deepClone(list); if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
} }
for (sort_index = sort_on_option.length - 1; sort_index >= 0; if (typeof value.cmp === "function") {
sort_index -= 1) { return (value.cmp(comparison_value) > 0);
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
} }
return list; return (value > comparison_value);
} };
Query.sortOn = sortOn;
/** /**
* Limit a list of items, according to index and length. If `clone` is true, * Comparison operator, test if this query value is equal or greater than the
* then the method will act on a cloned list. * item value
* *
* @param {Array} limit_option A couple [from, length] * @method >=
* @param {Array} list The item list to limit * @param {Number, String} object_value The value to compare
* @param {Boolean} [clone=false] If true, modifies a clone of the list * @param {Number, String} comparison_value The comparison value
* @return {Array} The filtered list * @return {Boolean} true if equal or greater, false otherwise
*/ */
function limit(limit_option, list, clone) { SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
if (!Array.isArray(limit_option)) { var value;
throw new TypeError("jioquery.limit(): " + if (!Array.isArray(object_value)) {
"Argument 1 is not of type 'array'"); object_value = [object_value];
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.limit(): " +
"Argument 2 is not of type 'array'");
} }
if (clone) { value = object_value[0];
list = deepClone(list); if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
} }
list.splice(0, limit_option[0]); if (typeof value.cmp === "function") {
if (limit_option[1]) { return (value.cmp(comparison_value) >= 0);
list.splice(limit_option[1]);
} }
return list; return (value >= comparison_value);
} };
Query.limit = limit; query_class_dict.simple = SimpleQuery;
query_class_dict.complex = ComplexQuery;
/** Query.parseStringToObject = parseStringToObject;
* Convert a search text to a regexp. Query.objectToSearchText = objectToSearchText;
*
* @param {String} string The string to convert window.Query = Query;
* @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_" window.SimpleQuery = SimpleQuery;
* @return {RegExp} The search text regexp window.ComplexQuery = ComplexQuery;
*/ window.QueryFactory = QueryFactory;
function searchTextToRegExp(string, use_wildcard_characters) {
if (typeof string !== 'string') {
throw new TypeError("jioquery.searchTextToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (use_wildcard_characters === false) {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
/%/g,
".*"
).replace(
/_/g,
"."
) + "$");
}
Query.searchTextToRegExp = searchTextToRegExp; }(RSVP, window, parseStringToObject));
;/*global window, moment */ ;/*global window, moment */
/*jslint nomen: true, maxlen: 200*/ /*jslint nomen: true, maxlen: 200*/
(function (window, moment) { (function (window, moment) {
...@@ -5950,70 +5841,6 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -5950,70 +5841,6 @@ Query.searchTextToRegExp = searchTextToRegExp;
} }
util.ajax = ajax; util.ajax = ajax;
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, Function, null.
*
* It can also clone object which are serializable, like Date.
*
* To make a class serializable, you need to implement the `toJSON` function
* which returns a JSON representation of the object. The returned value is
* used as first parameter of the object constructor.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (object === null) {
return null;
}
if (typeof object === 'object') {
if (Object.getPrototypeOf(object) === Object.prototype) {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
if (object instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is an invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. In
// browsers, giving `null` as parameter to `new Date()` doesn't return
// an invalid date.
// Cloning a date with `return new Date(object)` has problems on
// Firefox.
// I don't know why... (Tested on Firefox 23)
if (isFinite(object.getTime())) {
return new Date(object.toJSON());
}
return new Date("Invalid Date");
}
// clone serializable objects
if (typeof object.toJSON === 'function') {
return new (Object.getPrototypeOf(object).constructor)(object.toJSON());
}
// cannot clone
return object;
}
return object;
}
util.deepClone = deepClone;
function readBlobAsText(blob, encoding) { function readBlobAsText(blob, encoding) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject, notify) {
...@@ -7525,13 +7352,13 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -7525,13 +7352,13 @@ Query.searchTextToRegExp = searchTextToRegExp;
attachments: {} attachments: {}
}; };
} }
this._database[id].doc = JSON.stringify(metadata); this._database[id].doc = metadata;
return id; return id;
}; };
MemoryStorage.prototype.get = function (id) { MemoryStorage.prototype.get = function (id) {
try { try {
return JSON.parse(this._database[id].doc); return this._database[id].doc;
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
throw new jIO.util.jIOError( throw new jIO.util.jIOError(
...@@ -7619,18 +7446,26 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -7619,18 +7446,26 @@ Query.searchTextToRegExp = searchTextToRegExp;
MemoryStorage.prototype.hasCapacity = function (name) { MemoryStorage.prototype.hasCapacity = function (name) {
return (name === "list"); return ((name === "list") || (name === "include"));
}; };
MemoryStorage.prototype.buildQuery = function () { MemoryStorage.prototype.buildQuery = function (options) {
var rows = [], var rows = [],
i; i;
for (i in this._database) { for (i in this._database) {
if (this._database.hasOwnProperty(i)) { if (this._database.hasOwnProperty(i)) {
if (options.include_docs === true) {
rows.push({
id: i,
value: {},
doc: this._database[i]
});
} else {
rows.push({ rows.push({
id: i, id: i,
value: {} value: {}
}); });
}
} }
} }
...@@ -8622,6 +8457,250 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -8622,6 +8457,250 @@ Query.searchTextToRegExp = searchTextToRegExp;
jIO.addStorage('dav', DavStorage); jIO.addStorage('dav', DavStorage);
}(jIO, RSVP, DOMParser, Blob)); }(jIO, RSVP, DOMParser, Blob));
;/*
* Copyright 2015, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/**
* 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));
;/*jslint nomen: true */ ;/*jslint nomen: true */
/*global RSVP*/ /*global RSVP*/
...@@ -8880,10 +8959,10 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -8880,10 +8959,10 @@ Query.searchTextToRegExp = searchTextToRegExp;
// } // }
/*jslint nomen: true, unparam: true */ /*jslint nomen: true, unparam: true */
/*global jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText, /*global jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery*/ SimpleQuery, ComplexQuery*/
(function (jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText, (function (jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery) { SimpleQuery, ComplexQuery) {
"use strict"; "use strict";
...@@ -9340,14 +9419,14 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -9340,14 +9419,14 @@ Query.searchTextToRegExp = searchTextToRegExp;
if (result_list) { if (result_list) {
local_roles = result_list; local_roles = result_list;
parsed_query.query_list.splice(i, 1); parsed_query.query_list.splice(i, 1);
query = objectToSearchText(parsed_query); query = jIO.Query.objectToSearchText(parsed_query);
i = parsed_query.query_list.length; i = parsed_query.query_list.length;
} else { } else {
result_list = isMultipleLocalRoles(sub_query); result_list = isMultipleLocalRoles(sub_query);
if (result_list) { if (result_list) {
local_roles = result_list; local_roles = result_list;
parsed_query.query_list.splice(i, 1); parsed_query.query_list.splice(i, 1);
query = objectToSearchText(parsed_query); query = jIO.Query.objectToSearchText(parsed_query);
i = parsed_query.query_list.length; i = parsed_query.query_list.length;
} }
} }
...@@ -9398,7 +9477,7 @@ Query.searchTextToRegExp = searchTextToRegExp; ...@@ -9398,7 +9477,7 @@ Query.searchTextToRegExp = searchTextToRegExp;
jIO.addStorage("erp5", ERP5Storage); jIO.addStorage("erp5", ERP5Storage);
}(jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText, }(jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery)); SimpleQuery, ComplexQuery));
;/*jslint nomen: true*/ ;/*jslint nomen: true*/
/*global RSVP*/ /*global RSVP*/
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"name": "jio", "name": "jio",
"version": "v3.5.0", "version": "v3.6.0",
"license": "LGPLv3", "license": "LGPLv3",
"author": "Nexedi SA", "author": "Nexedi SA",
"contributors": [ "contributors": [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment