Commit d54f3480 authored by Tristan Cavelier's avatar Tristan Cavelier

Merge branch 'replicaterevisionstorage2'

parents c048fd03 8e7efe86
......@@ -20,13 +20,11 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
priv.storage_list_key = "storage_list";
priv.storage_list = spec[priv.storage_list_key];
my.env = my.env || spec.env || {};
priv.emptyFunction = function () {};
that.specToStore = function () {
var o = {};
o[priv.storage_list_key] = priv.storage_list;
o.env = my.env;
return o;
};
......@@ -68,21 +66,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return newlist;
};
/**
* Generates the next revision
* @method generateNextRevision
* @param {number|string} previous_revision The previous revision
* @param {string} docid The document id
* @return {string} The next revision
*/
priv.generateNextRevision = function (previous_revision, docid) {
my.env[docid].id += 1;
if (typeof previous_revision === "string") {
previous_revision = parseInt(previous_revision.split("-")[0], 10);
}
return (previous_revision + 1) + "-" + my.env[docid].id.toString();
};
/**
* Checks a revision format
* @method checkRevisionFormat
......@@ -93,34 +76,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return (/^[0-9]+-[0-9a-zA-Z_]+$/.test(revision));
};
/**
* Initalize document environment object
* @method initEnv
* @param {string} docid The document id
* @return {object} The reference to the environment
*/
priv.initEnv = function (docid) {
my.env[docid] = {
"id": 0,
"distant_revisions": {},
"my_revisions": {},
"last_revisions": []
};
return my.env[docid];
};
priv.updateEnv = function (doc_env, doc_env_rev, index, doc_rev) {
doc_env.last_revisions[index] = doc_rev;
if (doc_rev !== undefined) {
if (!doc_env.my_revisions[doc_env_rev]) {
doc_env.my_revisions[doc_env_rev] = [];
doc_env.my_revisions[doc_env_rev].length = priv.storage_list.length;
}
doc_env.my_revisions[doc_env_rev][index] = doc_rev;
doc_env.distant_revisions[doc_rev] = doc_env_rev;
}
};
/**
* Clones an object in deep (without functions)
* @method clone
......@@ -188,303 +143,536 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
};
/**
* Post the document metadata to all sub storages
* @method post
* @param {object} command The JIO command
* Use "send" method to all sub storages.
* Calling "callback" only with the first response
* @method sendToAllFastestResponseOnly
* @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
*/
that.post = function (command) {
var functions = {}, doc_env, revs_info, doc, my_rev;
functions.begin = function () {
doc = command.cloneDoc();
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
}
if (typeof doc._id !== "string") {
doc._id = priv.generateUuid();
}
if (priv.post_allowed === undefined) {
priv.post_allowed = true;
}
doc_env = my.env[doc._id];
if (!doc_env || !doc_env.id) {
doc_env = priv.initEnv(doc._id);
}
my_rev = priv.generateNextRevision(doc._rev || 0, doc._id);
functions.sendDocument();
};
functions.sendDocument = function () {
var i, cloned_doc;
for (i = 0; i < priv.storage_list.length; i += 1) {
cloned_doc = priv.clone(doc);
if (typeof cloned_doc._rev === "string" &&
doc_env.my_revisions[cloned_doc._rev] !== undefined) {
cloned_doc._rev = doc_env.my_revisions[cloned_doc._rev][i];
}
priv.send(
doc_env.last_revisions[i] === "unique_" + i ||
priv.put_only ? "put" : "post",
i,
cloned_doc,
command.cloneOption(),
functions.checkSendResult
);
}
};
functions.checkSendResult = function (method, index, err, response) {
priv.sendToAllFastestResponseOnly = function (method, doc, option, callback) {
var i, callbackWrapper, error_count, last_error;
error_count = 0;
callbackWrapper = function (method, index, err, response) {
if (err) {
if (err.status === 409) {
if (method !== "put") {
functions.sendDocumentIndex(
"put",
index,
functions.checkSendResult
);
return;
}
error_count += 1;
last_error = err;
if (error_count === priv.storage_list.length) {
return callback(method, err, response);
}
priv.updateEnv(doc_env, my_rev, index, null);
functions.error(err);
return;
}
// success
priv.updateEnv(
doc_env,
my_rev,
index,
response.rev || "unique_" + index
);
functions.success({"ok": true, "id": doc._id, "rev": my_rev});
callback(method, err, response);
};
functions.success = function (response) {
// can be called once
that.success(response);
functions.success = priv.emptyFunction;
};
functions.error_count = 0;
functions.error = function (err) {
functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) {
that.error(err);
functions.error = priv.emptyFunction;
for (i = 0; i < priv.storage_list.length; i += 1) {
priv.send(method, i, doc, option, callbackWrapper);
}
};
/**
* Use "sendToAll" method, calling "callback" at the last response with
* the response list
* @method sendToAllGetResponseList
* @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 (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) {
error_list[index] = err;
response_list[index] = response;
callback_count += 1;
if (callback_count === priv.storage_list.length) {
callback(error_list, response_list);
}
};
functions.begin();
priv.sendToAll(method, doc, option, wrapper);
};
/**
* Put the document metadata to all sub storages
* @method put
* Checks if the sub storage are identical
* @method check
* @param {object} command The JIO command
*/
that.put = function (command) {
priv.put_only = true;
that.post(command);
that.check = function (command) {
function callback(err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
priv.check(
command.cloneDoc(),
command.cloneOption(),
callback
);
};
/**
* Put an attachment to a document to all sub storages
* @method putAttachment
* Repair the sub storages to make them identical
* @method repair
* @param {object} command The JIO command
*/
// that.putAttachment = function (command) {
that.repair = function (command) {
function callback(err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
priv.repair(
command.cloneDoc(),
command.cloneOption(),
true,
callback
);
};
// };
priv.check = function (doc, option, success, error) {
priv.repair(doc, option, false, success, error);
};
/**
* Get the document or attachment from all sub storages, get the fastest.
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
var functions = {}, doc_env, doc, my_rev, revs_array = [];
priv.repair = function (doc, option, repair, callback) {
var functions = {};
callback = callback || priv.emptyFunction;
option = option || {};
functions.begin = function () {
// };
// functions.repairAllSubStorages = function () {
var i;
doc = command.cloneDoc();
doc_env = my.env[doc._id];
if (!doc_env || !doc_env.id) {
// document environment is not set
doc_env = priv.initEnv(doc._id);
}
// document environment is set now
revs_array.length = priv.storage_list.length;
my_rev = doc._rev;
if (my_rev) {
functions.update_env = false;
}
for (i = 0; i < priv.storage_list.length; i += 1) {
// request all sub storages
if (doc_env.my_revisions[my_rev]) {
// if my_rev exist, convert it to distant revision
doc._rev = doc_env.my_revisions[my_rev][i];
}
priv.send("get", i, doc, command.cloneOption(), functions.callback);
priv.send(
repair ? "repair" : "check",
i,
doc,
option,
functions.repairAllSubStoragesCallback
);
}
};
functions.update_env = true;
functions.callback = function (method, index, err, response) {
functions.repair_sub_storages_count = 0;
functions.repairAllSubStoragesCallback = function (method,
index, err, response) {
if (err) {
revs_array[index] = null;
functions.error(err);
return;
return that.error(err);
}
doc_env.last_revisions[index] = response._rev || "unique_" + index;
revs_array[index] = response._rev || "unique_" + index;
if (doc_env.distant_revisions[response._rev || "unique_" + index]) {
// the document revision is already known
if (functions.update_env === true) {
my_rev = doc_env.distant_revisions[response._rev ||
"unique_" + index];
}
} else {
// the document revision is unknown
if (functions.update_env === true) {
my_rev = priv.generateNextRevision(0, doc._id);
doc_env.my_revisions[my_rev] = revs_array;
doc_env.distant_revisions[response._rev || "unique_" + index] =
my_rev;
}
functions.update_env = false;
functions.repair_sub_storages_count += 1;
if (functions.repair_sub_storages_count === priv.storage_list.length) {
functions.getAllDocuments(functions.newParam(
doc,
option,
repair
));
}
response._rev = my_rev;
functions.success(response);
};
functions.success = function (response) {
var i, start, tmp, tmp_object;
functions.success = priv.emptyFunction;
if (doc_env.my_revisions[my_rev]) {
// this was not a specific revision
// we can convert revisions recieved by the sub storage
if (response._conflicts) {
// convert conflicting revisions to replicate revisions
tmp_object = {};
for (i = 0; i < response._conflicts.length; i += 1) {
tmp_object[doc_env.distant_revisions[response._conflicts[i]] ||
response._conflicts[i]] = true;
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, _mimetype: ..}
// attachmentB : {_id: attachmentB, _revs_info, _mimetype: ..}
}
response._conflicts = priv.dictKeys2Array(tmp_object);
},
"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, doc = priv.clone(param.doc), option = priv.clone(param.option);
option.conflicts = true;
option.revs = true;
option.revs_info = true;
for (i = 0; i < priv.storage_list.length; i += 1) {
// if the document is not loaded
priv.send("get", i, doc, option, functions.dealResults(param));
}
functions.finished_count += 1;
};
functions.dealResults = function (param) {
return function (method, index, err, response) {
var response_object = {};
if (param.deal_result_state !== "ok") {
// deal result is in a wrong state, exit
return;
}
if (response._revisions) {
// convert revisions history to replicate revisions
tmp_object = {};
start = response._revisions.start;
for (i = 0; i < response._revisions.ids.length; i += 1, start -= 1) {
tmp = doc_env.distant_revisions[
start + "-" + response._revisions.ids[i]
];
if (tmp) {
response._revisions.ids[i] = tmp.split("-").slice(1).join("-");
}
if (err) {
if (err.status !== 404) {
// get document failed, exit
param.deal_result_state = "error";
callback({
"status": 40,
"statusText": "Check Failed",
"error": "check_failed",
"message": "An error occured on the sub storage",
"reason": err.reason
}, undefined);
return;
}
}
if (response._revs_info) {
// convert revs info to replicate revisions
for (i = 0; i < response._revs_info.length; i += 1) {
tmp = doc_env.distant_revisions[response._revs_info[i].rev];
if (tmp) {
response._revs_info[i].rev = tmp;
}
// 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);
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": 41,
"statusText": "Check Not Ok",
"error": "check_not_ok",
"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;
}
that.success(response);
};
functions.error_count = 0;
functions.error = function (err) {
functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) {
that.error(err);
functions.error = priv.emptyFunction;
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.begin();
};
/**
* Remove the document or attachment from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
var functions = {}, doc_env, revs_info, doc, my_rev;
functions.begin = function () {
doc = command.cloneDoc();
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
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(
"get",
param.responses.stats[response][0],
{
"_id": param.doc._id + "/" + attachment,
"_rev": JSON.parse(response)._rev
},
param.option,
functions.getAttachmentsCallback(
param,
attachment,
param.responses.stats[response]
)
);
}
}
}
}
doc_env = my.env[doc._id];
if (!doc_env || !doc_env.id) {
doc_env = priv.initEnv(doc._id);
};
functions.get_attachment_count = 0;
functions.getAttachmentsCallback = function (
param,
attachment_id,
index_list
) {
return function (method, index, err, response) {
if (err) {
callback({
"status": 40,
"statusText": "Check Failed",
"error": "check_failed",
"message": "Unable to retreive attachments",
"reason": err.reason
}, undefined);
return;
}
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]
);
}
}
}
my_rev = priv.generateNextRevision(doc._rev || 0, doc._id);
functions.sendDocument();
functions.finished_count -= 1;
};
functions.sendDocument = function () {
var i, cloned_doc;
for (i = 0; i < priv.storage_list.length; i += 1) {
cloned_doc = priv.clone(doc);
if (typeof cloned_doc._rev === "string" &&
doc_env.my_revisions[cloned_doc._rev] !== undefined) {
cloned_doc._rev = doc_env.my_revisions[cloned_doc._rev][i];
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,
"_mimetype": 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(
"remove",
i,
cloned_doc,
command.cloneOption(),
functions.checkSendResult
"put",
storage_list[i],
new_doc,
param.option,
functions.putAttachments(param, attachment_to_put)
);
}
that.end();
functions.finished_count += 1;
functions.finished();
};
functions.checkSendResult = function (method, index, err, response) {
if (err) {
priv.updateEnv(doc_env, my_rev, index, null);
functions.error(err);
return;
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
));
}
}
// success
priv.updateEnv(
doc_env,
my_rev,
index,
response.rev || "unique_" + index
);
functions.success({"ok": true, "id": doc._id, "rev": my_rev});
};
functions.success = function (response) {
// can be called once
that.success(response);
functions.success = priv.emptyFunction;
functions.putAttachments = function (param, attachment_to_put) {
return function (method, index, err, response) {
var i, attachment;
if (err) {
return callback({
"status": 40,
"statusText": "Check Failed",
"error": "check_failed",
"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,
"_mimetype": attachment_to_put[i]._mimetype,
"_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]
};
attachment._id += "/" + attachment._attachment;
delete attachment._attachment;
priv.send(
"putAttachment",
index,
attachment,
option,
functions.putAttachmentCallback(param)
);
}
if (attachment_to_put.length === 0) {
functions.finished();
}
};
};
functions.putAttachmentCallback = function (param) {
return function (method, index, err, response) {
if (err) {
return callback(err, undefined);
}
functions.finished();
};
};
functions.error_count = 0;
functions.error = function (err) {
functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) {
that.error(err);
functions.error = priv.emptyFunction;
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) {
var doc = command.cloneDoc();
doc._id = doc._id || priv.generateUuid();
priv.sendToAllFastestResponseOnly(
method,
doc,
command.cloneOption(),
function (method, err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
);
};
/**
* Post the document metadata to all sub storages
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
that.genericRequest(command, "put");
};
/**
* Put the document metadata to all sub storages
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
that.genericRequest(command, "post");
};
/**
* Put an attachment to a document to all sub storages
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
that.genericRequest(command, "putAttachment");
};
/**
* Get the document from all sub storages, get the fastest.
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
that.genericRequest(command, "get");
};
/**
* Get the attachment from all sub storages, get the fastest.
* @method getAttachment
* @param {object} command The JIO command
*/
that.getAttachment = function (command) {
that.genericRequest(command, "getAttachment");
};
/**
* Remove the document from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
that.genericRequest(command, "remove");
};
/**
* Remove the attachment from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that.removeAttachment = function (command) {
that.genericRequest(command, "removeAttachment");
};
return that;
});
......@@ -9,20 +9,31 @@
* "sub_storage": <sub storage description>
* }
*/
jIO.addStorageType('revision', function (spec, my) {
jIO.addStorageType("revision", function (spec, my) {
"use strict";
var that, priv = {};
var that = {}, priv = {};
spec = spec || {};
that = my.basicStorage(spec, my);
// ATTRIBUTES //
priv.doc_tree_suffix = ".revision_tree.json";
priv.sub_storage = spec.sub_storage;
// METHODS //
/**
* Constructor
*/
priv.RevisionStorage = function () {
// no init
};
priv.substorage_key = "sub_storage";
priv.doctree_suffix = ".revision_tree.json";
priv.substorage = spec[priv.substorage_key];
/**
* Description to store in order to be restored later
* @method specToStore
* @return {object} Descriptions to store
*/
that.specToStore = function () {
var o = {};
o[priv.substorage_key] = priv.substorage;
return o;
return {
"sub_storage": priv.sub_storage
};
};
/**
......@@ -70,1200 +81,825 @@ jIO.addStorageType('revision', function (spec, my) {
};
/**
* Returns an array version of a revision string
* @method revisionToArray
* @param {string} revision The revision string
* @return {array} Array containing a revision number and a hash
* Checks a revision format
* @method checkDocumentRevisionFormat
* @param {object} doc The document object
* @return {object} null if ok, else error object
*/
priv.revisionToArray = function (revision) {
if (typeof revision === "string") {
return [parseInt(revision.split('-')[0], 10),
revision.split('-')[1]];
priv.checkDocumentRevisionFormat = function (doc) {
var send_error = function (message) {
return {
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": message,
"reason": "Revision is wrong"
};
};
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");
}
}
return revision;
};
/**
* Convert the revision history object to an array of revisions.
* @method revisionHistoryToArray
* @param {object} revs The revision history
* @return {array} The revision array
* Creates a new document tree
* @method newDocTree
* @return {object} The new document tree
*/
priv.revisionHistoryToArray = function (revs) {
var i, start = revs.start, newlist = [];
for (i = 0; i < revs.ids.length; i += 1, start -= 1) {
newlist.push(start + "-" + revs.ids[i]);
}
return newlist;
priv.newDocTree = function () {
return {"children": []};
};
/**
* Generates the next revision of [previous_revision]. [string] helps us
* to generate a hash code.
* @methode generateNextRev
* @param {string} previous_revision The previous revision
* @param {object} doc The document metadata
* @param {object} revisions The revision history
* @param {boolean} deleted_flag The deleted flag
* @return {array} 0:The next revision number and 1:the hash code
* Convert revs_info to a simple revisions history
* @method revsInfoToHistory
* @param {array} revs_info The revs info
* @return {object} The revisions history
*/
priv.generateNextRevision = function (previous_revision,
doc, revisions, deleted_flag) {
var string = JSON.stringify(doc) + JSON.stringify(revisions) +
JSON.stringify(deleted_flag ? true : false);
if (typeof previous_revision === "number") {
return [previous_revision + 1, priv.hashCode(string)];
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]);
}
previous_revision = priv.revisionToArray(previous_revision);
return [previous_revision[0] + 1, priv.hashCode(string)];
return revisions;
};
/**
* Checks a revision format
* @method checkRevisionFormat
* @param {string} revision The revision string
* @return {boolean} True if ok, else false
* Convert the revision history object to an array of revisions.
* @method revisionHistoryToList
* @param {object} revs The revision history
* @return {array} The revision array
*/
priv.checkRevisionFormat = function (revision) {
return (/^[0-9]+-[0-9a-zA-Z]+$/.test(revision));
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;
};
/**
* Creates an empty document tree
* @method createDocumentTree
* @param {array} children An array of children (optional)
* @return {object} The new document tree
* 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.createDocumentTree = function (children) {
return {
"children": children || []
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;
};
/**
* Creates a new document tree node
* @method createDocumentTreeNode
* @param {string} revision The node revision
* @param {string} status The node status
* @param {array} children An array of children (optional)
* @return {object} The new document tree node
* Update a document metadata revision properties
* @method fillDocumentRevisionProperties
* @param {object} doc The document object
* @param {object} doc_tree The document tree
*/
priv.createDocumentTreeNode = function (revision, status, children) {
return {
"rev": revision,
"status": status,
"children": children || []
};
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;
}
};
/**
* Gets the specific revision from a document tree.
* @method getRevisionFromDocumentTree
* @param {object} document_tree The document tree
* @param {string} revision The specific revision
* @return {array} The good revs info array
* 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.getRevisionFromDocumentTree = function (document_tree, revision) {
var result, search, revs_info = [];
result = [];
// search method fills "result" with the good revs info
search = function (document_tree) {
var i;
if (document_tree.rev !== undefined) {
// node is not root
revs_info.unshift({
"rev": document_tree.rev,
"status": document_tree.status
});
if (document_tree.rev === revision) {
result = revs_info;
return;
priv.generateNextRevision = function (doc, deleted_flag) {
var string, revision_history, revs_info, pseudo_revision;
doc = priv.clone(doc) || {};
revision_history = doc._revs;
revs_info = doc._revs_info;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
string = JSON.stringify(doc) + JSON.stringify(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}];
}
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the good rev
search(document_tree.children[i]);
if (result.length > 0) {
// The result is already found
return;
revs_info = getRevisionInfoRec(child);
if (revs_info.length > 0 || revision === undefined) {
revs_info.push({"rev": child.rev, "status": child.status});
return revs_info;
}
revs_info.shift();
}
return [];
};
search(document_tree);
return result;
return getRevisionInfoRec(doc_tree);
};
/**
* Gets the winner revision from a document tree.
* The winner is the deeper revision on the left.
* @method getWinnerRevisionFromDocumentTree
* @param {object} document_tree The document tree
* @return {array} The winner revs info array
*/
priv.getWinnerRevisionFromDocumentTree = function (document_tree) {
var result, search, revs_info = [];
result = [];
// search method fills "result" with the winner revs info
search = function (document_tree, deep) {
var i;
if (document_tree.rev !== undefined) {
// node is not root
revs_info.unshift({
"rev": document_tree.rev,
"status": document_tree.status
});
}
if (document_tree.children.length === 0 && document_tree.status !==
"deleted") {
// This node is a leaf
if (result.length < deep) {
// The leaf is deeper than result
result = [];
for (i = 0; i < revs_info.length; i += 1) {
result.push(revs_info[i]);
}
}
priv.updateDocumentTree = function (doc, doc_tree) {
var revs_info, updateDocumentTreeRec, next_rev;
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;
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i], deep + 1);
revs_info.shift();
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);
};
search(document_tree, 0);
return result;
updateDocumentTreeRec(doc_tree, priv.clone(revs_info));
};
/**
* Add a document revision branch to the document tree
* @method updateDocumentTree
* @param {object} doctree The document tree object
* @param {object|array} revs The revision history object or a revision array
* @param {boolean} deleted The deleted flag
* @param {array} The document revs_info
*/
priv.updateDocumentTree = function (doctree, revs, deleted) {
var revs_info, doctree_iterator, flag, i, rev;
revs_info = [];
if (revs.ids) {
// revs is a revision history object
revs = priv.revisionHistoryToArray(revs);
} else {
// revs is an array of revisions
revs = priv.clone(revs);
}
doctree_iterator = doctree;
while (revs.length > 0) {
rev = revs.pop(0);
revs_info.unshift({
"rev": rev,
"status": "missing"
});
for (i = 0; i < doctree_iterator.children.length; i += 1) {
if (doctree_iterator.children[i].rev === rev) {
doctree_iterator = doctree_iterator.children[i];
revs_info[0].status = doctree_iterator.status;
rev = undefined;
break;
}
}
if (rev) {
doctree_iterator.children.unshift({
"rev": rev,
"status": "missing",
"children": []
});
doctree_iterator = doctree_iterator.children[0];
priv.send = function (method, doc, option, callback) {
that.addJob(
method,
priv.sub_storage,
doc,
option,
function (success) {
callback(undefined, success);
},
function (err) {
callback(err, undefined);
}
}
flag = deleted === true ? "deleted" : "available";
revs_info[0].status = flag;
doctree_iterator.status = flag;
return revs_info;
);
};
/**
* Add a document revision to the document tree
* @method postToDocumentTree
* @param {object} doctree The document tree object
* @param {object} doc The document object
* @param {boolean} set_node_to_deleted Set the revision to deleted
* @return {array} The added document revs_info
*/
priv.postToDocumentTree = function (doctree, doc, set_node_to_deleted) {
var i, revs_info, next_rev, next_rev_str, selectNode, selected_node,
flag;
flag = set_node_to_deleted === true ? "deleted" : "available";
revs_info = [];
selected_node = doctree;
selectNode = function (node) {
priv.getWinnerRevsInfo = function (doc_tree) {
var revs_info = [], getWinnerRevsInfoRec;
getWinnerRevsInfoRec = function (doc_tree, tmp_revs_info) {
var i;
if (node.rev !== undefined) {
// node is not root
revs_info.unshift({
"rev": node.rev,
"status": node.status
});
if (doc_tree.rev) {
tmp_revs_info.unshift({"rev": doc_tree.rev, "status": doc_tree.status});
}
if (node.rev === doc._rev) {
selected_node = node;
return "node_selected";
}
for (i = 0; i < node.children.length; i += 1) {
if (selectNode(node.children[i]) === "node_selected") {
return "node_selected";
if (doc_tree.children.length === 0) {
if (revs_info.length < tmp_revs_info.length ||
(revs_info.length > 0 && revs_info[0].status === "deleted")) {
revs_info = priv.clone(tmp_revs_info);
}
revs_info.shift();
}
};
if (typeof doc._rev === "string") {
// document has a previous revision
if (selectNode(selected_node) !== "node_selected") {
// no node was selected, so add a node with a specific rev
revs_info.unshift({
"rev": doc._rev,
"status": "missing"
});
selected_node.children.unshift(priv.createDocumentTreeNode(
doc._rev,
"missing"
));
selected_node = selected_node.children[0];
}
}
next_rev = priv.generateNextRevision(
doc._rev || 0,
doc,
priv.revsInfoToHistory(revs_info),
set_node_to_deleted
);
next_rev_str = next_rev.join("-");
// don't add if the next rev already exists
for (i = 0; i < selected_node.children.length; i += 1) {
if (selected_node.children[i].rev === next_rev_str) {
revs_info.unshift({
"rev": next_rev_str,
"status": flag
});
if (selected_node.children[i].status !== flag) {
selected_node.children[i].status = flag;
}
return revs_info;
for (i = 0; i < doc_tree.children.length; i += 1) {
getWinnerRevsInfoRec(doc_tree.children[i], tmp_revs_info);
}
}
revs_info.unshift({
"rev": next_rev.join('-'),
"status": flag
});
selected_node.children.unshift(priv.createDocumentTreeNode(
next_rev.join('-'),
flag
));
tmp_revs_info.shift();
};
getWinnerRevsInfoRec(doc_tree, []);
return revs_info;
};
/**
* Gets an array of leaves revisions from document tree
* @method getLeavesFromDocumentTree
* @param {object} document_tree The document tree
* @param {string} except The revision to except
* @return {array} The array of leaves revisions
*/
priv.getLeavesFromDocumentTree = function (document_tree, except) {
var result, search;
result = [];
// search method fills [result] with the winner revision
search = function (document_tree) {
priv.getConflicts = function (revision, doc_tree) {
var conflicts = [], getConflictsRec;
getConflictsRec = function (doc_tree) {
var i;
if (except !== undefined && except === document_tree.rev) {
if (doc_tree.rev === revision) {
return;
}
if (document_tree.children.length === 0 && document_tree.status !==
"deleted") {
// This node is a leaf
result.push(document_tree.rev);
return;
if (doc_tree.children.length === 0) {
if (doc_tree.status !== "deleted") {
conflicts.push(doc_tree.rev);
}
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i]);
for (i = 0; i < doc_tree.children.length; i += 1) {
getConflictsRec(doc_tree.children[i]);
}
};
search(document_tree);
return result;
getConflictsRec(doc_tree);
return conflicts.length === 0 ? undefined : conflicts;
};
/**
* Check if revision is a leaf
* @method isRevisionALeaf
* @param {string} revision revision to check
* @param {array} leaves all leaves on tree
* @return {boolean} true/false
*/
priv.isRevisionALeaf = function (document_tree, revision) {
var result, search;
result = undefined;
// search method fills "result" with the good revs info
search = function (document_tree) {
var i;
if (document_tree.rev !== undefined) {
// node is not root
if (document_tree.rev === revision) {
if (document_tree.children.length === 0) {
// This node is a leaf
result = true;
return;
}
result = false;
priv.get = function (doc, option, callback) {
priv.send("get", doc, option, callback);
};
priv.put = function (doc, option, callback) {
priv.send("put", doc, option, callback);
};
priv.remove = function (doc, option, callback) {
priv.send("remove", doc, option, callback);
};
priv.putAttachment = function (attachment, option, callback) {
priv.send("putAttachment", attachment, option, callback);
};
priv.getDocument = function (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(doc, option, callback);
};
priv.getAttachment = priv.get;
priv.putDocument = function (doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + "." + doc._rev;
delete doc._attachment;
delete doc._data;
delete doc._mimetype;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
priv.put(doc, option, callback);
};
priv.getRevisionTree = function (doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + priv.doc_tree_suffix;
priv.get(doc, option, callback);
};
priv.getAttachmentList = function (doc, option, callback) {
var attachment_id, dealResults, state = "ok", result_list = [], count = 0;
dealResults = function (attachment_id, attachment_meta) {
return function (err, attachment) {
if (state !== "ok") {
return;
}
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the good rev
search(document_tree.children[i]);
if (result !== undefined) {
// The result is already found
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": attachment,
"_mimetype": attachment_meta.content_type
});
if (count === 0) {
state = "finished";
callback(undefined, result_list);
}
};
};
search(document_tree);
return result || false;
};
/**
* 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 revisions = {
"start": 0,
"ids": []
}, i;
if (revs_info.length > 0) {
revisions.start = parseInt(revs_info[0].rev.split('-')[0], 10);
for (attachment_id in doc._attachments) {
if (doc._attachments.hasOwnProperty(attachment_id)) {
count += 1;
priv.get(
{"_id": doc._id + "/" + attachment_id},
option,
dealResults(attachment_id, doc._attachments[attachment_id])
);
}
}
for (i = 0; i < revs_info.length; i += 1) {
revisions.ids.push(revs_info[i].rev.split('-')[1]);
if (count === 0) {
callback(undefined, []);
}
return revisions;
};
/**
* Returns the revision of the revision position from a revs_info array.
* @method getRevisionFromPosition
* @param {array} revs_info The revs_info array
* @param {number} rev_pos The revision position number
* @return {string} The revision of the good position (empty string if fail)
*/
priv.getRevisionFromPosition = function (revs_info, rev_pos) {
var i;
for (i = revs_info.length - 1; i >= 0; i -= 1) {
if (priv.revisionToArray(revs_info[i].rev)[0] === rev_pos) {
return revs_info[i].rev;
priv.putAttachmentList = function (doc, option, attachment_list, callback) {
var i, dealResults, state = "ok", count = 0, attachment;
attachment_list = attachment_list || [];
dealResults = function (index) {
return function (err, response) {
if (state !== "ok") {
return;
}
count -= 1;
if (err) {
state = "error";
return callback(err, undefined);
}
if (count === 0) {
state = "finished";
callback(undefined, {"id": doc._id, "ok": true});
}
};
};
for (i = 0; i < attachment_list.length; i += 1) {
attachment = attachment_list[i];
if (attachment !== undefined) {
count += 1;
attachment._id = doc._id + "." + doc._rev + "/" +
attachment._attachment;
delete attachment._attachment;
priv.putAttachment(attachment, option, dealResults(i));
}
}
return '';
if (count === 0) {
return callback(undefined, {"id": doc._id, "ok": true});
}
};
/**
* 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) {
var f = {}, doctree, revs_info, doc, docid, prev_doc;
doc = command.cloneDoc();
docid = command.getDocId();
priv.putDocumentTree = function (doc, option, doc_tree, callback) {
doc_tree = priv.clone(doc_tree);
doc_tree._id = doc._id + priv.doc_tree_suffix;
priv.put(doc_tree, option, callback);
};
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
priv.notFoundError = function (message, reason) {
return {
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": message,
"reason": reason
};
};
priv.conflictError = function (message, reason) {
return {
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": message,
"reason": reason
};
};
priv.revisionGenericRequest = function (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 (typeof docid !== "string") {
doc._id = priv.generateUuid();
docid = doc._id;
if (specific_parameter.attachment_id) {
doc._attachment = specific_parameter.attachment_id;
}
f.getDocumentTree = function () {
var option = command.cloneOption();
if (option.max_retry === 0) {
option.max_retry = 3;
callback.begin = function () {
var check_error;
doc._id = doc._id || priv.generateUuid();
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(doc, option, callback.getRevisionTree);
};
callback.getRevisionTree = function (err, response) {
var winner_info, 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);
}
}
that.addJob(
"get",
priv.substorage,
docid + priv.doctree_suffix,
option,
function (response) {
doctree = response;
f.updateRevsInfo();
f.getDocument();
},
function (err) {
switch (err.status) {
case 404:
doctree = priv.createDocumentTree();
f.updateRevsInfo();
f.getDocument();
break;
default:
err.message = "Cannot get document revision tree";
f.error(err);
break;
doc_tree = response || 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);
}
}
);
};
f.getDocument = function () {
if (revs_info[1] === undefined) {
f.postDocument([]);
} else {
that.addJob(
"get",
priv.substorage,
command.getDocId() + "." + revs_info[1].rev,
command.getOption(),
function (response) {
var attachment_list = [], i;
prev_doc = response;
for (i in response._attachments) {
if (response._attachments.hasOwnProperty(i)) {
attachment_list.push({"id": i, "attachment": {
"_id": command.getDocId() + "." + revs_info[0].rev + "/" + i,
"_mimetype": response._attachments[i].content_type,
"_data": undefined
}});
}
}
f.postDocument(attachment_list);
},
function (err) {
if (err.status === 404) {
f.postDocument([]);
return;
}
err.message = "Cannot retrieve document";
f.error(err);
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(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(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(doc, option, callback.putDocument);
};
f.updateRevsInfo = function () {
if (doc._revs) {
revs_info = priv.updateDocumentTree(doctree, doc._revs);
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 = {};
} else {
err.message = "Cannot get document";
return onEnd(err, undefined);
}
}
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, 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(doc, option, doc_tree, callback.putDocumentTree);
} else {
revs_info = priv.postToDocumentTree(doctree, doc);
priv.getAttachmentList(res_doc, option, callback.getAttachmentList);
}
};
f.postDocument = function (attachment_list) {
doc._id = docid + "." + revs_info[0].rev;
delete doc._rev;
delete doc._revs;
that.addJob(
"post",
priv.substorage,
doc,
command.cloneOption(),
function () {
var i;
if (attachment_list.length === 0) {
f.sendDocumentTree();
} else {
f.send_document_tree_count = attachment_list.length;
for (i = 0; i < attachment_list.length; i += 1) {
f.copyAttachment(attachment_list[i].id,
attachment_list[i].attachment);
}
callback.getAttachmentList = function (err, res_list) {
var i, attachment_found = false;
if (err) {
err.message = "Cannot get attachment";
return onEnd(err, undefined);
}
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, attachment_list[i]._data);
}
},
function (err) {
switch (err.status) {
case 409:
// file already exists
f.sendDocumentTree();
break;
default:
err.message = "Cannot upload document";
f.error(err);
}
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(doc, option, callback.putDocument);
};
f.copyAttachment = function (attachmentid, attachment) {
that.addJob(
"get",
priv.substorage,
prev_doc._id + "/" + attachmentid,
command.cloneOption(),
function (response) {
attachment._data = response;
that.addJob(
"putAttachment",
priv.substorage,
attachment,
command.cloneOption(),
function (response) {
f.sendDocumentTree();
},
function (err) {
err.message = "Cannot copy previous attachment";
f.error(err);
}
);
},
function (err) {
err.message = "Cannot get previous attachment";
f.error(err);
callback.putDocument = function (err, response) {
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(
doc,
option,
attachment_list,
callback.putAttachmentList
);
};
f.send_document_tree_count = 0;
f.sendDocumentTree = function () {
f.send_document_tree_count -= 1;
if (f.send_document_tree_count > 0) {
return;
callback.putAttachmentList = function (err, response) {
if (err) {
err.message = "Cannot copy attacments to the document";
return onEnd(err, undefined);
}
doctree._id = docid + priv.doctree_suffix;
that.addJob(
"put",
priv.substorage,
doctree,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": docid,
"rev": revs_info[0].rev
});
},
function (err) {
// xxx do we try to delete the posted document ?
err.message = "Cannot save document revision tree";
f.error(err);
}
);
priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree);
};
f.error = function (err) {
f.error = function () {};
that.error(err);
callback.putDocumentTree = function (err, response) {
if (err) {
err.message = "Cannot update the document history";
return onEnd(err, undefined);
}
onEnd(undefined, {
"ok": true,
"id": doc._id + (specific_parameter.putAttachment ||
specific_parameter.removeAttachment ||
specific_parameter.getAttachment ?
"/" + doc._attachment : ""),
"rev": doc._rev
});
// if (option.keep_revision_history !== true) {
// // priv.remove(prev_doc, option, function () {
// // - change "available" status to "deleted"
// // - remove attachments
// // - done, no callback
// // });
// }
};
f.getDocumentTree();
callback.begin();
};
/**
* Update the document metadata and update a document tree.
* 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 put
* @method post
* @param {object} command The JIO command
*/
that.put = function (command) {
that.post(command);
that.post = function (command) {
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{},
function (err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
);
};
/**
* Create/Update the document attachment and update a document tree.
* 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 putAttachment
* @method put
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
var functions = {}, doc, doctree, revs_info, prev_doc;
doc = command.cloneDoc();
functions.begin = function () {
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
}
functions.getDocumentTree();
};
functions.getDocumentTree = function () {
var option = command.cloneOption();
if (option.max_retry === 0) {
option.max_retry = 3;
}
that.addJob(
"get",
priv.substorage,
command.getDocId() + priv.doctree_suffix,
option,
function (response) {
doctree = response;
functions.updateRevsInfo();
functions.getDocument();
},
function (err) {
switch (err.status) {
case 404:
doctree = priv.createDocumentTree();
functions.updateRevsInfo();
functions.getDocument();
break;
default:
err.message = "Cannot get document revision tree";
that.error(err);
break;
}
}
);
};
functions.updateRevsInfo = function () {
if (doc._revs) {
revs_info = priv.updateDocumentTree(doctree, doc._revs);
} else {
revs_info = priv.postToDocumentTree(doctree, doc);
}
};
functions.postEmptyDocument = function () {
that.addJob(
"post",
priv.substorage,
{"_id": command.getDocId() + "." + revs_info[0].rev},
command.getOption(),
function (response) {
doc._rev = response.rev;
functions.postAttachment();
},
function (err) {
err.message = "Cannot upload document";
that.error(err);
}
);
};
functions.getDocument = function () {
if (revs_info[1] === undefined) {
functions.postEmptyDocument();
} else {
that.addJob(
"get",
priv.substorage,
command.getDocId() + "." + revs_info[1].rev,
command.getOption(),
function (response) {
var attachment_list = [], i;
prev_doc = response;
for (i in response._attachments) {
if (response._attachments.hasOwnProperty(i)) {
attachment_list.push({"id": i, "attachment": {
"_id": command.getDocId() + "." + revs_info[0].rev + "/" + i,
"_mimetype": response._attachments[i].content_type,
"_data": undefined
}});
}
}
functions.postDocument(attachment_list);
},
function (err) {
if (err.status === 404) {
functions.postDocument([]);
return;
}
err.message = "Cannot upload document";
that.error(err);
}
);
}
};
functions.postDocument = function (attachment_list) {
that.addJob(
"post",
priv.substorage,
command.getDocId() + "." + revs_info[0].rev,
command.getOption(),
function (response) {
var i;
if (attachment_list.length === 0) {
functions.postAttachment();
} else {
functions.post_attachment_count = attachment_list.length;
for (i = 0; i < attachment_list.length; i += 1) {
functions.copyAttachment(attachment_list[i].id,
attachment_list[i].attachment);
}
}
},
function (err) {
err.message = "Cannot upload document";
that.error(err);
}
);
};
functions.copyAttachment = function (attachmentid, attachment) {
that.addJob(
"get",
priv.substorage,
prev_doc._id + "/" + attachmentid,
command.cloneOption(),
function (response) {
attachment._data = response;
that.addJob(
"putAttachment",
priv.substorage,
attachment,
command.cloneOption(),
function (response) {
functions.postAttachment();
},
function (err) {
err.message = "Cannot copy previous attachment";
functions.error(err);
}
);
},
function (err) {
err.message = "Cannot copy previous attachment";
functions.error(err);
that.put = function (command) {
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{},
function (err, response) {
if (err) {
return that.error(err);
}
);
};
functions.post_attachment_count = 0;
functions.postAttachment = function () {
functions.post_attachment_count -= 1;
if (functions.post_attachment_count > 0) {
return;
that.success(response);
}
that.addJob(
"putAttachment",
priv.substorage,
{
"_id": command.getDocId() + "." + revs_info[0].rev + "/" +
command.getAttachmentId(),
);
};
that.putAttachment = function (command) {
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"doc_id": command.getDocId(),
"attachment_id": command.getAttachmentId(),
"add_to_attachment_list": {
"_attachment": command.getAttachmentId(),
"_mimetype": command.getAttachmentMimeType(),
"_data": command.getAttachmentData()
},
command.cloneOption(),
function () {
functions.sendDocumentTree();
},
function (err) {
switch (err.status) {
case 409:
// file already exists
functions.sendDocumentTree();
break;
default:
err.message = "Cannot upload attachment";
functions.error(err);
break;
}
}
);
};
functions.sendDocumentTree = function () {
doctree._id = command.getDocId() + priv.doctree_suffix;
that.addJob(
"put",
priv.substorage,
doctree,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": command.getDocId() + "/" + command.getAttachmentId(),
"rev": revs_info[0].rev
});
},
function (err) {
// xxx do we try to delete the posted document ?
err.message = "Cannot save document revision tree";
functions.error(err);
"putAttachment": true
},
function (err, response) {
if (err) {
return that.error(err);
}
);
};
functions.error = function (err) {
functions.error = function () {};
that.error(err);
};
functions.begin();
};
/**
* Get the document metadata or attachment.
* Options:
* - {boolean} revs Add simple revision history (false by default).
* - {boolean} revs_info Add revs info (false by default).
* - {boolean} conflicts Add conflict object (false by default).
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
var f = {}, doctree, revs_info, prev_rev, option;
option = command.cloneOption();
if (option.max_retry === 0) {
option.max_retry = 3;
}
prev_rev = command.getDocInfo("_rev");
if (typeof prev_rev === "string") {
if (!priv.checkRevisionFormat(prev_rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"[0-9]+-[0-9a-zA-Z]+",
"reason": "Previous revision is wrong"
});
return;
that.success(response);
}
}
f.getDocumentTree = function () {
that.addJob(
"get",
priv.substorage,
{
"_id": command.getDocId() + priv.doctree_suffix,
"_rev": command.getDocInfo("_rev")
},
option,
function (response) {
doctree = response;
if (prev_rev === undefined) {
revs_info = priv.getWinnerRevisionFromDocumentTree(doctree);
if (revs_info.length > 0) {
prev_rev = revs_info[0].rev;
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document is deleted"
});
return;
}
} else {
revs_info = priv.getRevisionFromDocumentTree(doctree, prev_rev);
}
f.getDocument(command.getDocId() + "." + prev_rev,
command.getAttachmentId());
},
function (err) {
switch (err.status) {
case 404:
that.error(err);
break;
default:
err.message = "Cannot get document revision tree";
that.error(err);
break;
}
}
);
};
f.getDocument = function (docid, attmtid) {
that.addJob(
"get",
priv.substorage,
{"_id": docid, "_rev": command.getDocInfo("_rev")},
option,
function (response) {
var attmt;
if (typeof response !== "string") {
if (attmtid !== undefined) {
if (response._attachments !== undefined) {
attmt = response._attachments[attmtid];
if (attmt !== undefined) {
prev_rev = priv.getRevisionFromPosition(
revs_info,
attmt.revpos
);
f.getDocument(command.getDocId() + "." + prev_rev + "/" +
attmtid);
return;
}
}
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment is missing"
});
return;
}
response._id = command.getDocId();
response._rev = prev_rev;
if (command.getOption("revs") === true) {
response._revisions = priv.revsInfoToHistory(revs_info);
}
if (command.getOption("revs_info") === true) {
response._revs_info = revs_info;
}
if (command.getOption("conflicts") === true) {
response._conflicts = priv.getLeavesFromDocumentTree(
doctree,
prev_rev
);
if (response._conflicts.length === 0) {
delete response._conflicts;
}
}
}
that.success(response);
},
function (err) {
that.error(err);
}
);
};
if (command.getAttachmentId() && prev_rev !== undefined) {
f.getDocument(command.getDocId() + "." + prev_rev +
"/" + command.getAttachmentId());
} else {
f.getDocumentTree();
}
);
};
/**
* Remove document or attachment.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
var f = {}, del_rev, option, new_doc, revs_info;
option = command.cloneOption();
if (option.max_retry === 0) {
option.max_retry = 3;
if (command.getAttachmentId()) {
return that.removeAttachment(command);
}
del_rev = command.getDoc()._rev;
f.removeDocument = function (docid, doctree) {
if (command.getOption("keep_revision_history") !== true) {
if (command.getAttachmentId() === undefined) {
// update tree
revs_info = priv.postToDocumentTree(
doctree,
command.getDoc(),
true
);
// remove revision
that.addJob(
"remove",
priv.substorage,
docid,
option,
function () {
// put tree
doctree._id = command.getDocId() + priv.doctree_suffix;
that.addJob(
"put",
priv.substorage,
doctree,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": command.getDocId(),
"rev": revs_info[0].rev
});
},
function () {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Cannot update document tree"
});
return;
}
);
},
function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "File not found",
"reason": "Document was not found"
});
return;
}
);
} else {
// get previsous document
that.addJob(
"get",
priv.substorage,
command.getDocId() + "." + del_rev,
option,
function (response) {
// update tree
revs_info = priv.postToDocumentTree(doctree, command.getDoc());
new_doc = response;
delete new_doc._attachments;
new_doc._id = new_doc._id + "." + revs_info[0].rev;
// post new document version
that.addJob(
"post",
priv.substorage,
new_doc,
command.cloneOption(),
function () {
// put tree
doctree._id = command.getDocId() + priv.doctree_suffix;
that.addJob(
"put",
priv.substorage,
doctree,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": new_doc._id,
"rev": revs_info[0].rev
});
},
function (err) {
err.message =
"Cannot save document revision tree";
that.error(err);
}
);
},
function () {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Cannot update document"
});
return;
}
);
},
function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "File not found",
"reason": "Document was not found"
});
return;
}
);
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"revision_needed": true,
"remove": true
},
function (err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
};
if (typeof del_rev === "string") {
if (!priv.checkRevisionFormat(del_rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"[0-9]+-[0-9a-zA-Z]+",
"reason": "Previous revision is wrong"
});
return;
}
}
// get doctree
that.addJob(
"get",
priv.substorage,
command.getDocId() + priv.doctree_suffix,
option,
function (response) {
response._conflicts = priv.getLeavesFromDocumentTree(response);
);
};
if (del_rev === undefined) {
// no revision provided
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Cannot delete a document without revision"
});
return;
that.removeAttachment = function (command) {
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"doc_id": command.getDocId(),
"attachment_id": command.getAttachmentId(),
"revision_needed": true,
"removeAttachment": true,
"remove_from_attachment_list": {
"_attachment": command.getAttachmentId()
}
// revision provided
if (priv.isRevisionALeaf(response, del_rev) === true) {
if (typeof command.getAttachmentId() === "string") {
f.removeDocument(command.getDocId() + "." + del_rev +
"/" + command.getAttachmentId(), response);
} else {
f.removeDocument(command.getDocId() + "." + del_rev,
response);
}
} else {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Trying to remove non-latest revision"
});
return;
},
function (err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
);
};
that.get = function (command) {
if (command.getAttachmentId()) {
return that.getAttachment(command);
}
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"get": true
},
function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Document tree not found, please checkdocument ID",
"reason": "Incorrect document ID"
});
return;
function (err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
);
};
/**
* Get all documents
* @method allDocs
* @param {object} command The JIO command
*/
that.allDocs = function () {
setTimeout(function () {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Your are not allowed to use this command",
"reason": "LocalStorage forbids AllDocs command executions"
});
});
that.getAttachment = function (command) {
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"doc_id": command.getDocId(),
"attachment_id": command.getAttachmentId(),
"getAttachment": true
},
function (err, response) {
if (err) {
return that.error(err);
}
that.success(response);
}
);
};
// END //
priv.RevisionStorage();
return that;
});
}); // end RevisionStorage
......@@ -2,6 +2,7 @@
/*global postCommand: true, putCommand: true, getCommand: true,
removeCommand: true, allDocsCommand: true,
putAttachmentCommand: true, failStatus: true, doneStatus: true,
checkCommand: true, repairCommand: true,
hex_md5: true */
var command = function (spec, my) {
var that = {},
......@@ -16,7 +17,9 @@ var command = function (spec, my) {
'get': getCommand,
'remove': removeCommand,
'allDocs': allDocsCommand,
'putAttachment': putAttachmentCommand
'putAttachment': putAttachmentCommand,
'check': checkCommand,
'repair': repairCommand
};
// creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) {
......
......@@ -4,7 +4,7 @@
storage_type_object: true, invalidStorageType: true, jobRules: true,
job: true, postCommand: true, putCommand: true, getCommand:true,
allDocsCommand: true, putAttachmentCommand: true,
removeCommand: true */
removeCommand: true, checkCommand: true, repairCommand: true */
// Class jio
var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
spec = spec || {};
......@@ -377,10 +377,10 @@ Object.defineProperty(that, "allDocs", {
* Put an attachment to a document.
* @method putAttachment
* @param {object} doc The document object. Contains at least:
* - {string} id The document id: "doc_id/attchment_id"
* - {string} data Base64 attachment data
* - {string} mimetype The attachment mimetype
* - {string} rev The attachment revision
* - {string} _id The document id: "doc_id/attchment_id"
* - {string} _data Base64 attachment data
* - {string} _mimetype The attachment mimetype
* - {string} _rev The attachment revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
......@@ -408,3 +408,63 @@ Object.defineProperty(that, "putAttachment", {
});
}
});
/**
* Check a document.
* @method check
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "check", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(checkCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Repair a document.
* @method repair
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "repair", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(repairCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
......@@ -159,6 +159,18 @@ var storage = function (spec, my) {
});
};
that.check = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.repair = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.success = function () {};
that.retry = function () {};
that.error = function () {};
......
......@@ -34,9 +34,14 @@ clone = function (obj) {
// generates a revision hash from document metadata, revision history
// and the deleted_flag
generateRevisionHash = function (doc, revisions, deleted_flag) {
var string = JSON.stringify(doc) + JSON.stringify(revisions) +
JSON.stringify(deleted_flag? true: false);
return hex_sha256(string);
var string;
doc = clone(doc);
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
string = JSON.stringify(doc) + JSON.stringify(revisions) +
JSON.stringify(deleted_flag? true: false);
return hex_sha256(string);
},
// localStorage wrapper
localstorage = {
......@@ -646,7 +651,6 @@ test ("Similar Jobs at the same time (Update)", function () {
o.jio.put({"_id": "file", "content": "content"}, o.f2); // 2
o.jio.put({"_id": "file", "content": "content"}, o.f3); // 3
deepEqual(getLastJob(o.jio.getId()).id, 1, "Check job queue");
console.log(JSON.parse(JSON.stringify(localStorage)));
o.tick(o, 1000, "f");
o.tick(o, "f2");
o.tick(o, "f3");
......@@ -1264,7 +1268,8 @@ test ("Post", function(){
o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"};
o.revisions = {"start": 1, "ids": [o.rev.split('-')[1]]};
o.rev = "2-"+generateRevisionHash(o.doc, o.revisions);
o.spy (o, "status", undefined, "Post + revision");
o.spy (o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post + revision");
o.jio.post(o.doc, o.f);
o.tick(o);
......@@ -1293,8 +1298,92 @@ test ("Post", function(){
"Check document tree"
);
o.jio.stop();
// add attachment
o.doc._attachments = {
"attachment_test": {
"length": 35,
"digest": "A",
"content_type": "oh/yeah"
}
};
localstorage.setItem(o.localpath + "/post1." + o.rev, o.doc);
localstorage.setItem(o.localpath + "/post1." + o.rev + "/attachment_test",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
// post + attachment copy
o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"};
o.revisions = {
"start": 2,
"ids": [o.rev.split('-')[1], o.revisions.ids[0]]
};
o.rev = "3-"+generateRevisionHash(o.doc, o.revisions);
o.spy (o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post + attachment copy");
o.jio.post(o.doc, o.f);
o.tick(o);
// check attachment
deepEqual(
localstorage.getItem(o.localpath + "/post1." + o.rev +
"/attachment_test"),
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"Check Attachment"
);
// check document tree
o.doc_tree._id = "post1.revision_tree.json";
o.doc_tree.children[0].children[0].children.unshift({
"rev": o.rev, "status": "available", "children": []
});
deepEqual(
localstorage.getItem(
o.localpath + "/post1.revision_tree.json"
),
o.doc_tree,
"Check document tree"
);
// post + wrong revision
o.doc = {"_id": "post1", "_rev": "3-wr3", "title": "myPost3"};
o.revisions = {"start": 3, "ids": ["wr3"]};
o.rev = "4-"+generateRevisionHash(o.doc, o.revisions);
o.spy(o, "value", {"id": "post1", "ok": true, "rev": o.rev},
"Postt + wrong revision");
o.jio.post(o.doc, o.f);
o.tick(o);
// check document
deepEqual(
localstorage.getItem(o.localpath + "/post1.3-wr3"),
null,
"Check document"
);
// check document
o.doc._id = "post1." + o.rev;
delete o.doc._rev;
deepEqual(
localstorage.getItem(o.localpath + "/post1." + o.rev),
o.doc,
"Check document"
);
// check document tree
o.doc_tree._id = "post1.revision_tree.json";
o.doc_tree.children.unshift({
"rev": "3-wr3", "status": "missing", "children": [{
"rev": o.rev, "status": "available", "children": []
}]
});
deepEqual(
localstorage.getItem(
o.localpath + "/post1.revision_tree.json"
),
o.doc_tree,
"Check document tree"
);
o.jio.stop();
});
test ("Put", function(){
......@@ -1550,19 +1639,21 @@ test("Put Attachment", function () {
// putAttachment without doc id
// error 20 -> document id required
o.spy(o, "status", 20, "PutAttachment without doc id");
o.spy(o, "status", 20, "PutAttachment without doc id" +
" -> 20 document id required");
o.jio.putAttachment({}, o.f);
o.tick(o);
// putAttachment without attachment id
// erorr 22 -> attachment id required
o.spy(o, "status", 22, "PutAttachment without attachment id");
o.spy(o, "status", 22, "PutAttachment without attachment id" +
" -> 22 attachment id required");
o.jio.putAttachment({"_id": "putattmt1"}, o.f);
o.tick(o);
// putAttachment without document
o.revisions = {"start": 0, "ids": []}
o.rev_hash = generateRevisionHash({"_id": "doc1/attmt1"},
o.rev_hash = generateRevisionHash({"_id": "doc1", "_attachment": "attmt1"},
o.revisions);
o.rev = "1-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev},
......@@ -1596,14 +1687,23 @@ test("Put Attachment", function () {
),
"", "Check attachment"
);
// adding a metadata to the document
o.doc = localstorage.getItem(
"jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
);
o.doc.title = "My Title";
localstorage.setItem(
"jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev,
o.doc
);
// update attachment
o.prev_rev = o.rev;
o.revisions = {"start": 1, "ids": [o.rev_hash]}
o.rev_hash = generateRevisionHash({
"_id": "doc1/attmt1",
"_id": "doc1",
"_data": "abc",
"_rev": o.prev_rev
"_attachment": "attmt1",
}, o.revisions);
o.rev = "2-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev},
......@@ -1622,6 +1722,7 @@ test("Put Attachment", function () {
),
{
"_id": "doc1." + o.rev,
"title": "My Title",
"_attachments": {
"attmt1": {
"length": 3,
......@@ -1646,9 +1747,9 @@ test("Put Attachment", function () {
o.prev_rev = o.rev;
o.revisions = {"start": 2, "ids": [o.rev_hash, o.revisions.ids[0]]}
o.rev_hash = generateRevisionHash({
"_id": "doc1/attmt2",
"_id": "doc1",
"_data": "def",
"_rev": o.prev_rev
"_attachment": "attmt2",
}, o.revisions);
o.rev = "3-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt2", "rev": o.rev},
......@@ -1667,6 +1768,7 @@ test("Put Attachment", function () {
),
{
"_id": "doc1." + o.rev,
"title": "My Title",
"_attachments": {
"attmt1": {
"length": 3,
......@@ -1710,12 +1812,14 @@ test ("Get", function(){
o.localpath = "jio/localstorage/urevget/arevget";
// get inexistent document
o.spy(o, "status", 404, "Get inexistent document (winner)");
o.spy(o, "status", 404, "Get inexistent document (winner)" +
" -> 404 Not Found");
o.jio.get({"_id": "get1"}, o.f);
o.tick(o);
// get inexistent attachment
o.spy(o, "status", 404, "Get inexistent attachment (winner)");
o.spy(o, "status", 404, "Get inexistent attachment (winner)" +
" -> 404 Not Found");
o.jio.get({"_id": "get1/get2"}, o.f);
o.tick(o);
......@@ -1723,15 +1827,16 @@ test ("Get", function(){
o.doctree = {"children":[{
"rev": "1-rev1", "status": "available", "children": []
}]};
o.doc_myget1 = {"_id": "get1", "title": "myGet1"};
o.doc_myget1 = {"_id": "get1.1-rev1", "title": "myGet1"};
localstorage.setItem(o.localpath+"/get1.revision_tree.json", o.doctree);
localstorage.setItem(o.localpath+"/get1.1-rev1", o.doc_myget1);
// get document
o.doc_myget1_cloned = clone(o.doc_myget1);
o.doc_myget1_cloned["_rev"] = "1-rev1";
o.doc_myget1_cloned["_revisions"] = {"start": 1, "ids": ["rev1"]};
o.doc_myget1_cloned["_revs_info"] = [{
o.doc_myget1_cloned._id = "get1";
o.doc_myget1_cloned._rev = "1-rev1";
o.doc_myget1_cloned._revisions = {"start": 1, "ids": ["rev1"]};
o.doc_myget1_cloned._revs_info = [{
"rev": "1-rev1", "status": "available"
}];
o.spy(o, "value", o.doc_myget1_cloned, "Get document (winner)");
......@@ -1748,14 +1853,15 @@ test ("Get", function(){
"rev": "2-rev3", "status": "available", "children": []
}]
}]};
o.doc_myget2 = {"_id": "get1", "title": "myGet2"};
o.doc_myget3 = {"_id": "get1", "title": "myGet3"};
o.doc_myget2 = {"_id": "get1.1-rev2", "title": "myGet2"};
o.doc_myget3 = {"_id": "get1.2-rev3", "title": "myGet3"};
localstorage.setItem(o.localpath+"/get1.revision_tree.json", o.doctree);
localstorage.setItem(o.localpath+"/get1.1-rev2", o.doc_myget2);
localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_myget3);
// get document
o.doc_myget3_cloned = clone(o.doc_myget3);
o.doc_myget3_cloned._id = "get1";
o.doc_myget3_cloned["_rev"] = "2-rev3";
o.doc_myget3_cloned["_revisions"] = {"start": 2, "ids": ["rev3","rev2"]};
o.doc_myget3_cloned["_revs_info"] = [{
......@@ -1772,7 +1878,8 @@ test ("Get", function(){
o.tick(o);
// get inexistent specific document
o.spy(o, "status", 404, "Get document (inexistent specific revision)");
o.spy(o, "status", 404, "Get document (inexistent specific revision)" +
" -> 404 Not Found");
o.jio.get({"_id": "get1", "_rev": "1-rev0"}, {
"revs_info": true, "revs": true, "conflicts": true,
}, o.f);
......@@ -1780,6 +1887,7 @@ test ("Get", function(){
// get specific document
o.doc_myget2_cloned = clone(o.doc_myget2);
o.doc_myget2_cloned._id = "get1";
o.doc_myget2_cloned["_rev"] = "1-rev2";
o.doc_myget2_cloned["_revisions"] = {"start": 1, "ids": ["rev2"]};
o.doc_myget2_cloned["_revs_info"] = [{
......@@ -1793,18 +1901,16 @@ test ("Get", function(){
o.tick(o);
// adding an attachment
o.attmt_myget2 = {
o.attmt_myget3 = {
"get2": {
"length": 3,
"digest": "md5-dontcare",
"revpos": 1
"content_type": "oh/yeah"
}
};
o.doc_myget2["_attachments"] = o.attmt_myget2;
o.doc_myget3["_attachments"] = o.attmt_myget2;
localstorage.setItem(o.localpath+"/get1.1-rev2", o.doc_myget2);
o.doc_myget3._attachments = o.attmt_myget3;
localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_myget3);
localstorage.setItem(o.localpath+"/get1.1-rev2/get2", "abc");
localstorage.setItem(o.localpath+"/get1.2-rev3/get2", "abc");
// get attachment winner
o.spy(o, "value", "abc", "Get attachment (winner)");
......@@ -1812,7 +1918,8 @@ test ("Get", function(){
o.tick(o);
// get inexistent attachment specific rev
o.spy(o, "status", 404, "Get inexistent attachment (specific revision)");
o.spy(o, "status", 404, "Get inexistent attachment (specific revision)" +
" -> 404 Not Found");
o.jio.get({"_id": "get1/get2", "_rev": "1-rev1"}, {
"revs_info": true, "revs": true, "conflicts": true,
}, o.f);
......@@ -1820,13 +1927,13 @@ test ("Get", function(){
// get attachment specific rev
o.spy(o, "value", "abc", "Get attachment (specific revision)");
o.jio.get({"_id": "get1/get2", "_rev": "1-rev2"}, {
o.jio.get({"_id": "get1/get2", "_rev": "2-rev3"}, {
"revs_info": true, "revs": true, "conflicts": true,
}, o.f);
o.tick(o);
// get document with attachment (specific revision)
o.doc_myget2_cloned["_attachments"] = o.attmt_myget2;
delete o.doc_myget2_cloned._attachments;
o.spy(o, "value", o.doc_myget2_cloned,
"Get document which have an attachment (specific revision)");
o.jio.get({"_id": "get1", "_rev": "1-rev2"}, {
......@@ -1835,7 +1942,7 @@ test ("Get", function(){
o.tick(o);
// get document with attachment (winner)
o.doc_myget3_cloned["_attachments"] = o.attmt_myget2;
o.doc_myget3_cloned._attachments = o.attmt_myget3;
o.spy(o, "value", o.doc_myget3_cloned,
"Get document which have an attachment (winner)");
o.jio.get({"_id": "get1"},
......@@ -1862,194 +1969,139 @@ test ("Remove", function(){
o.localpath = "jio/localstorage/urevrem/arevrem";
// 1. remove document without revision
o.spy (o, "status", 404,
"Remove document (no doctree, no revision)");
o.spy(o, "status", 409, "Remove document without revision " +
"-> 409 Conflict");
o.jio.remove({"_id":"remove1"}, o.f);
o.tick(o);
// 2. remove attachment without revision
o.spy (o, "status", 404,
"Remove attachment (no doctree, no revision)");
o.spy(o, "status", 409, "Remove attachment without revision " +
"-> 409 Conflict");
o.jio.remove({"_id":"remove1/remove2"}, o.f);
o.tick(o);
// adding two documents
o.doc_myremove1 = {"_id": "remove1", "title": "myRemove1"};
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2"};
o.very_old_rev = "1-veryoldrev";
// adding a document with attachments
o.doc_myremove1 = {
"_id": "remove1.1-veryoldrev",
"title": "myRemove1"
};
localstorage.setItem(o.localpath+"/remove1."+o.very_old_rev,
localstorage.setItem(o.localpath + "/remove1.1-veryoldrev",
o.doc_myremove1);
localstorage.setItem(o.localpath+"/remove1.1-rev2", o.doc_myremove1);
// add attachment
o.attmt_myremove1 = {
"remove2": {
"length": 3,
"digest": "md5-dontcare",
"revpos":1
},
o.doc_myremove1._id = "remove1.2-oldrev";
o.attachment_remove2 = {
"length": 3,
"digest": "md5-dontcare",
"content_type": "oh/yeah"
}
o.attachment_remove3 = {
"length": 5,
"digest": "md5-865f5cc7fbd7854902eae9d8211f178a",
"content_type": "he/ho"
}
o.doc_myremove1._attachments = {
"remove2": o.attachment_remove2,
"remove3": o.attachment_remove3
};
o.doc_myremove1 = {"_id": "remove1", "title": "myRemove1",
"_attachments":o.attmt_myremove1};
o.revisions = {"start":1,"ids":[o.very_old_rev.split('-'),[1]]}
o.old_rev = "2-"+generateRevisionHash(o.doc_myremove1, o.revisions);
localstorage.setItem(o.localpath+"/remove1."+o.old_rev, o.doc_myremove1);
localstorage.setItem(o.localpath+"/remove1."+o.old_rev+"/remove2", "xyz");
localstorage.setItem(o.localpath + "/remove1.2-oldrev",
o.doc_myremove1);
localstorage.setItem(o.localpath + "/remove1.2-oldrev/remove2", "abc");
localstorage.setItem(o.localpath + "/remove1.2-oldrev/remove3", "defgh");
o.doctree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": []
// add document tree
o.doctree = {
"children": [{
"rev": "1-veryoldrev", "status": "available", "children": [{
"rev": "2-oldrev", "status": "available", "children": []
}]
},{
"rev": "1-rev2", "status": "available", "children": []
}]};
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree);
// 3. remove non existing attachment with revision
o.spy(o, "status", 404,
"Remove NON-existing attachment (revision)");
o.jio.remove({"_id":"remove1.1-rev2/remove0","_rev":o.old_rev}, o.f);
o.tick(o);
o.revisions = {"start": 2, "ids":[
o.old_rev.split('-')[1], o.very_old_rev.split('-')[1]
]};
o.doc_myremove1 = {"_id":"remove1/remove2","_rev":o.old_rev};
o.rev = "3-"+generateRevisionHash(o.doc_myremove1, o.revisions);
}]
};
localstorage.setItem(o.localpath + "/remove1.revision_tree.json",
o.doctree);
// 4. remove existing attachment with revision
o.spy (o, "value", {"ok": true, "id": "remove1."+o.rev, "rev": o.rev},
"Remove existing attachment (revision)");
o.jio.remove({"_id":"remove1/remove2","_rev":o.old_rev}, o.f);
// 3. remove inexistent attachment
o.spy(o, "status", 404, "Remove inexistent attachment -> 404 Not Found");
o.jio.remove({"_id": "remove1/remove0", "_rev": "2-oldrev"}, o.f);
o.tick(o);
o.testtree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": [{
"rev": o.rev, "status": "available", "children": []
}]
// 4. remove existing attachment
o.rev_hash = generateRevisionHash({
"_id": "remove1",
"_attachment": "remove2",
}, {"start": 2, "ids": ["oldrev", "veryoldrev"]});
o.spy (o, "value",
{"ok": true, "id": "remove1/remove2", "rev": "3-" + o.rev_hash},
"Remove existing attachment");
o.jio.remove({"_id":"remove1/remove2", "_rev": "2-oldrev"}, o.f);
o.tick(o);
o.doctree = {
"children":[{
"rev": "1-veryoldrev", "status": "available", "children": [{
"rev": "2-oldrev", "status": "available", "children": [{
"rev": "3-" + o.rev_hash, "status": "available", "children": []
}]
}]
},{
"rev": "1-rev2", "status": "available", "children": []
}]};
}]
};
// 5. check if document tree has been updated correctly
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1.revision_tree.json"
),o.testtree, "Check document tree");
o.localpath + "/remove1.revision_tree.json"
), o.doctree, "Check document tree");
// 6. check if attachment has been removed
// 6. check if the attachment still exists
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.rev+"/remove2"
), null, "Check attachment");
o.localpath + "/remove1.2-oldrev/remove2"
), "abc", "Check attachment -> still exists");
// 7. check if document is updated
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.rev
), {"_id": "remove1."+o.rev, "title":"myRemove1"}, "Check document");
// add another attachment
o.attmt_myremove2 = {
"remove3": {
"length": 3,
"digest": "md5-hello123"
},
"revpos":1
};
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2",
"_attachments":o.attmt_myremove2};
o.revisions = {"start":1,"ids":["rev2"] };
o.second_old_rev = "2-"+generateRevisionHash(o.doc_myremove2, o.revisions);
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev,
o.doc_myremove2);
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev+"/remove3",
"stu");
o.doctree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": [{
"rev": o.rev, "status": "available", "children":[]
}]
}]
},{
"rev": "1-rev2", "status": "available", "children": [{
"rev": o.second_old_rev, "status": "available", "children":[]
}]
}]};
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree);
// 8. remove non existing attachment without revision
o.spy (o,"status", 409,
"409 - Removing non-existing-attachment (no revision)");
o.jio.remove({"_id":"remove1/remove0"}, o.f);
o.tick(o);
o.revisions = {"start":2,"ids":[o.second_old_rev.split('-')[1],"rev2"]};
o.doc_myremove3 = {"_id":"remove1/remove3","_rev":o.second_old_rev};
o.second_rev = "3-"+generateRevisionHash(o.doc_myremove3, o.revisions);
// 9. remove existing attachment without revision
o.spy (o,"status", 409, "409 - Removing existing attachment (no revision)");
o.jio.remove({"_id":"remove1/remove3"}, o.f);
o.tick(o);
// 10. remove wrong revision
o.spy (o,"status", 409, "409 - Removing document (false revision)");
o.jio.remove({"_id":"remove1","_rev":"1-rev2"}, o.f);
o.tick(o);
o.revisions = {"start": 3, "ids":[
o.rev.split('-')[1],
o.old_rev.split('-')[1],o.very_old_rev.split('-')[1]
]};
o.doc_myremove4 = {"_id":"remove1","_rev":o.rev};
o.second_new_rev = "4-"+
generateRevisionHash(o.doc_myremove4, o.revisions, true);
// 11. remove document version with revision
o.spy (o, "value", {"ok": true, "id": "remove1", "rev":
o.second_new_rev},
"Remove document (with revision)");
o.jio.remove({"_id":"remove1", "_rev":o.rev}, o.f);
o.localpath + "/remove1.3-" + o.rev_hash
), {
"_id": "remove1.3-" + o.rev_hash,
"title":"myRemove1",
"_attachments": {"remove3": o.attachment_remove3}
}, "Check document");
// 8. remove document with wrong revision
o.spy(o, "status", 409, "Remove document with wrong revision " +
"-> 409 Conflict");
o.jio.remove({"_id":"remove1", "_rev": "1-a"}, o.f);
o.tick(o);
// 9. remove attachment wrong revision
o.spy(o, "status", 409, "Remove attachment with wrong revision " +
"-> 409 Conflict");
o.jio.remove({"_id":"remove1/remove2", "_rev": "1-a"}, o.f);
o.tick(o);
// 10. remove document
o.last_rev = "3-" + o.rev_hash;
o.rev_hash = generateRevisionHash(
{"_id": "remove1"},
{"start": 3, "ids": [o.rev_hash, "oldrev", "veryoldrev"]},
true
);
o.spy(o, "value", {"ok": true, "id": "remove1", "rev": "4-" + o.rev_hash},
"Remove document");
o.jio.remove({"_id":"remove1", "_rev": o.last_rev}, o.f);
o.tick(o);
o.testtree["children"][0]["children"][0]["children"][0]["children"].push({
"rev": o.second_new_rev,
"status": "deleted",
"children": []
});
o.testtree["children"][1]["children"].push({
"rev":o.second_old_rev,
"status":"available",
"children":[]
// 11. check document tree
o.doctree.children[0].children[0].children[0].children.unshift({
"rev": "4-" + o.rev_hash,
"status": "deleted",
"children": []
});
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1.revision_tree.json"
), o.testtree, "Check document tree");
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.second_new_rev+"/remove2"
), null, "Check attachment");
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.second_new_rev
), null, "Check document");
// remove document without revision
o.spy (o,"status", 409, "409 - Removing document (no revision)");
o.jio.remove({"_id":"remove1"}, o.f);
o.tick(o);
deepEqual(localstorage.getItem(o.localpath + "/remove1.revision_tree.json"),
o.doctree, "Check document tree");
o.jio.stop();
});
module ( "Jio Revision Storage + Local Storage" );
test ("Scenario", function(){
var o = generateTools(this);
......@@ -2196,9 +2248,7 @@ test ("Scenario", function(){
module ("JIO Replicate Revision Storage");
var testReplicateRevisionStorageGenerator = function (
sinon, jio_description, document_name_have_revision
) {
var testReplicateRevisionStorage = function (sinon, jio_description) {
var o = generateTools(sinon), leavesAction, generateLocalPath;
......@@ -2253,15 +2303,15 @@ module ("JIO Replicate Revision Storage");
// check document
o.doc._id = o.uuid;
o.revision = {"start": 0, "ids": []};
o.rev = "1-1";
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.revisions = {"start": 0, "ids": []};
o.rev_hash = generateRevisionHash(o.doc, o.revisions);
o.rev = "1-" + o.rev_hash;
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
deepEqual(o.response_rev, o.rev, "Check revision");
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
doc._id += "." + o.rev;
suffix = "." + o.rev;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
......@@ -2274,10 +2324,10 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", {
"_id": o.uuid,
"title": "post document without id",
"_rev": "1-1",
"_revisions": {"start": 1, "ids": ["1"]},
"_revs_info": [{"rev": "1-1", "status": "available"}]
}, "Get the previous document (without revision)");
"_rev": o.rev,
"_revisions": {"start": 1, "ids": [o.rev_hash]},
"_revs_info": [{"rev": o.rev, "status": "available"}]
}, "Get the generated document, the winner");
o.jio.get({"_id": o.uuid}, {
"conflicts": true,
"revs": true,
......@@ -2287,8 +2337,12 @@ module ("JIO Replicate Revision Storage");
// post a new document with id
o.doc = {"_id": "doc1", "title": "post new doc with id"};
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Post document (with id)");
o.rev1_1_hash = generateRevisionHash(o.doc, o.revisions);
o.rev1_1 = "1-" + o.rev1_1_hash;
o.rev1_1_history = {"start": 1, "ids": [o.rev1_1_hash]};
o.rev1_1_revs_info = [{"rev": o.rev1_1, "status": "available"}];
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev1_1},
"Post new document with an id");
o.jio.post(o.doc, o.f);
o.tick(o);
......@@ -2297,15 +2351,11 @@ module ("JIO Replicate Revision Storage");
// 1-1
// check document
o.local_rev_hash = generateRevisionHash(o.doc, o.revision);
o.local_rev = "1-" + o.local_rev_hash;
o.specific_rev_hash = o.local_rev_hash;
o.specific_rev = o.local_rev;
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
doc._id += "." + o.rev1_1;
suffix = "." + o.rev1_1;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
......@@ -2318,9 +2368,9 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", {
"_id": "doc1",
"title": "post new doc with id",
"_rev": "1-1",
"_revisions": {"start": 1, "ids": ["1"]},
"_revs_info": [{"rev": "1-1", "status": "available"}]
"_rev": o.rev1_1,
"_revisions": {"start": 1, "ids": [o.rev1_1_hash]},
"_revs_info": [{"rev": o.rev1_1, "status": "available"}]
}, "Get the previous document (without revision)");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
......@@ -2331,8 +2381,11 @@ module ("JIO Replicate Revision Storage");
// post same document without revision
o.doc = {"_id": "doc1", "title": "post same document without revision"};
o.rev = "1-2";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
o.rev1_2_hash = generateRevisionHash(o.doc, o.revisions);
o.rev1_2 = "1-" + o.rev1_2_hash;
o.rev1_2_history = {"start": 1, "ids": [o.rev1_2_hash]};
o.rev1_2_revs_info = [{"rev": o.rev1_2, "status": "available"}];
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev1_2},
"Post same document (without revision)");
o.jio.post(o.doc, o.f);
o.tick(o);
......@@ -2342,12 +2395,11 @@ module ("JIO Replicate Revision Storage");
// 1-1 1-2
// check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
doc._id += "." + o.rev1_2;
suffix = "." + o.rev1_2;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
......@@ -2357,9 +2409,17 @@ module ("JIO Replicate Revision Storage");
});
// post a new revision
o.doc = {"_id": "doc1", "title": "post new revision", "_rev": o.rev};
o.rev = "2-3";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
o.doc = {"_id": "doc1", "title": "post new revision", "_rev": o.rev1_2};
o.revisions.start += 1;
o.revisions.ids.unshift(o.rev1_2_hash);
o.rev2_3_hash = generateRevisionHash(o.doc, o.revisions);
o.rev2_3 = "2-" + o.rev2_3_hash;
o.rev2_3_history = clone(o.rev1_2_history);
o.rev2_3_history.start += 1;
o.rev2_3_history.ids.unshift(o.rev2_3_hash);
o.rev2_3_revs_info = clone(o.rev1_2_revs_info);
o.rev2_3_revs_info.unshift({"rev": o.rev2_3, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev2_3},
"Post document (with revision)");
o.jio.post(o.doc, o.f);
o.tick(o);
......@@ -2371,17 +2431,12 @@ module ("JIO Replicate Revision Storage");
// 2-3
// check document
o.revision.start += 1;
o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-"));
o.doc._rev = o.local_rev;
o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision);
o.specific_rev_conflict = o.local_rev;
o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc);
delete doc._rev;
if (param.revision) {
doc._id += "." + o.local_rev;
suffix = "." + o.local_rev;
doc._id += "." + o.rev2_3;
suffix = "." + o.rev2_3;
}
deepEqual(
localstorage.getItem(generateLocalPath(storage_description) +
......@@ -2394,31 +2449,15 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", {
"_id": "doc1",
"title": "post same document without revision",
"_rev": "1-2",
"_revisions": {"start": 1, "ids": ["2"]},
"_revs_info": [{"rev": "1-2", "status": "available"}],
"_conflicts": ["1-1"]
"_rev": o.rev1_2,
"_revisions": {"start": 1, "ids": [o.rev1_2_hash]},
"_revs_info": [{"rev": o.rev1_2, "status": "available"}],
"_conflicts": [o.rev1_1]
}, "Get the previous document (with revision)");
o.jio.get({"_id": "doc1", "_rev": "1-2"}, {
"conflicts": true,
"revs": true,
"revs_info": true,
}, o.f);
o.tick(o);
// get the post document with specific revision
o.spy(o, "value", {
"_id": "doc1",
"title": "post new doc with id",
"_rev": o.specific_rev,
"_revisions": {"start": 1, "ids": [o.specific_rev_hash]},
"_revs_info": [{"rev": o.specific_rev, "status": "available"}],
"_conflicts": [o.specific_rev_conflict]
}, "Get a previous document (with local storage revision)");
o.jio.get({"_id": "doc1", "_rev": o.specific_rev}, {
o.jio.get({"_id": "doc1", "_rev": o.rev1_2}, {
"conflicts": true,
"revs": true,
"revs_info": true,
"revs_info": true
}, o.f);
o.tick(o);
......@@ -2429,8 +2468,11 @@ module ("JIO Replicate Revision Storage");
// put document without rev
o.doc = {"_id": "doc1", "title": "put new document"};
o.rev = "1-4";
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev},
o.rev1_4_hash = generateRevisionHash(o.doc, {"start": 0, "ids": []});
o.rev1_4 = "1-" + o.rev1_4_hash;
o.rev1_4_history = {"start": 1, "ids": [o.rev1_4_hash]};
o.rev1_4_revs_info = [{"rev": o.rev1_4, "status": "available"}];
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev1_4},
"Put document without rev")
o.jio.put(o.doc, o.f);
o.tick(o);
......@@ -2442,10 +2484,14 @@ module ("JIO Replicate Revision Storage");
// 2-3
// put new revision
o.doc = {"_id": "doc1", "title": "put new revision", "_rev": "1-4"};
o.rev = "2-5";
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev},
"Put document without rev")
o.doc = {"_id": "doc1", "title": "put new revision", "_rev": o.rev1_4};
o.rev2_5_hash = generateRevisionHash(o.doc, o.rev1_4_history);
o.rev2_5 = "2-" + o.rev2_5_hash
o.rev2_5_history = {"start": 2, "ids": [o.rev2_5_hash, o.rev1_4_hash]};
o.rev2_5_revs_info = clone(o.rev1_4_revs_info);
o.rev2_5_revs_info.unshift({"rev": o.rev2_5, "status": "available"});
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev2_5},
"Put new revision")
o.jio.put(o.doc, o.f);
o.tick(o);
......@@ -2456,39 +2502,362 @@ module ("JIO Replicate Revision Storage");
// 2-3 2-5
// putAttachment to inexistent document
o.doc = {
"_id": "doc2",
"_mimetype": "text/plain",
"_data": "doc 2 - attachment 1",
"_attachment": "attachment1"
};
o.rev_hash = generateRevisionHash(o.doc, {"start": 0, "ids": []});
o.rev = "1-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc2/attachment1", "rev": o.rev},
"Put an attachment to an inexistent document");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.putAttachment(o.doc, o.f);
o.tick(o);
// putAttachment
o.doc = {
"_id": "doc1",
"_mimetype": "text/plain",
"_data": "doc 1 - attachment 1",
"_attachment": "attachment1",
"_rev": o.rev2_5
};
o.rev3_6_hash = generateRevisionHash(o.doc, o.rev2_5_history);
o.rev3_6 = "3-" + o.rev3_6_hash;
o.rev3_6_history = clone(o.rev2_5_history);
o.rev3_6_history.start += 1;
o.rev3_6_history.ids.unshift(o.rev3_6_hash);
o.rev3_6_revs_info = clone(o.rev2_5_revs_info);
o.rev3_6_revs_info.unshift({"rev": o.rev3_6, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1/attachment1", "rev": o.rev3_6},
"Put an attachment to the first document");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.putAttachment(o.doc, o.f);
o.tick(o);
// __/__
// / | \
// 1-1 1-2 1-4
// | |
// 2-3 2-5
// |
// 3-6+a1
// get document
o.doc = {
"_id": "doc1",
"_rev": o.rev3_6,
"_revisions": o.rev3_6_history,
"_revs_info": o.rev3_6_revs_info,
"_conflicts": [o.rev2_3, o.rev1_1],
"_attachments": {
"attachment1": {
"length": "doc 1 - attachment 1".length,
"content_type": "text/plain",
"digest": "md5-0505c1fb6aae02dd1695d33841726564"
}
},
"title": "put new revision"
};
o.spy(o, "value", o.doc, "Get document, the winner");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// get attachment
o.doc = {
"_id": "doc1",
"_attachment": "attachment1"
};
o.spy(o, "value", "doc 1 - attachment 1", "Get the winner's attachment");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.get(o.doc, o.f);
o.tick(o);
// put document
// get document
o.doc = {
"_id": "doc1",
"_rev": o.rev3_6,
"title": "Put revision, attachment must be copied"
};
o.rev4_7_hash = generateRevisionHash(o.doc, o.rev3_6_history);
o.rev4_7 = "4-" + o.rev4_7_hash;
o.rev4_7_history = clone(o.rev3_6_history);
o.rev4_7_history.start += 1;
o.rev4_7_history.ids.unshift(o.rev4_7_hash);
o.rev4_7_revs_info = clone(o.rev3_6_revs_info);
o.rev4_7_revs_info.unshift({"rev": o.rev4_7, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev4_7},
"Update document, attachment should be copied");
o.jio.put(o.doc, o.f);
o.tick(o);
// __/__
// / | \
// 1-1 1-2 1-4
// | |
// 2-3 2-5
// |
// 3-6+a1
// |
// 4-7+a1
// get document, attachment must be copied
o.doc = {
"_id": "doc1",
"_rev": o.rev4_7,
"title": o.doc.title,
"_attachments": {
"attachment1": {
"length": "doc 1 - attachment 1".length,
"content_type": "text/plain",
"digest": "md5-0505c1fb6aae02dd1695d33841726564"
}
},
"_conflicts": [o.rev2_3, o.rev1_1],
"_revisions": o.rev4_7_history,
"_revs_info": o.rev4_7_revs_info
};
o.spy(o, "value", o.doc,
"Get the new winner document and its attachment metadata");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// get attachment
o.doc = {
"_id": "doc1",
"_attachment": "attachment1"
};
o.spy(o, "value", "doc 1 - attachment 1",
"Get the winner's attachment again");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.get(o.doc, o.f);
o.tick(o);
// remove attachment
// get document
// get inexistent attachment
o.doc = {
"_id": "doc1",
"_attachment": "attachment1",
"_rev": o.rev4_7
};
o.rev5_8_hash = generateRevisionHash(o.doc, o.rev4_7_history);
o.rev5_8 = "5-" + o.rev5_8_hash;
o.rev5_8_history = clone(o.rev4_7_history);
o.rev5_8_history.start += 1;
o.rev5_8_history.ids.unshift(o.rev5_8_hash);
o.rev5_8_revs_info = clone(o.rev4_7_revs_info);
o.rev5_8_revs_info.unshift({"rev": o.rev5_8, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1/attachment1", "rev": o.rev5_8},
"Remove attachment");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.remove(o.doc, o.f);
o.tick(o);
// remove document and conflict
o.rev = "3-6";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document");
o.jio.remove({"_id": "doc1", "_rev": "2-5"}, o.f);
// __/__
// / | \
// 1-1 1-2 1-4
// | |
// 2-3 2-5
// |
// 3-6+a1
// |
// 4-7+a1
// |
// 5-8
// get document to check attachment existence
o.doc = {
"_id": "doc1",
"_rev": o.rev5_8,
"title": "Put revision, attachment must be copied",
"_conflicts": [o.rev2_3, o.rev1_1],
"_revisions": o.rev5_8_history,
"_revs_info": o.rev5_8_revs_info
};
o.spy(o, "value", o.doc,
"Get the new winner document, no attachment must be provided");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// remove document and conflict
o.rev = "3-7";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document");
o.jio.remove({"_id": "doc1", "_rev": "2-3"}, o.f);
// get specific document
o.doc = {
"_id": "doc1",
"_rev": o.rev4_7,
"title": o.doc.title,
"_attachments": {
"attachment1": {
"length": "doc 1 - attachment 1".length,
"content_type": "text/plain",
"digest": "md5-0505c1fb6aae02dd1695d33841726564"
}
},
"_conflicts": [o.rev2_3, o.rev1_1],
"_revisions": o.rev4_7_history,
"_revs_info": o.rev4_7_revs_info
};
o.spy(o, "value", o.doc,
"Get the new winner document and its attachment metadata");
o.jio.get({"_id": "doc1", "_rev": o.rev4_7}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// remove document
o.rev = "2-8";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document");
o.jio.remove({"_id": "doc1", "_rev": "1-1"}, o.f);
// get inexistent attachment
o.spy(o, "status", 404, "Get inexistent winner attachment" +
" -> 404 Not Found");
o.jio.get({"_id": "doc1/attachment1"}, o.f);
o.tick(o);
// get specific attachment
o.doc = {
"_id": "doc1",
"_attachment": "attachment1",
"_rev": o.rev3_6
};
o.spy(o, "value", "doc 1 - attachment 1",
"Get a specific attachment");
o.doc._id += "/" + o.doc._attachment;
delete o.doc._attachment;
o.jio.get(o.doc, o.f);
o.tick(o);
// remove specific document and conflict
o.doc = {"_id": "doc1", "_rev": o.rev1_1};
// generate with deleted_flag
o.rev2_9_hash = generateRevisionHash(o.doc, o.rev1_1_history, true);
o.rev2_9 = "2-" + o.rev2_9_hash;
o.rev2_9_history = clone(o.rev1_1_history);
o.rev2_9_history.start += 1;
o.rev2_9_history.ids.unshift(o.rev2_9_hash);
o.rev2_9_revs_info = clone(o.rev1_1_revs_info);
o.rev2_9_revs_info.unshift({"rev": o.rev2_9, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev2_9},
"Remove specific document, and one conflict");
o.jio.remove(o.doc, o.f);
o.tick(o);
// __/___
// / | \
// 1-1 1-2 1-4
// | | |
// D2-9 2-3 2-5
// |
// 3-6+a1
// |
// 4-7+a1
// |
// 5-8
// remove specific document and conflict
o.doc = {"_id": "doc1", "_rev": o.rev2_3};
o.rev3_10_hash = generateRevisionHash(o.doc, o.rev2_3_history, true);
o.rev3_10 = "3-" + o.rev3_10_hash;
o.rev3_10_history = clone(o.rev2_3_history);
o.rev3_10_history.start += 1;
o.rev3_10_history.ids.unshift(o.rev3_10_hash);
o.rev3_10_revs_info = clone(o.rev2_3_revs_info);
o.rev3_10_revs_info.unshift({"rev": o.rev3_10, "status": "available"});
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev3_10},
"Remove specific document, and one conflict");
o.jio.remove(o.doc, o.f);
o.tick(o);
// ___/____
// / | \
// 1-1 1-2 1-4
// | | |
// D2-9 2-3 2-5
// | |
// D3-10 3-6+a1
// |
// 4-7+a1
// |
// 5-8
// get document no more conflict
o.doc = {
"_id": "doc1",
"_rev": o.rev5_8,
"title": "Put revision, attachment must be copied",
"_revisions": o.rev5_8_history,
"_revs_info": o.rev5_8_revs_info
};
o.spy(o, "value", o.doc,
"Get the new winner document, no more conflicts");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// remove document
o.doc = {
"_id": "doc1",
"_rev": o.rev5_8
};
o.rev6_11_hash = generateRevisionHash(o.doc, o.rev5_8_history, true);
o.rev6_11 = "6-" + o.rev6_11_hash;
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev6_11},
"Remove the last document");
o.jio.remove(o.doc, o.f);
o.tick(o);
// ___/____
// / | \
// 1-1 1-2 1-4
// | | |
// D2-9 2-3 2-5
// | |
// D3-10 3-6+a1
// |
// 4-7+a1
// |
// 5-8
// |
// D6-11
// get inexistent document
o.spy(o, "status", 404, "Get inexistent document");
o.spy(o, "status", 404, "Get inexistent document -> 404 Not Found");
o.jio.get({"_id": "doc3"}, {
"conflicts": true,
"revs": true,
"revisions": true
}, o.f);
o.tick(o);
// get specific deleted document
o.spy(o, "status", 404, "Get deleted document -> 404 Not Found");
o.jio.get({"_id": "doc1", "rev": o.rev3_10}, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// get specific deleted document
o.spy(o, "status", 404, "Get deleted document -> 404 Not Found");
o.jio.get({"_id": "doc1"}, {
"conflicts": true,
"revs": true,
......@@ -2501,7 +2870,7 @@ module ("JIO Replicate Revision Storage");
};
test ("[Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
testReplicateRevisionStorage(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
......@@ -2514,7 +2883,7 @@ module ("JIO Replicate Revision Storage");
});
});
test("[Replicate Revision + Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
testReplicateRevisionStorage(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "replicaterevision",
......@@ -2530,27 +2899,27 @@ module ("JIO Replicate Revision Storage");
});
});
test ("2x [Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
testReplicateRevisionStorage(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "ureprevlocloc1",
"application_name": "areprevloc1"
"application_name": "areprevlocloc1"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "ureprevlocloc2",
"application_name": "areprevloc2"
"application_name": "areprevlocloc2"
}
}]
});
});
test("2x [Replicate Rev + 2x [Rev + Local]] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
testReplicateRevisionStorage(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "replicaterevision",
......@@ -2589,6 +2958,269 @@ module ("JIO Replicate Revision Storage");
}]
});
});
var replicateStorageSynchronisationGenerator = function (
that,
description,
index
) {
var o = generateTools(that);
o.jio = JIO.newJio(description);
o.localpath1 = "jio/localstorage/usyncreprevlocloc1/" + index;
o.localpath2 = "jio/localstorage/usyncreprevlocloc2/" + index;
o.localpath3 = "jio/localstorage/usyncreprevlocloc3/" + index;
o.localpath4 = "jio/localstorage/usyncreprevlocloc4/" + index;
// add documents to localstorage
o.doctree1_1 = {
"children": [{
"rev": "1-111",
"status": "available",
"children": [],
}]
};
o.doc1_1 = {"_id": "doc1.1-111", "title": "A"};
localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
o.doctree1_1);
localstorage.setItem(o.localpath2 + "/doc1.revision_tree.json",
o.doctree1_1);
localstorage.setItem(o.localpath3 + "/doc1.revision_tree.json",
o.doctree1_1);
localstorage.setItem(o.localpath4 + "/doc1.revision_tree.json",
o.doctree1_1);
localstorage.setItem(o.localpath1 + "/" + o.doc1_1._id, o.doc1_1);
localstorage.setItem(o.localpath2 + "/" + o.doc1_1._id, o.doc1_1);
localstorage.setItem(o.localpath3 + "/" + o.doc1_1._id, o.doc1_1);
localstorage.setItem(o.localpath4 + "/" + o.doc1_1._id, o.doc1_1);
// no synchronisation
o.spy(o, "value", {"ok": true, "id": "doc1"},
"Check document");
o.jio.check({"_id": "doc1"}, o.f);
o.tick(o);
o.spy(o, "value", {"ok": true, "id": "doc1"},
"Repair document");
o.jio.repair({"_id": "doc1"}, o.f);
o.tick(o);
// check documents from localstorage
deepEqual(
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
o.doctree1_1,
"Check revision tree 1, no synchro done"
);
deepEqual(
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
o.doctree1_1,
"Check revision tree 2, no synchro done"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.revision_tree.json"),
o.doctree1_1,
"Check revision tree 3, no synchro done"
);
deepEqual(
localstorage.getItem(o.localpath4 + "/doc1.revision_tree.json"),
o.doctree1_1,
"Check revision tree 4, no synchro done"
);
// add documents to localstorage
o.doctree2_2 = clone(o.doctree1_1);
o.doctree2_2.children[0].children.push({
"rev": "2-222",
"status": "available",
"children": []
});
o.doc2_2 = {
"_id": "doc1.2-222",
"title": "B",
"_attachments": {
"haha": {
"length": 3,
"digest": "md5-900150983cd24fb0d6963f7d28e17f72",
"content_type": "text/plain"
}
}
};
localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
o.doctree2_2);
localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id, o.doc2_2)
localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id + "/haha", "abc");
// document synchronisation without conflict
o.spy(o, "status", 41, "Check document");
o.jio.check({"_id": "doc1"}, o.f);
o.tick(o, 50000);
o.spy(o, "value", {"ok": true, "id": "doc1"},
"Repair document");
o.jio.repair({"_id": "doc1"}, o.f);
o.tick(o, 50000);
// check documents from localstorage
deepEqual(
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
o.doctree2_2,
"Check revision tree 1, no synchro done"
);
deepEqual(
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
o.doctree2_2,
"Check revision tree 2, revision synchro done"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.revision_tree.json"),
o.doctree2_2,
"Check revision tree 3, revision synchro done"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.2-222"),
o.doc2_2,
"Check document 3"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.2-222/haha"),
"abc",
"Check attachment 3"
);
deepEqual(
localstorage.getItem(o.localpath4 + "/doc1.revision_tree.json"),
o.doctree2_2,
"Check revision tree 4, revision synchro done"
);
// add documents to localstorage
o.doctree2_3 = clone(o.doctree2_2);
o.doctree2_3.children[0].children.unshift({
"rev": "2-223",
"status": "available",
"children": []
});
o.doc2_3 = {"_id": "doc1.2-223", "title": "C"};
localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
o.doctree2_3);
localstorage.setItem(o.localpath1 + "/" + o.doc2_3._id, o.doc2_3);
// document synchronisation with conflict
o.spy(o, "status", 41, "Check document");
o.jio.check({"_id": "doc1"}, o.f);
o.tick(o, 50000);
o.spy(o, "value", {"ok": true, "id": "doc1"},
"Repair document");
o.jio.repair({"_id": "doc1"}, o.f);
o.tick(o, 50000);
// check documents from localstorage
deepEqual(
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
o.doctree2_3,
"Check revision tree 1, rev synchro"
);
deepEqual(
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
o.doctree2_3,
"Check revision tree 2, rev synchro"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.revision_tree.json"),
o.doctree2_3,
"Check revision tree 3, rev synchro"
);
deepEqual(
localstorage.getItem(o.localpath3 + "/doc1.2-223"),
o.doc2_3,
"Check document 3"
);
deepEqual(
localstorage.getItem(o.localpath4 + "/doc1.revision_tree.json"),
o.doctree2_3,
"Check revision tree 4, rev synchro"
);
o.jio.stop();
};
test("Storage Synchronisation (Repair) 4x [Rev + Local]", function () {
replicateStorageSynchronisationGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc1",
"application_name": "1"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc2",
"application_name": "1"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc3",
"application_name": "1"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc4",
"application_name": "1"
}
}]
}, "1");
});
test("Storage Synchronisation (Repair) 2x [Rep 2x [Rev + Local]]",
function () {
replicateStorageSynchronisationGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc1",
"application_name": "2"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc2",
"application_name": "2"
}
}]
}, {
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc3",
"application_name": "2"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc4",
"application_name": "2"
}
}]
}]
}, "2");
});
/*
module ("Jio DAVStorage");
......
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