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) { ...@@ -20,13 +20,11 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
priv.storage_list_key = "storage_list"; priv.storage_list_key = "storage_list";
priv.storage_list = spec[priv.storage_list_key]; priv.storage_list = spec[priv.storage_list_key];
my.env = my.env || spec.env || {};
priv.emptyFunction = function () {}; priv.emptyFunction = function () {};
that.specToStore = function () { that.specToStore = function () {
var o = {}; var o = {};
o[priv.storage_list_key] = priv.storage_list; o[priv.storage_list_key] = priv.storage_list;
o.env = my.env;
return o; return o;
}; };
...@@ -68,21 +66,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -68,21 +66,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return newlist; 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 * Checks a revision format
* @method checkRevisionFormat * @method checkRevisionFormat
...@@ -93,34 +76,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -93,34 +76,6 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return (/^[0-9]+-[0-9a-zA-Z_]+$/.test(revision)); 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) * Clones an object in deep (without functions)
* @method clone * @method clone
...@@ -188,303 +143,536 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -188,303 +143,536 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
}; };
/** /**
* Post the document metadata to all sub storages * Use "send" method to all sub storages.
* @method post * Calling "callback" only with the first response
* @param {object} command The JIO command * @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) { priv.sendToAllFastestResponseOnly = function (method, doc, option, callback) {
var functions = {}, doc_env, revs_info, doc, my_rev; var i, callbackWrapper, error_count, last_error;
functions.begin = function () { error_count = 0;
doc = command.cloneDoc(); callbackWrapper = function (method, index, err, response) {
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) {
if (err) { if (err) {
if (err.status === 409) { error_count += 1;
if (method !== "put") { last_error = err;
functions.sendDocumentIndex( if (error_count === priv.storage_list.length) {
"put", return callback(method, err, response);
index,
functions.checkSendResult
);
return;
}
} }
priv.updateEnv(doc_env, my_rev, index, null);
functions.error(err);
return;
} }
// success callback(method, err, response);
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) { for (i = 0; i < priv.storage_list.length; i += 1) {
// can be called once priv.send(method, i, doc, option, callbackWrapper);
that.success(response); }
functions.success = priv.emptyFunction; };
};
functions.error_count = 0; /**
functions.error = function (err) { * Use "sendToAll" method, calling "callback" at the last response with
functions.error_count += 1; * the response list
if (functions.error_count === priv.storage_list.length) { * @method sendToAllGetResponseList
that.error(err); * @param {string} method The request method
functions.error = priv.emptyFunction; * @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 * Checks if the sub storage are identical
* @method put * @method check
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.put = function (command) { that.check = function (command) {
priv.put_only = true; function callback(err, response) {
that.post(command); 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 * Repair the sub storages to make them identical
* @method putAttachment * @method repair
* @param {object} command The JIO command * @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);
};
/** priv.repair = function (doc, option, repair, callback) {
* Get the document or attachment from all sub storages, get the fastest. var functions = {};
* @method get callback = callback || priv.emptyFunction;
* @param {object} command The JIO command option = option || {};
*/
that.get = function (command) {
var functions = {}, doc_env, doc, my_rev, revs_array = [];
functions.begin = function () { functions.begin = function () {
// };
// functions.repairAllSubStorages = function () {
var i; 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) { for (i = 0; i < priv.storage_list.length; i += 1) {
// request all sub storages priv.send(
if (doc_env.my_revisions[my_rev]) { repair ? "repair" : "check",
// if my_rev exist, convert it to distant revision i,
doc._rev = doc_env.my_revisions[my_rev][i]; doc,
} option,
priv.send("get", i, doc, command.cloneOption(), functions.callback); functions.repairAllSubStoragesCallback
);
} }
}; };
functions.update_env = true; functions.repair_sub_storages_count = 0;
functions.callback = function (method, index, err, response) { functions.repairAllSubStoragesCallback = function (method,
index, err, response) {
if (err) { if (err) {
revs_array[index] = null; return that.error(err);
functions.error(err);
return;
} }
doc_env.last_revisions[index] = response._rev || "unique_" + index; functions.repair_sub_storages_count += 1;
revs_array[index] = response._rev || "unique_" + index; if (functions.repair_sub_storages_count === priv.storage_list.length) {
if (doc_env.distant_revisions[response._rev || "unique_" + index]) { functions.getAllDocuments(functions.newParam(
// the document revision is already known doc,
if (functions.update_env === true) { option,
my_rev = doc_env.distant_revisions[response._rev || repair
"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;
} }
response._rev = my_rev;
functions.success(response);
}; };
functions.success = function (response) { functions.newParam = function (doc, option, repair) {
var i, start, tmp, tmp_object; var param = {
functions.success = priv.emptyFunction; "doc": doc, // the document to repair
if (doc_env.my_revisions[my_rev]) { "option": option,
// this was not a specific revision "repair": repair,
// we can convert revisions recieved by the sub storage "responses": {
if (response._conflicts) { "count": 0,
// convert conflicting revisions to replicate revisions "list": [
tmp_object = {}; // 0: response0
for (i = 0; i < response._conflicts.length; i += 1) { // 1: response1
tmp_object[doc_env.distant_revisions[response._conflicts[i]] || // 2: response2
response._conflicts[i]] = true; ],
"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) { if (err) {
// convert revisions history to replicate revisions if (err.status !== 404) {
tmp_object = {}; // get document failed, exit
start = response._revisions.start; param.deal_result_state = "error";
for (i = 0; i < response._revisions.ids.length; i += 1, start -= 1) { callback({
tmp = doc_env.distant_revisions[ "status": 40,
start + "-" + response._revisions.ids[i] "statusText": "Check Failed",
]; "error": "check_failed",
if (tmp) { "message": "An error occured on the sub storage",
response._revisions.ids[i] = tmp.split("-").slice(1).join("-"); "reason": err.reason
} }, undefined);
return;
} }
} }
if (response._revs_info) { // success to get the document
// convert revs info to replicate revisions // add the response in memory
for (i = 0; i < response._revs_info.length; i += 1) { param.responses.count += 1;
tmp = doc_env.distant_revisions[response._revs_info[i].rev]; param.responses.list[index] = response;
if (tmp) {
response._revs_info[i].rev = tmp; // 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.makeResponsesStats = function (responses) {
functions.error = function (err) { var i, str_response;
functions.error_count += 1; for (i = 0; i < responses.count; i += 1) {
if (functions.error_count === priv.storage_list.length) { str_response = JSON.stringify(responses.list[i]);
that.error(err); if (responses.stats[str_response] === undefined) {
functions.error = priv.emptyFunction; responses.stats[str_response] = [];
responses.stats_items.push([
str_response,
responses.stats[str_response]
]);
}
responses.stats[str_response].push(i);
} }
}; };
functions.begin(); functions.getAttachments = function (param) {
}; var response, parsed_response, attachment;
for (response in param.responses.stats) {
/** if (param.responses.stats.hasOwnProperty(response)) {
* Remove the document or attachment from all sub storages. parsed_response = JSON.parse(response);
* @method remove for (attachment in parsed_response._attachments) {
* @param {object} command The JIO command if ((parsed_response._attachments).hasOwnProperty(attachment)) {
*/ functions.get_attachment_count += 1;
that.remove = function (command) { priv.send(
var functions = {}, doc_env, revs_info, doc, my_rev; "get",
functions.begin = function () { param.responses.stats[response][0],
doc = command.cloneDoc(); {
"_id": param.doc._id + "/" + attachment,
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) { "_rev": JSON.parse(response)._rev
that.error({ },
"status": 31, param.option,
"statusText": "Wrong Revision Format", functions.getAttachmentsCallback(
"error": "wrong_revision_format", param,
"message": "The document previous revision does not match " + attachment,
"^[0-9]+-[0-9a-zA-Z]+$", param.responses.stats[response]
"reason": "Previous revision is wrong" )
}); );
return; }
}
}
} }
doc_env = my.env[doc._id]; };
if (!doc_env || !doc_env.id) { functions.get_attachment_count = 0;
doc_env = priv.initEnv(doc._id); 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.finished_count -= 1;
functions.sendDocument();
}; };
functions.sendDocument = function () { functions.synchronizeResponseToSubStorage = function (
var i, cloned_doc; param,
for (i = 0; i < priv.storage_list.length; i += 1) { response,
cloned_doc = priv.clone(doc); storage_list
if (typeof cloned_doc._rev === "string" && ) {
doc_env.my_revisions[cloned_doc._rev] !== undefined) { var i, new_doc, attachment_to_put = [];
cloned_doc._rev = doc_env.my_revisions[cloned_doc._rev][i]; 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( priv.send(
"remove", "put",
i, storage_list[i],
cloned_doc, new_doc,
command.cloneOption(), param.option,
functions.checkSendResult functions.putAttachments(param, attachment_to_put)
); );
} }
that.end(); functions.finished_count += 1;
functions.finished();
}; };
functions.checkSendResult = function (method, index, err, response) { functions.synchronizeConflicts = function (param) {
if (err) { var rev, new_doc, new_option;
priv.updateEnv(doc_env, my_rev, index, null); new_option = priv.clone(param.option);
functions.error(err); new_option.synchronize_conflict = false;
return; 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) { functions.putAttachments = function (param, attachment_to_put) {
// can be called once return function (method, index, err, response) {
that.success(response); var i, attachment;
functions.success = priv.emptyFunction; 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.finished_count = 0;
functions.error = function (err) { functions.finished = function () {
functions.error_count += 1; var response_object = {};
if (functions.error_count === priv.storage_list.length) { functions.finished_count -= 1;
that.error(err); if (functions.finished_count === 0) {
functions.error = priv.emptyFunction; response_object.ok = true;
response_object.id = doc._id;
if (doc._rev) {
response_object.rev = doc._rev;
}
callback(undefined, response_object);
} }
}; };
functions.begin(); 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; return that;
}); });
...@@ -9,20 +9,31 @@ ...@@ -9,20 +9,31 @@
* "sub_storage": <sub storage description> * "sub_storage": <sub storage description>
* } * }
*/ */
jIO.addStorageType('revision', function (spec, my) { jIO.addStorageType("revision", function (spec, my) {
"use strict"; "use strict";
var that, priv = {}; var that = {}, priv = {};
spec = spec || {}; spec = spec || {};
that = my.basicStorage(spec, my); 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"; * Description to store in order to be restored later
priv.substorage = spec[priv.substorage_key]; * @method specToStore
* @return {object} Descriptions to store
*/
that.specToStore = function () { that.specToStore = function () {
var o = {}; return {
o[priv.substorage_key] = priv.substorage; "sub_storage": priv.sub_storage
return o; };
}; };
/** /**
...@@ -70,1200 +81,825 @@ jIO.addStorageType('revision', function (spec, my) { ...@@ -70,1200 +81,825 @@ jIO.addStorageType('revision', function (spec, my) {
}; };
/** /**
* Returns an array version of a revision string * Checks a revision format
* @method revisionToArray * @method checkDocumentRevisionFormat
* @param {string} revision The revision string * @param {object} doc The document object
* @return {array} Array containing a revision number and a hash * @return {object} null if ok, else error object
*/ */
priv.revisionToArray = function (revision) { priv.checkDocumentRevisionFormat = function (doc) {
if (typeof revision === "string") { var send_error = function (message) {
return [parseInt(revision.split('-')[0], 10), return {
revision.split('-')[1]]; "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. * Creates a new document tree
* @method revisionHistoryToArray * @method newDocTree
* @param {object} revs The revision history * @return {object} The new document tree
* @return {array} The revision array
*/ */
priv.revisionHistoryToArray = function (revs) { priv.newDocTree = function () {
var i, start = revs.start, newlist = []; return {"children": []};
for (i = 0; i < revs.ids.length; i += 1, start -= 1) {
newlist.push(start + "-" + revs.ids[i]);
}
return newlist;
}; };
/** /**
* Generates the next revision of [previous_revision]. [string] helps us * Convert revs_info to a simple revisions history
* to generate a hash code. * @method revsInfoToHistory
* @methode generateNextRev * @param {array} revs_info The revs info
* @param {string} previous_revision The previous revision * @return {object} The revisions history
* @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
*/ */
priv.generateNextRevision = function (previous_revision, priv.revsInfoToHistory = function (revs_info) {
doc, revisions, deleted_flag) { var i, revisions = {
var string = JSON.stringify(doc) + JSON.stringify(revisions) + "start": 0,
JSON.stringify(deleted_flag ? true : false); "ids": []
if (typeof previous_revision === "number") { };
return [previous_revision + 1, priv.hashCode(string)]; 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 revisions;
return [previous_revision[0] + 1, priv.hashCode(string)];
}; };
/** /**
* Checks a revision format * Convert the revision history object to an array of revisions.
* @method checkRevisionFormat * @method revisionHistoryToList
* @param {string} revision The revision string * @param {object} revs The revision history
* @return {boolean} True if ok, else false * @return {array} The revision array
*/ */
priv.checkRevisionFormat = function (revision) { priv.revisionHistoryToList = function (revs) {
return (/^[0-9]+-[0-9a-zA-Z]+$/.test(revision)); 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 * Convert revision list to revs info.
* @method createDocumentTree * @method revisionListToRevsInfo
* @param {array} children An array of children (optional) * @param {array} revision_list The revision list
* @return {object} The new document tree * @param {object} doc_tree The document tree
* @return {array} The document revs info
*/ */
priv.createDocumentTree = function (children) { priv.revisionListToRevsInfo = function (revision_list, doc_tree) {
return { var revisionListToRevsInfoRec, revs_info = [], j;
"children": children || [] 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 * Update a document metadata revision properties
* @method createDocumentTreeNode * @method fillDocumentRevisionProperties
* @param {string} revision The node revision * @param {object} doc The document object
* @param {string} status The node status * @param {object} doc_tree The document tree
* @param {array} children An array of children (optional)
* @return {object} The new document tree node
*/ */
priv.createDocumentTreeNode = function (revision, status, children) { priv.fillDocumentRevisionProperties = function (doc, doc_tree) {
return { if (doc._revs_info) {
"rev": revision, doc._revs = priv.revsInfoToHistory(doc._revs_info);
"status": status, } else if (doc._revs) {
"children": children || [] 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. * Generates the next revision of a document.
* @method getRevisionFromDocumentTree * @methode generateNextRevision
* @param {object} document_tree The document tree * @param {object} doc The document metadata
* @param {string} revision The specific revision * @param {boolean} deleted_flag The deleted flag
* @return {array} The good revs info array * @return {array} 0:The next revision number and 1:the hash code
*/ */
priv.getRevisionFromDocumentTree = function (document_tree, revision) { priv.generateNextRevision = function (doc, deleted_flag) {
var result, search, revs_info = []; var string, revision_history, revs_info, pseudo_revision;
result = []; doc = priv.clone(doc) || {};
// search method fills "result" with the good revs info revision_history = doc._revs;
search = function (document_tree) { revs_info = doc._revs_info;
var i; delete doc._rev;
if (document_tree.rev !== undefined) { delete doc._revs;
// node is not root delete doc._revs_info;
revs_info.unshift({ string = JSON.stringify(doc) + JSON.stringify(revision_history) +
"rev": document_tree.rev, JSON.stringify(deleted_flag ? true : false);
"status": document_tree.status revision_history.start += 1;
}); revision_history.ids.unshift(priv.hashCode(string));
if (document_tree.rev === revision) { doc._revs = revision_history;
result = revs_info; doc._rev = revision_history.start + "-" + revision_history.ids[0];
return; revs_info.unshift({
"rev": doc._rev,
"status": deleted_flag ? "deleted" : "available"
});
doc._revs_info = revs_info;
return doc;
};
/**
* Gets the revs info from the document tree
* @method getRevisionInfo
* @param {string} revision The revision to search for
* @param {object} doc_tree The document tree
* @return {array} The revs info
*/
priv.getRevisionInfo = function (revision, doc_tree) {
var getRevisionInfoRec;
getRevisionInfoRec = function (doc_tree) {
var i, child, revs_info;
for (i = 0; i < doc_tree.children.length; i += 1) {
child = doc_tree.children[i];
if (child.rev === revision) {
return [{"rev": child.rev, "status": child.status}];
} }
} revs_info = getRevisionInfoRec(child);
// This node has children if (revs_info.length > 0 || revision === undefined) {
for (i = 0; i < document_tree.children.length; i += 1) { revs_info.push({"rev": child.rev, "status": child.status});
// searching deeper to find the good rev return revs_info;
search(document_tree.children[i]);
if (result.length > 0) {
// The result is already found
return;
} }
revs_info.shift();
} }
return [];
}; };
search(document_tree); return getRevisionInfoRec(doc_tree);
return result;
}; };
/** priv.updateDocumentTree = function (doc, doc_tree) {
* Gets the winner revision from a document tree. var revs_info, updateDocumentTreeRec, next_rev;
* The winner is the deeper revision on the left. doc = priv.clone(doc);
* @method getWinnerRevisionFromDocumentTree revs_info = doc._revs_info;
* @param {object} document_tree The document tree updateDocumentTreeRec = function (doc_tree, revs_info) {
* @return {array} The winner revs info array var i, child, info;
*/ if (revs_info.length === 0) {
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]);
}
}
return; return;
} }
// This node has children info = revs_info.pop();
for (i = 0; i < document_tree.children.length; i += 1) { for (i = 0; i < doc_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf child = doc_tree.children[i];
search(document_tree.children[i], deep + 1); if (child.rev === info.rev) {
revs_info.shift(); 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); updateDocumentTreeRec(doc_tree, priv.clone(revs_info));
return result;
}; };
/** priv.send = function (method, doc, option, callback) {
* Add a document revision branch to the document tree that.addJob(
* @method updateDocumentTree method,
* @param {object} doctree The document tree object priv.sub_storage,
* @param {object|array} revs The revision history object or a revision array doc,
* @param {boolean} deleted The deleted flag option,
* @param {array} The document revs_info function (success) {
*/ callback(undefined, success);
priv.updateDocumentTree = function (doctree, revs, deleted) { },
var revs_info, doctree_iterator, flag, i, rev; function (err) {
revs_info = []; callback(err, undefined);
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];
} }
} );
flag = deleted === true ? "deleted" : "available";
revs_info[0].status = flag;
doctree_iterator.status = flag;
return revs_info;
}; };
/** priv.getWinnerRevsInfo = function (doc_tree) {
* Add a document revision to the document tree var revs_info = [], getWinnerRevsInfoRec;
* @method postToDocumentTree getWinnerRevsInfoRec = function (doc_tree, tmp_revs_info) {
* @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) {
var i; var i;
if (node.rev !== undefined) { if (doc_tree.rev) {
// node is not root tmp_revs_info.unshift({"rev": doc_tree.rev, "status": doc_tree.status});
revs_info.unshift({
"rev": node.rev,
"status": node.status
});
} }
if (node.rev === doc._rev) { if (doc_tree.children.length === 0) {
selected_node = node; if (revs_info.length < tmp_revs_info.length ||
return "node_selected"; (revs_info.length > 0 && revs_info[0].status === "deleted")) {
} revs_info = priv.clone(tmp_revs_info);
for (i = 0; i < node.children.length; i += 1) {
if (selectNode(node.children[i]) === "node_selected") {
return "node_selected";
} }
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];
} }
} for (i = 0; i < doc_tree.children.length; i += 1) {
next_rev = priv.generateNextRevision( getWinnerRevsInfoRec(doc_tree.children[i], tmp_revs_info);
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;
} }
} tmp_revs_info.shift();
revs_info.unshift({ };
"rev": next_rev.join('-'), getWinnerRevsInfoRec(doc_tree, []);
"status": flag
});
selected_node.children.unshift(priv.createDocumentTreeNode(
next_rev.join('-'),
flag
));
return revs_info; return revs_info;
}; };
/** priv.getConflicts = function (revision, doc_tree) {
* Gets an array of leaves revisions from document tree var conflicts = [], getConflictsRec;
* @method getLeavesFromDocumentTree getConflictsRec = function (doc_tree) {
* @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) {
var i; var i;
if (except !== undefined && except === document_tree.rev) { if (doc_tree.rev === revision) {
return; return;
} }
if (document_tree.children.length === 0 && document_tree.status !== if (doc_tree.children.length === 0) {
"deleted") { if (doc_tree.status !== "deleted") {
// This node is a leaf conflicts.push(doc_tree.rev);
result.push(document_tree.rev); }
return;
} }
// This node has children for (i = 0; i < doc_tree.children.length; i += 1) {
for (i = 0; i < document_tree.children.length; i += 1) { getConflictsRec(doc_tree.children[i]);
// searching deeper to find the deeper leaf
search(document_tree.children[i]);
} }
}; };
search(document_tree); getConflictsRec(doc_tree);
return result; return conflicts.length === 0 ? undefined : conflicts;
}; };
/** priv.get = function (doc, option, callback) {
* Check if revision is a leaf priv.send("get", doc, option, callback);
* @method isRevisionALeaf };
* @param {string} revision revision to check priv.put = function (doc, option, callback) {
* @param {array} leaves all leaves on tree priv.send("put", doc, option, callback);
* @return {boolean} true/false };
*/ priv.remove = function (doc, option, callback) {
priv.isRevisionALeaf = function (document_tree, revision) { priv.send("remove", doc, option, callback);
var result, search; };
result = undefined; priv.putAttachment = function (attachment, option, callback) {
// search method fills "result" with the good revs info priv.send("putAttachment", attachment, option, callback);
search = function (document_tree) { };
var i;
if (document_tree.rev !== undefined) { priv.getDocument = function (doc, option, callback) {
// node is not root doc = priv.clone(doc);
if (document_tree.rev === revision) { doc._id = doc._id + "." + doc._rev;
if (document_tree.children.length === 0) { delete doc._attachment;
// This node is a leaf delete doc._rev;
result = true; delete doc._revs;
return; delete doc._revs_info;
} priv.get(doc, option, callback);
result = false; };
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; return;
} }
} count -= 1;
// This node has children if (err) {
for (i = 0; i < document_tree.children.length; i += 1) { if (err.status === 404) {
// searching deeper to find the good rev result_list.push(undefined);
search(document_tree.children[i]); } else {
if (result !== undefined) { state = "error";
// The result is already found return callback(err, undefined);
return; }
} }
} 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); for (attachment_id in doc._attachments) {
return result || false; if (doc._attachments.hasOwnProperty(attachment_id)) {
}; count += 1;
priv.get(
/** {"_id": doc._id + "/" + attachment_id},
* Convert revs_info to a simple revisions history option,
* @method revsInfoToHistory dealResults(attachment_id, doc._attachments[attachment_id])
* @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 (i = 0; i < revs_info.length; i += 1) { if (count === 0) {
revisions.ids.push(revs_info[i].rev.split('-')[1]); callback(undefined, []);
} }
return revisions;
}; };
/** priv.putAttachmentList = function (doc, option, attachment_list, callback) {
* Returns the revision of the revision position from a revs_info array. var i, dealResults, state = "ok", count = 0, attachment;
* @method getRevisionFromPosition attachment_list = attachment_list || [];
* @param {array} revs_info The revs_info array dealResults = function (index) {
* @param {number} rev_pos The revision position number return function (err, response) {
* @return {string} The revision of the good position (empty string if fail) if (state !== "ok") {
*/ return;
priv.getRevisionFromPosition = function (revs_info, rev_pos) { }
var i; count -= 1;
for (i = revs_info.length - 1; i >= 0; i -= 1) { if (err) {
if (priv.revisionToArray(revs_info[i].rev)[0] === rev_pos) { state = "error";
return revs_info[i].rev; 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});
}
}; };
/** priv.putDocumentTree = function (doc, option, doc_tree, callback) {
* Post the document metadata and create or update a document tree. doc_tree = priv.clone(doc_tree);
* Options: doc_tree._id = doc._id + priv.doc_tree_suffix;
* - {boolean} keep_revision_history To keep the previous revisions priv.put(doc_tree, option, callback);
* (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();
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) { priv.notFoundError = function (message, reason) {
that.error({ return {
"status": 31, "status": 404,
"statusText": "Wrong Revision Format", "statusText": "Not Found",
"error": "wrong_revision_format", "error": "not_found",
"message": "The document previous revision does not match " + "message": message,
"^[0-9]+-[0-9a-zA-Z]+$", "reason": reason
"reason": "Previous revision is wrong" };
}); };
return;
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") { if (specific_parameter.attachment_id) {
doc._id = priv.generateUuid(); doc._attachment = specific_parameter.attachment_id;
docid = doc._id;
} }
f.getDocumentTree = function () { callback.begin = function () {
var option = command.cloneOption(); var check_error;
if (option.max_retry === 0) { doc._id = doc._id || priv.generateUuid();
option.max_retry = 3; 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( doc_tree = response || priv.newDocTree();
"get", if (specific_parameter.get || specific_parameter.getAttachment) {
priv.substorage, if (!doc._rev) {
docid + priv.doctree_suffix, winner_info = priv.getWinnerRevsInfo(doc_tree);
option, if (winner_info.length === 0) {
function (response) { return onEnd(priv.notFoundError(
doctree = response; "Document not found",
f.updateRevsInfo(); "missing"
f.getDocument(); ), undefined);
},
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;
} }
} if (winner_info[0].status === "deleted") {
); return onEnd(priv.notFoundError(
}; "Document not found",
f.getDocument = function () { "deleted"
if (revs_info[1] === undefined) { ), 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);
} }
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 () { callback.getDocument = function (err, res_doc) {
if (doc._revs) { var k, conflicts;
revs_info = priv.updateDocumentTree(doctree, doc._revs); 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 { } else {
revs_info = priv.postToDocumentTree(doctree, doc); priv.getAttachmentList(res_doc, option, callback.getAttachmentList);
} }
}; };
f.postDocument = function (attachment_list) { callback.getAttachmentList = function (err, res_list) {
doc._id = docid + "." + revs_info[0].rev; var i, attachment_found = false;
delete doc._rev; if (err) {
delete doc._revs; err.message = "Cannot get attachment";
that.addJob( return onEnd(err, undefined);
"post", }
priv.substorage, attachment_list = res_list || [];
doc, if (specific_parameter.getAttachment) {
command.cloneOption(), // getting specific attachment
function () { for (i = 0; i < attachment_list.length; i += 1) {
var i; if (attachment_list[i] &&
if (attachment_list.length === 0) { doc._attachment ===
f.sendDocumentTree(); attachment_list[i]._attachment) {
} else { return onEnd(undefined, attachment_list[i]._data);
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);
}
} }
}, }
function (err) { return onEnd(priv.notFoundError(
switch (err.status) { "Unable to get an inexistent attachment",
case 409: "missing"
// file already exists ), undefined);
f.sendDocumentTree(); }
break; if (specific_parameter.remove_from_attachment_list) {
default: // removing specific attachment
err.message = "Cannot upload document"; for (i = 0; i < attachment_list.length; i += 1) {
f.error(err); if (attachment_list[i] &&
specific_parameter.remove_from_attachment_list._attachment ===
attachment_list[i]._attachment) {
attachment_found = true;
attachment_list[i] = undefined;
break; 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) { callback.putDocument = function (err, response) {
that.addJob( var i, attachment_found = false;
"get", if (err) {
priv.substorage, err.message = "Cannot post the document";
prev_doc._id + "/" + attachmentid, return onEnd(err, undefined);
command.cloneOption(), }
function (response) { if (specific_parameter.add_to_attachment_list) {
attachment._data = response; // adding specific attachment
that.addJob( attachment_list = attachment_list || [];
"putAttachment", for (i = 0; i < attachment_list.length; i += 1) {
priv.substorage, if (attachment_list[i] &&
attachment, specific_parameter.add_to_attachment_list._attachment ===
command.cloneOption(), attachment_list[i]._attachment) {
function (response) { attachment_found = true;
f.sendDocumentTree(); attachment_list[i] = specific_parameter.add_to_attachment_list;
}, break;
function (err) { }
err.message = "Cannot copy previous attachment"; }
f.error(err); if (!attachment_found) {
} attachment_list.unshift(specific_parameter.add_to_attachment_list);
);
},
function (err) {
err.message = "Cannot get previous attachment";
f.error(err);
} }
}
priv.putAttachmentList(
doc,
option,
attachment_list,
callback.putAttachmentList
); );
}; };
f.send_document_tree_count = 0; callback.putAttachmentList = function (err, response) {
f.sendDocumentTree = function () { if (err) {
f.send_document_tree_count -= 1; err.message = "Cannot copy attacments to the document";
if (f.send_document_tree_count > 0) { return onEnd(err, undefined);
return;
} }
doctree._id = docid + priv.doctree_suffix; priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree);
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);
}
);
}; };
f.error = function (err) { callback.putDocumentTree = function (err, response) {
f.error = function () {}; if (err) {
that.error(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: * Options:
* - {boolean} keep_revision_history To keep the previous revisions * - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI). * (false by default) (NYI).
* @method put * @method post
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.put = function (command) { that.post = function (command) {
that.post(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: * Options:
* - {boolean} keep_revision_history To keep the previous revisions * - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI). * (false by default) (NYI).
* @method putAttachment * @method put
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.putAttachment = function (command) { that.put = function (command) {
var functions = {}, doc, doctree, revs_info, prev_doc; priv.revisionGenericRequest(
doc = command.cloneDoc(); command.cloneDoc(),
functions.begin = function () { command.cloneOption(),
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) { {},
that.error({ function (err, response) {
"status": 31, if (err) {
"statusText": "Wrong Revision Format", return that.error(err);
"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.success(response);
};
functions.post_attachment_count = 0;
functions.postAttachment = function () {
functions.post_attachment_count -= 1;
if (functions.post_attachment_count > 0) {
return;
} }
that.addJob( );
"putAttachment", };
priv.substorage,
{
"_id": command.getDocId() + "." + revs_info[0].rev + "/" + that.putAttachment = function (command) {
command.getAttachmentId(), priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"doc_id": command.getDocId(),
"attachment_id": command.getAttachmentId(),
"add_to_attachment_list": {
"_attachment": command.getAttachmentId(),
"_mimetype": command.getAttachmentMimeType(), "_mimetype": command.getAttachmentMimeType(),
"_data": command.getAttachmentData() "_data": command.getAttachmentData()
}, },
command.cloneOption(), "putAttachment": true
function () { },
functions.sendDocumentTree(); function (err, response) {
}, if (err) {
function (err) { return that.error(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);
} }
); that.success(response);
};
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;
} }
} );
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) { that.remove = function (command) {
var f = {}, del_rev, option, new_doc, revs_info; if (command.getAttachmentId()) {
option = command.cloneOption(); return that.removeAttachment(command);
if (option.max_retry === 0) {
option.max_retry = 3;
} }
del_rev = command.getDoc()._rev; priv.revisionGenericRequest(
command.cloneDoc(),
f.removeDocument = function (docid, doctree) { command.cloneOption(),
if (command.getOption("keep_revision_history") !== true) { {
if (command.getAttachmentId() === undefined) { "revision_needed": true,
// update tree "remove": true
revs_info = priv.postToDocumentTree( },
doctree, function (err, response) {
command.getDoc(), if (err) {
true return that.error(err);
);
// 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;
}
);
} }
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) { that.removeAttachment = function (command) {
// no revision provided priv.revisionGenericRequest(
that.error({ command.cloneDoc(),
"status": 409, command.cloneOption(),
"statusText": "Conflict", {
"error": "conflict", "doc_id": command.getDocId(),
"message": "Document update conflict.", "attachment_id": command.getAttachmentId(),
"reason": "Cannot delete a document without revision" "revision_needed": true,
}); "removeAttachment": true,
return; "remove_from_attachment_list": {
"_attachment": command.getAttachmentId()
} }
// revision provided },
if (priv.isRevisionALeaf(response, del_rev) === true) { function (err, response) {
if (typeof command.getAttachmentId() === "string") { if (err) {
f.removeDocument(command.getDocId() + "." + del_rev + return that.error(err);
"/" + 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;
} }
that.success(response);
}
);
};
that.get = function (command) {
if (command.getAttachmentId()) {
return that.getAttachment(command);
}
priv.revisionGenericRequest(
command.cloneDoc(),
command.cloneOption(),
{
"get": true
}, },
function () { function (err, response) {
that.error({ if (err) {
"status": 404, return that.error(err);
"statusText": "Not Found", }
"error": "not_found", that.success(response);
"message": "Document tree not found, please checkdocument ID",
"reason": "Incorrect document ID"
});
return;
} }
); );
}; };
/** that.getAttachment = function (command) {
* Get all documents priv.revisionGenericRequest(
* @method allDocs command.cloneDoc(),
* @param {object} command The JIO command command.cloneOption(),
*/ {
that.allDocs = function () { "doc_id": command.getDocId(),
setTimeout(function () { "attachment_id": command.getAttachmentId(),
that.error({ "getAttachment": true
"status": 405, },
"statusText": "Method Not Allowed", function (err, response) {
"error": "method_not_allowed", if (err) {
"message": "Your are not allowed to use this command", return that.error(err);
"reason": "LocalStorage forbids AllDocs command executions" }
}); that.success(response);
}); }
);
}; };
// END //
priv.RevisionStorage();
return that; return that;
}); }); // end RevisionStorage
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/*global postCommand: true, putCommand: true, getCommand: true, /*global postCommand: true, putCommand: true, getCommand: true,
removeCommand: true, allDocsCommand: true, removeCommand: true, allDocsCommand: true,
putAttachmentCommand: true, failStatus: true, doneStatus: true, putAttachmentCommand: true, failStatus: true, doneStatus: true,
checkCommand: true, repairCommand: true,
hex_md5: true */ hex_md5: true */
var command = function (spec, my) { var command = function (spec, my) {
var that = {}, var that = {},
...@@ -16,7 +17,9 @@ var command = function (spec, my) { ...@@ -16,7 +17,9 @@ var command = function (spec, my) {
'get': getCommand, 'get': getCommand,
'remove': removeCommand, 'remove': removeCommand,
'allDocs': allDocsCommand, 'allDocs': allDocsCommand,
'putAttachment': putAttachmentCommand 'putAttachment': putAttachmentCommand,
'check': checkCommand,
'repair': repairCommand
}; };
// creates the good command thanks to his label // creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) { if (spec.label && priv.commandlist[spec.label]) {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
storage_type_object: true, invalidStorageType: true, jobRules: true, storage_type_object: true, invalidStorageType: true, jobRules: true,
job: true, postCommand: true, putCommand: true, getCommand:true, job: true, postCommand: true, putCommand: true, getCommand:true,
allDocsCommand: true, putAttachmentCommand: true, allDocsCommand: true, putAttachmentCommand: true,
removeCommand: true */ removeCommand: true, checkCommand: true, repairCommand: true */
// Class jio // Class jio
var that = {}, priv = {}, jio_id_array_name = 'jio/id_array'; var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
spec = spec || {}; spec = spec || {};
...@@ -377,10 +377,10 @@ Object.defineProperty(that, "allDocs", { ...@@ -377,10 +377,10 @@ Object.defineProperty(that, "allDocs", {
* Put an attachment to a document. * Put an attachment to a document.
* @method putAttachment * @method putAttachment
* @param {object} doc The document object. Contains at least: * @param {object} doc The document object. Contains at least:
* - {string} id The document id: "doc_id/attchment_id" * - {string} _id The document id: "doc_id/attchment_id"
* - {string} data Base64 attachment data * - {string} _data Base64 attachment data
* - {string} mimetype The attachment mimetype * - {string} _mimetype The attachment mimetype
* - {string} rev The attachment revision * - {string} _rev The attachment revision
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document. * - {boolean} revs Include revision history of the document.
...@@ -408,3 +408,63 @@ Object.defineProperty(that, "putAttachment", { ...@@ -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) { ...@@ -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.success = function () {};
that.retry = function () {}; that.retry = function () {};
that.error = function () {}; that.error = function () {};
......
...@@ -34,9 +34,14 @@ clone = function (obj) { ...@@ -34,9 +34,14 @@ clone = function (obj) {
// generates a revision hash from document metadata, revision history // generates a revision hash from document metadata, revision history
// and the deleted_flag // and the deleted_flag
generateRevisionHash = function (doc, revisions, deleted_flag) { generateRevisionHash = function (doc, revisions, deleted_flag) {
var string = JSON.stringify(doc) + JSON.stringify(revisions) + var string;
JSON.stringify(deleted_flag? true: false); doc = clone(doc);
return hex_sha256(string); 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 wrapper
localstorage = { localstorage = {
...@@ -646,7 +651,6 @@ test ("Similar Jobs at the same time (Update)", function () { ...@@ -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.f2); // 2
o.jio.put({"_id": "file", "content": "content"}, o.f3); // 3 o.jio.put({"_id": "file", "content": "content"}, o.f3); // 3
deepEqual(getLastJob(o.jio.getId()).id, 1, "Check job queue"); 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, 1000, "f");
o.tick(o, "f2"); o.tick(o, "f2");
o.tick(o, "f3"); o.tick(o, "f3");
...@@ -1264,7 +1268,8 @@ test ("Post", function(){ ...@@ -1264,7 +1268,8 @@ test ("Post", function(){
o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"}; o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"};
o.revisions = {"start": 1, "ids": [o.rev.split('-')[1]]}; o.revisions = {"start": 1, "ids": [o.rev.split('-')[1]]};
o.rev = "2-"+generateRevisionHash(o.doc, o.revisions); 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.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -1293,8 +1298,92 @@ test ("Post", function(){ ...@@ -1293,8 +1298,92 @@ test ("Post", function(){
"Check document tree" "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(){ test ("Put", function(){
...@@ -1550,19 +1639,21 @@ test("Put Attachment", function () { ...@@ -1550,19 +1639,21 @@ test("Put Attachment", function () {
// putAttachment without doc id // putAttachment without doc id
// error 20 -> document id required // 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.jio.putAttachment({}, o.f);
o.tick(o); o.tick(o);
// putAttachment without attachment id // putAttachment without attachment id
// erorr 22 -> attachment id required // 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.jio.putAttachment({"_id": "putattmt1"}, o.f);
o.tick(o); o.tick(o);
// putAttachment without document // putAttachment without document
o.revisions = {"start": 0, "ids": []} o.revisions = {"start": 0, "ids": []}
o.rev_hash = generateRevisionHash({"_id": "doc1/attmt1"}, o.rev_hash = generateRevisionHash({"_id": "doc1", "_attachment": "attmt1"},
o.revisions); o.revisions);
o.rev = "1-" + o.rev_hash; o.rev = "1-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev}, o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev},
...@@ -1596,14 +1687,23 @@ test("Put Attachment", function () { ...@@ -1596,14 +1687,23 @@ test("Put Attachment", function () {
), ),
"", "Check attachment" "", "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 // update attachment
o.prev_rev = o.rev; o.prev_rev = o.rev;
o.revisions = {"start": 1, "ids": [o.rev_hash]} o.revisions = {"start": 1, "ids": [o.rev_hash]}
o.rev_hash = generateRevisionHash({ o.rev_hash = generateRevisionHash({
"_id": "doc1/attmt1", "_id": "doc1",
"_data": "abc", "_data": "abc",
"_rev": o.prev_rev "_attachment": "attmt1",
}, o.revisions); }, o.revisions);
o.rev = "2-" + o.rev_hash; o.rev = "2-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev}, o.spy(o, "value", {"ok": true, "id": "doc1/attmt1", "rev": o.rev},
...@@ -1622,6 +1722,7 @@ test("Put Attachment", function () { ...@@ -1622,6 +1722,7 @@ test("Put Attachment", function () {
), ),
{ {
"_id": "doc1." + o.rev, "_id": "doc1." + o.rev,
"title": "My Title",
"_attachments": { "_attachments": {
"attmt1": { "attmt1": {
"length": 3, "length": 3,
...@@ -1646,9 +1747,9 @@ test("Put Attachment", function () { ...@@ -1646,9 +1747,9 @@ test("Put Attachment", function () {
o.prev_rev = o.rev; o.prev_rev = o.rev;
o.revisions = {"start": 2, "ids": [o.rev_hash, o.revisions.ids[0]]} o.revisions = {"start": 2, "ids": [o.rev_hash, o.revisions.ids[0]]}
o.rev_hash = generateRevisionHash({ o.rev_hash = generateRevisionHash({
"_id": "doc1/attmt2", "_id": "doc1",
"_data": "def", "_data": "def",
"_rev": o.prev_rev "_attachment": "attmt2",
}, o.revisions); }, o.revisions);
o.rev = "3-" + o.rev_hash; o.rev = "3-" + o.rev_hash;
o.spy(o, "value", {"ok": true, "id": "doc1/attmt2", "rev": o.rev}, o.spy(o, "value", {"ok": true, "id": "doc1/attmt2", "rev": o.rev},
...@@ -1667,6 +1768,7 @@ test("Put Attachment", function () { ...@@ -1667,6 +1768,7 @@ test("Put Attachment", function () {
), ),
{ {
"_id": "doc1." + o.rev, "_id": "doc1." + o.rev,
"title": "My Title",
"_attachments": { "_attachments": {
"attmt1": { "attmt1": {
"length": 3, "length": 3,
...@@ -1710,12 +1812,14 @@ test ("Get", function(){ ...@@ -1710,12 +1812,14 @@ test ("Get", function(){
o.localpath = "jio/localstorage/urevget/arevget"; o.localpath = "jio/localstorage/urevget/arevget";
// get inexistent document // 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.jio.get({"_id": "get1"}, o.f);
o.tick(o); o.tick(o);
// get inexistent attachment // 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.jio.get({"_id": "get1/get2"}, o.f);
o.tick(o); o.tick(o);
...@@ -1723,15 +1827,16 @@ test ("Get", function(){ ...@@ -1723,15 +1827,16 @@ test ("Get", function(){
o.doctree = {"children":[{ o.doctree = {"children":[{
"rev": "1-rev1", "status": "available", "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.revision_tree.json", o.doctree);
localstorage.setItem(o.localpath+"/get1.1-rev1", o.doc_myget1); localstorage.setItem(o.localpath+"/get1.1-rev1", o.doc_myget1);
// get document // get document
o.doc_myget1_cloned = clone(o.doc_myget1); o.doc_myget1_cloned = clone(o.doc_myget1);
o.doc_myget1_cloned["_rev"] = "1-rev1"; o.doc_myget1_cloned._id = "get1";
o.doc_myget1_cloned["_revisions"] = {"start": 1, "ids": ["rev1"]}; o.doc_myget1_cloned._rev = "1-rev1";
o.doc_myget1_cloned["_revs_info"] = [{ o.doc_myget1_cloned._revisions = {"start": 1, "ids": ["rev1"]};
o.doc_myget1_cloned._revs_info = [{
"rev": "1-rev1", "status": "available" "rev": "1-rev1", "status": "available"
}]; }];
o.spy(o, "value", o.doc_myget1_cloned, "Get document (winner)"); o.spy(o, "value", o.doc_myget1_cloned, "Get document (winner)");
...@@ -1748,14 +1853,15 @@ test ("Get", function(){ ...@@ -1748,14 +1853,15 @@ test ("Get", function(){
"rev": "2-rev3", "status": "available", "children": [] "rev": "2-rev3", "status": "available", "children": []
}] }]
}]}; }]};
o.doc_myget2 = {"_id": "get1", "title": "myGet2"}; o.doc_myget2 = {"_id": "get1.1-rev2", "title": "myGet2"};
o.doc_myget3 = {"_id": "get1", "title": "myGet3"}; o.doc_myget3 = {"_id": "get1.2-rev3", "title": "myGet3"};
localstorage.setItem(o.localpath+"/get1.revision_tree.json", o.doctree); 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.1-rev2", o.doc_myget2);
localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_myget3); localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_myget3);
// get document // get document
o.doc_myget3_cloned = clone(o.doc_myget3); 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["_rev"] = "2-rev3";
o.doc_myget3_cloned["_revisions"] = {"start": 2, "ids": ["rev3","rev2"]}; o.doc_myget3_cloned["_revisions"] = {"start": 2, "ids": ["rev3","rev2"]};
o.doc_myget3_cloned["_revs_info"] = [{ o.doc_myget3_cloned["_revs_info"] = [{
...@@ -1772,7 +1878,8 @@ test ("Get", function(){ ...@@ -1772,7 +1878,8 @@ test ("Get", function(){
o.tick(o); o.tick(o);
// get inexistent specific document // 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"}, { o.jio.get({"_id": "get1", "_rev": "1-rev0"}, {
"revs_info": true, "revs": true, "conflicts": true, "revs_info": true, "revs": true, "conflicts": true,
}, o.f); }, o.f);
...@@ -1780,6 +1887,7 @@ test ("Get", function(){ ...@@ -1780,6 +1887,7 @@ test ("Get", function(){
// get specific document // get specific document
o.doc_myget2_cloned = clone(o.doc_myget2); 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["_rev"] = "1-rev2";
o.doc_myget2_cloned["_revisions"] = {"start": 1, "ids": ["rev2"]}; o.doc_myget2_cloned["_revisions"] = {"start": 1, "ids": ["rev2"]};
o.doc_myget2_cloned["_revs_info"] = [{ o.doc_myget2_cloned["_revs_info"] = [{
...@@ -1793,18 +1901,16 @@ test ("Get", function(){ ...@@ -1793,18 +1901,16 @@ test ("Get", function(){
o.tick(o); o.tick(o);
// adding an attachment // adding an attachment
o.attmt_myget2 = { o.attmt_myget3 = {
"get2": { "get2": {
"length": 3, "length": 3,
"digest": "md5-dontcare", "digest": "md5-dontcare",
"revpos": 1 "content_type": "oh/yeah"
} }
}; };
o.doc_myget2["_attachments"] = o.attmt_myget2; o.doc_myget3._attachments = o.attmt_myget3;
o.doc_myget3["_attachments"] = o.attmt_myget2;
localstorage.setItem(o.localpath+"/get1.1-rev2", o.doc_myget2);
localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_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 // get attachment winner
o.spy(o, "value", "abc", "Get attachment (winner)"); o.spy(o, "value", "abc", "Get attachment (winner)");
...@@ -1812,7 +1918,8 @@ test ("Get", function(){ ...@@ -1812,7 +1918,8 @@ test ("Get", function(){
o.tick(o); o.tick(o);
// get inexistent attachment specific rev // 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"}, { o.jio.get({"_id": "get1/get2", "_rev": "1-rev1"}, {
"revs_info": true, "revs": true, "conflicts": true, "revs_info": true, "revs": true, "conflicts": true,
}, o.f); }, o.f);
...@@ -1820,13 +1927,13 @@ test ("Get", function(){ ...@@ -1820,13 +1927,13 @@ test ("Get", function(){
// get attachment specific rev // get attachment specific rev
o.spy(o, "value", "abc", "Get attachment (specific revision)"); 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, "revs_info": true, "revs": true, "conflicts": true,
}, o.f); }, o.f);
o.tick(o); o.tick(o);
// get document with attachment (specific revision) // 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, o.spy(o, "value", o.doc_myget2_cloned,
"Get document which have an attachment (specific revision)"); "Get document which have an attachment (specific revision)");
o.jio.get({"_id": "get1", "_rev": "1-rev2"}, { o.jio.get({"_id": "get1", "_rev": "1-rev2"}, {
...@@ -1835,7 +1942,7 @@ test ("Get", function(){ ...@@ -1835,7 +1942,7 @@ test ("Get", function(){
o.tick(o); o.tick(o);
// get document with attachment (winner) // 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, o.spy(o, "value", o.doc_myget3_cloned,
"Get document which have an attachment (winner)"); "Get document which have an attachment (winner)");
o.jio.get({"_id": "get1"}, o.jio.get({"_id": "get1"},
...@@ -1862,194 +1969,139 @@ test ("Remove", function(){ ...@@ -1862,194 +1969,139 @@ test ("Remove", function(){
o.localpath = "jio/localstorage/urevrem/arevrem"; o.localpath = "jio/localstorage/urevrem/arevrem";
// 1. remove document without revision // 1. remove document without revision
o.spy (o, "status", 404, o.spy(o, "status", 409, "Remove document without revision " +
"Remove document (no doctree, no revision)"); "-> 409 Conflict");
o.jio.remove({"_id":"remove1"}, o.f); o.jio.remove({"_id":"remove1"}, o.f);
o.tick(o); o.tick(o);
// 2. remove attachment without revision // 2. remove attachment without revision
o.spy (o, "status", 404, o.spy(o, "status", 409, "Remove attachment without revision " +
"Remove attachment (no doctree, no revision)"); "-> 409 Conflict");
o.jio.remove({"_id":"remove1/remove2"}, o.f); o.jio.remove({"_id":"remove1/remove2"}, o.f);
o.tick(o); o.tick(o);
// adding two documents // adding a document with attachments
o.doc_myremove1 = {"_id": "remove1", "title": "myRemove1"}; o.doc_myremove1 = {
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2"}; "_id": "remove1.1-veryoldrev",
"title": "myRemove1"
o.very_old_rev = "1-veryoldrev"; };
localstorage.setItem(o.localpath+"/remove1."+o.very_old_rev, localstorage.setItem(o.localpath + "/remove1.1-veryoldrev",
o.doc_myremove1); o.doc_myremove1);
localstorage.setItem(o.localpath+"/remove1.1-rev2", o.doc_myremove1);
// add attachment o.doc_myremove1._id = "remove1.2-oldrev";
o.attmt_myremove1 = { o.attachment_remove2 = {
"remove2": { "length": 3,
"length": 3, "digest": "md5-dontcare",
"digest": "md5-dontcare", "content_type": "oh/yeah"
"revpos":1 }
}, 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.2-oldrev",
localstorage.setItem(o.localpath+"/remove1."+o.old_rev+"/remove2", "xyz"); o.doc_myremove1);
localstorage.setItem(o.localpath + "/remove1.2-oldrev/remove2", "abc");
localstorage.setItem(o.localpath + "/remove1.2-oldrev/remove3", "defgh");
o.doctree = {"children":[{ // add document tree
"rev": o.very_old_rev, "status": "available", "children": [{ o.doctree = {
"rev": o.old_rev, "status": "available", "children": [] "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",
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree); 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);
// 4. remove existing attachment with revision // 3. remove inexistent attachment
o.spy (o, "value", {"ok": true, "id": "remove1."+o.rev, "rev": o.rev}, o.spy(o, "status", 404, "Remove inexistent attachment -> 404 Not Found");
"Remove existing attachment (revision)"); o.jio.remove({"_id": "remove1/remove0", "_rev": "2-oldrev"}, o.f);
o.jio.remove({"_id":"remove1/remove2","_rev":o.old_rev}, o.f);
o.tick(o); o.tick(o);
o.testtree = {"children":[{ // 4. remove existing attachment
"rev": o.very_old_rev, "status": "available", "children": [{ o.rev_hash = generateRevisionHash({
"rev": o.old_rev, "status": "available", "children": [{ "_id": "remove1",
"rev": o.rev, "status": "available", "children": [] "_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 // 5. check if document tree has been updated correctly
deepEqual(localstorage.getItem( deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1.revision_tree.json" o.localpath + "/remove1.revision_tree.json"
),o.testtree, "Check document tree"); ), o.doctree, "Check document tree");
// 6. check if attachment has been removed // 6. check if the attachment still exists
deepEqual(localstorage.getItem( deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.rev+"/remove2" o.localpath + "/remove1.2-oldrev/remove2"
), null, "Check attachment"); ), "abc", "Check attachment -> still exists");
// 7. check if document is updated // 7. check if document is updated
deepEqual(localstorage.getItem( deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.rev o.localpath + "/remove1.3-" + o.rev_hash
), {"_id": "remove1."+o.rev, "title":"myRemove1"}, "Check document"); ), {
"_id": "remove1.3-" + o.rev_hash,
// add another attachment "title":"myRemove1",
o.attmt_myremove2 = { "_attachments": {"remove3": o.attachment_remove3}
"remove3": { }, "Check document");
"length": 3,
"digest": "md5-hello123" // 8. remove document with wrong revision
}, o.spy(o, "status", 409, "Remove document with wrong revision " +
"revpos":1 "-> 409 Conflict");
}; o.jio.remove({"_id":"remove1", "_rev": "1-a"}, o.f);
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2", o.tick(o);
"_attachments":o.attmt_myremove2};
o.revisions = {"start":1,"ids":["rev2"] }; // 9. remove attachment wrong revision
o.second_old_rev = "2-"+generateRevisionHash(o.doc_myremove2, o.revisions); o.spy(o, "status", 409, "Remove attachment with wrong revision " +
"-> 409 Conflict");
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev, o.jio.remove({"_id":"remove1/remove2", "_rev": "1-a"}, o.f);
o.doc_myremove2); o.tick(o);
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev+"/remove3",
"stu"); // 10. remove document
o.last_rev = "3-" + o.rev_hash;
o.doctree = {"children":[{ o.rev_hash = generateRevisionHash(
"rev": o.very_old_rev, "status": "available", "children": [{ {"_id": "remove1"},
"rev": o.old_rev, "status": "available", "children": [{ {"start": 3, "ids": [o.rev_hash, "oldrev", "veryoldrev"]},
"rev": o.rev, "status": "available", "children":[] true
}] );
}] o.spy(o, "value", {"ok": true, "id": "remove1", "rev": "4-" + o.rev_hash},
},{ "Remove document");
"rev": "1-rev2", "status": "available", "children": [{ o.jio.remove({"_id":"remove1", "_rev": o.last_rev}, o.f);
"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.tick(o); o.tick(o);
o.testtree["children"][0]["children"][0]["children"][0]["children"].push({ // 11. check document tree
"rev": o.second_new_rev, o.doctree.children[0].children[0].children[0].children.unshift({
"status": "deleted", "rev": "4-" + o.rev_hash,
"children": [] "status": "deleted",
}); "children": []
o.testtree["children"][1]["children"].push({
"rev":o.second_old_rev,
"status":"available",
"children":[]
}); });
deepEqual(localstorage.getItem(o.localpath + "/remove1.revision_tree.json"),
deepEqual(localstorage.getItem( o.doctree, "Check document tree");
"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);
o.jio.stop(); o.jio.stop();
}); });
module ( "Jio Revision Storage + Local Storage" );
test ("Scenario", function(){ test ("Scenario", function(){
var o = generateTools(this); var o = generateTools(this);
...@@ -2196,9 +2248,7 @@ test ("Scenario", function(){ ...@@ -2196,9 +2248,7 @@ test ("Scenario", function(){
module ("JIO Replicate Revision Storage"); module ("JIO Replicate Revision Storage");
var testReplicateRevisionStorageGenerator = function ( var testReplicateRevisionStorage = function (sinon, jio_description) {
sinon, jio_description, document_name_have_revision
) {
var o = generateTools(sinon), leavesAction, generateLocalPath; var o = generateTools(sinon), leavesAction, generateLocalPath;
...@@ -2253,15 +2303,15 @@ module ("JIO Replicate Revision Storage"); ...@@ -2253,15 +2303,15 @@ module ("JIO Replicate Revision Storage");
// check document // check document
o.doc._id = o.uuid; o.doc._id = o.uuid;
o.revision = {"start": 0, "ids": []}; o.revisions = {"start": 0, "ids": []};
o.rev = "1-1"; o.rev_hash = generateRevisionHash(o.doc, o.revisions);
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision); o.rev = "1-" + o.rev_hash;
o.leavesAction(function (storage_description, param) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
if (param.revision) { if (param.revision) {
deepEqual(o.response_rev, o.rev, "Check revision"); deepEqual(o.response_rev, o.rev, "Check revision");
doc._id += "." + o.local_rev; doc._id += "." + o.rev;
suffix = "." + o.local_rev; suffix = "." + o.rev;
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
...@@ -2274,10 +2324,10 @@ module ("JIO Replicate Revision Storage"); ...@@ -2274,10 +2324,10 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", { o.spy(o, "value", {
"_id": o.uuid, "_id": o.uuid,
"title": "post document without id", "title": "post document without id",
"_rev": "1-1", "_rev": o.rev,
"_revisions": {"start": 1, "ids": ["1"]}, "_revisions": {"start": 1, "ids": [o.rev_hash]},
"_revs_info": [{"rev": "1-1", "status": "available"}] "_revs_info": [{"rev": o.rev, "status": "available"}]
}, "Get the previous document (without revision)"); }, "Get the generated document, the winner");
o.jio.get({"_id": o.uuid}, { o.jio.get({"_id": o.uuid}, {
"conflicts": true, "conflicts": true,
"revs": true, "revs": true,
...@@ -2287,8 +2337,12 @@ module ("JIO Replicate Revision Storage"); ...@@ -2287,8 +2337,12 @@ module ("JIO Replicate Revision Storage");
// post a new document with id // post a new document with id
o.doc = {"_id": "doc1", "title": "post new doc with id"}; o.doc = {"_id": "doc1", "title": "post new doc with id"};
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, o.rev1_1_hash = generateRevisionHash(o.doc, o.revisions);
"Post document (with id)"); 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.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2297,15 +2351,11 @@ module ("JIO Replicate Revision Storage"); ...@@ -2297,15 +2351,11 @@ module ("JIO Replicate Revision Storage");
// 1-1 // 1-1
// check document // 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) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
if (param.revision) { if (param.revision) {
doc._id += "." + o.local_rev; doc._id += "." + o.rev1_1;
suffix = "." + o.local_rev; suffix = "." + o.rev1_1;
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
...@@ -2318,9 +2368,9 @@ module ("JIO Replicate Revision Storage"); ...@@ -2318,9 +2368,9 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", { o.spy(o, "value", {
"_id": "doc1", "_id": "doc1",
"title": "post new doc with id", "title": "post new doc with id",
"_rev": "1-1", "_rev": o.rev1_1,
"_revisions": {"start": 1, "ids": ["1"]}, "_revisions": {"start": 1, "ids": [o.rev1_1_hash]},
"_revs_info": [{"rev": "1-1", "status": "available"}] "_revs_info": [{"rev": o.rev1_1, "status": "available"}]
}, "Get the previous document (without revision)"); }, "Get the previous document (without revision)");
o.jio.get({"_id": "doc1"}, { o.jio.get({"_id": "doc1"}, {
"conflicts": true, "conflicts": true,
...@@ -2331,8 +2381,11 @@ module ("JIO Replicate Revision Storage"); ...@@ -2331,8 +2381,11 @@ module ("JIO Replicate Revision Storage");
// post same document without revision // post same document without revision
o.doc = {"_id": "doc1", "title": "post same document without revision"}; o.doc = {"_id": "doc1", "title": "post same document without revision"};
o.rev = "1-2"; o.rev1_2_hash = generateRevisionHash(o.doc, o.revisions);
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, 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)"); "Post same document (without revision)");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2342,12 +2395,11 @@ module ("JIO Replicate Revision Storage"); ...@@ -2342,12 +2395,11 @@ module ("JIO Replicate Revision Storage");
// 1-1 1-2 // 1-1 1-2
// check document // check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
if (param.revision) { if (param.revision) {
doc._id += "." + o.local_rev; doc._id += "." + o.rev1_2;
suffix = "." + o.local_rev; suffix = "." + o.rev1_2;
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
...@@ -2357,9 +2409,17 @@ module ("JIO Replicate Revision Storage"); ...@@ -2357,9 +2409,17 @@ module ("JIO Replicate Revision Storage");
}); });
// post a new revision // post a new revision
o.doc = {"_id": "doc1", "title": "post new revision", "_rev": o.rev}; o.doc = {"_id": "doc1", "title": "post new revision", "_rev": o.rev1_2};
o.rev = "2-3"; o.revisions.start += 1;
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, 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)"); "Post document (with revision)");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2371,17 +2431,12 @@ module ("JIO Replicate Revision Storage"); ...@@ -2371,17 +2431,12 @@ module ("JIO Replicate Revision Storage");
// 2-3 // 2-3
// check document // 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) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
delete doc._rev; delete doc._rev;
if (param.revision) { if (param.revision) {
doc._id += "." + o.local_rev; doc._id += "." + o.rev2_3;
suffix = "." + o.local_rev; suffix = "." + o.rev2_3;
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
...@@ -2394,31 +2449,15 @@ module ("JIO Replicate Revision Storage"); ...@@ -2394,31 +2449,15 @@ module ("JIO Replicate Revision Storage");
o.spy(o, "value", { o.spy(o, "value", {
"_id": "doc1", "_id": "doc1",
"title": "post same document without revision", "title": "post same document without revision",
"_rev": "1-2", "_rev": o.rev1_2,
"_revisions": {"start": 1, "ids": ["2"]}, "_revisions": {"start": 1, "ids": [o.rev1_2_hash]},
"_revs_info": [{"rev": "1-2", "status": "available"}], "_revs_info": [{"rev": o.rev1_2, "status": "available"}],
"_conflicts": ["1-1"] "_conflicts": [o.rev1_1]
}, "Get the previous document (with revision)"); }, "Get the previous document (with revision)");
o.jio.get({"_id": "doc1", "_rev": "1-2"}, { o.jio.get({"_id": "doc1", "_rev": o.rev1_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}, {
"conflicts": true, "conflicts": true,
"revs": true, "revs": true,
"revs_info": true, "revs_info": true
}, o.f); }, o.f);
o.tick(o); o.tick(o);
...@@ -2429,8 +2468,11 @@ module ("JIO Replicate Revision Storage"); ...@@ -2429,8 +2468,11 @@ module ("JIO Replicate Revision Storage");
// put document without rev // put document without rev
o.doc = {"_id": "doc1", "title": "put new document"}; o.doc = {"_id": "doc1", "title": "put new document"};
o.rev = "1-4"; o.rev1_4_hash = generateRevisionHash(o.doc, {"start": 0, "ids": []});
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev}, 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") "Put document without rev")
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2442,10 +2484,14 @@ module ("JIO Replicate Revision Storage"); ...@@ -2442,10 +2484,14 @@ module ("JIO Replicate Revision Storage");
// 2-3 // 2-3
// put new revision // put new revision
o.doc = {"_id": "doc1", "title": "put new revision", "_rev": "1-4"}; o.doc = {"_id": "doc1", "title": "put new revision", "_rev": o.rev1_4};
o.rev = "2-5"; o.rev2_5_hash = generateRevisionHash(o.doc, o.rev1_4_history);
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev}, o.rev2_5 = "2-" + o.rev2_5_hash
"Put document without rev") 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.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2456,39 +2502,362 @@ module ("JIO Replicate Revision Storage"); ...@@ -2456,39 +2502,362 @@ module ("JIO Replicate Revision Storage");
// 2-3 2-5 // 2-3 2-5
// putAttachment to inexistent document // 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 // 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 // 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 // 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 // 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 // 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 // remove attachment
// get document o.doc = {
// get inexistent attachment "_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"); // 1-1 1-2 1-4
o.jio.remove({"_id": "doc1", "_rev": "2-5"}, o.f); // | |
// 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); o.tick(o);
// remove document and conflict // get specific document
o.rev = "3-7"; o.doc = {
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, "_id": "doc1",
"Remove document"); "_rev": o.rev4_7,
o.jio.remove({"_id": "doc1", "_rev": "2-3"}, o.f); "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); o.tick(o);
// remove document // get inexistent attachment
o.rev = "2-8"; o.spy(o, "status", 404, "Get inexistent winner attachment" +
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, " -> 404 Not Found");
"Remove document"); o.jio.get({"_id": "doc1/attachment1"}, o.f);
o.jio.remove({"_id": "doc1", "_rev": "1-1"}, 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); 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 // 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"}, { o.jio.get({"_id": "doc1"}, {
"conflicts": true, "conflicts": true,
"revs": true, "revs": true,
...@@ -2501,7 +2870,7 @@ module ("JIO Replicate Revision Storage"); ...@@ -2501,7 +2870,7 @@ module ("JIO Replicate Revision Storage");
}; };
test ("[Revision + Local Storage] Scenario", function () { test ("[Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorage(this, {
"type": "replicaterevision", "type": "replicaterevision",
"storage_list": [{ "storage_list": [{
"type": "revision", "type": "revision",
...@@ -2514,7 +2883,7 @@ module ("JIO Replicate Revision Storage"); ...@@ -2514,7 +2883,7 @@ module ("JIO Replicate Revision Storage");
}); });
}); });
test("[Replicate Revision + Revision + Local Storage] Scenario", function () { test("[Replicate Revision + Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorage(this, {
"type": "replicaterevision", "type": "replicaterevision",
"storage_list": [{ "storage_list": [{
"type": "replicaterevision", "type": "replicaterevision",
...@@ -2530,27 +2899,27 @@ module ("JIO Replicate Revision Storage"); ...@@ -2530,27 +2899,27 @@ module ("JIO Replicate Revision Storage");
}); });
}); });
test ("2x [Revision + Local Storage] Scenario", function () { test ("2x [Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorage(this, {
"type": "replicaterevision", "type": "replicaterevision",
"storage_list": [{ "storage_list": [{
"type": "revision", "type": "revision",
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "ureprevlocloc1", "username": "ureprevlocloc1",
"application_name": "areprevloc1" "application_name": "areprevlocloc1"
} }
}, { }, {
"type": "revision", "type": "revision",
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "ureprevlocloc2", "username": "ureprevlocloc2",
"application_name": "areprevloc2" "application_name": "areprevlocloc2"
} }
}] }]
}); });
}); });
test("2x [Replicate Rev + 2x [Rev + Local]] Scenario", function () { test("2x [Replicate Rev + 2x [Rev + Local]] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorage(this, {
"type": "replicaterevision", "type": "replicaterevision",
"storage_list": [{ "storage_list": [{
"type": "replicaterevision", "type": "replicaterevision",
...@@ -2589,6 +2958,269 @@ module ("JIO Replicate Revision Storage"); ...@@ -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"); 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