Commit d7fcbbaf authored by Tristan Cavelier's avatar Tristan Cavelier

Revision storage redesigned to avoid some unknown errors

parent 049ea125
...@@ -187,6 +187,253 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -187,6 +187,253 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
} }
}; };
////////////////////////////////////////////////////////////////////////////////
that.check = function (command) {
priv.check(
command.cloneDoc(),
command.cloneOption(),
that.success,
that.error
);
};
that.repair = function (command) {
priv.repair(
command.cloneDoc(),
command.cloneOption(),
true,
that.success,
that.error
);
};
priv.check = function (doc, option, success, error) {
priv.repair(doc, option, false, success, error);
};
priv.repair = function (doc, option, repair, callback) {
var functions = {};
// console.log("priv.repair");
callback = callback || priv.emptyFunction;
option = option || {};
functions.begin = function () {
// console.log("repair begin");
functions.getAllDocuments(functions.newParam(
doc,
option,
repair
));
};
functions.newParam = function (doc, option, repair) {
// console.log("repair new param");
var param = {
"doc": doc,
"option": option,
"repair": repair,
"responses": {
"count": 0,
"list": [
// 0: response0
// 1: response1
// 2: response2
],
"stats": {
// responseA: [0, 1]
// responseB: [2]
},
"stats_items": [
// 0: [responseA, [0, 1]]
// 1: [responseB, [2]]
]
},
"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) {
// console.log("repair getAllDocument");
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) {
// console.log("repair dealResults");
return function (method, index, err, response) {
if (param.deal_result_state !== "ok") {
// deal result is in a wrong state, exit
// console.log("repair dealResults wrong state");
return;
}
if (err) {
if (err.status !== 404) {
// get document failed, exit
param.deal_result_state = "error";
// console.log("repair dealResults error");
callback({
"status": 40,
"statusText": "Check Failed",
"error": "check_failed",
"message": "An error occured on the sub storage",
"reason": err.reason
});
return;
}
}
// success to get the document
// add the response in memory
param.responses.count += 1;
param.responses.list[index] = response;
// add the conflicting revision for other synchronizations
functions.addConflicts(param, (response || {})._conflicts);
if (param.responses.count !== param.responses.list.length) {
// this is not the last response, wait for the next response
// console.log("repair dealResults not last");
return;
}
// console.log("repair dealResults last");
// this is now the last response
functions.makeResponsesStats(param.responses);
if (param.responses.stats_items.length === 1) {
// the responses are equals!
// console.log("repair dealResults OK");
callback(undefined, {
"ok": true,
"id": param.doc._id,
"rev": (typeof param.responses.list[0] === "object" ?
param.responses.list[0]._rev : undefined)
});
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"
});
return;
}
// repair
functions.synchronizeAllSubStorage(param);
if (param.option.synchronize_conflicts !== false) {
functions.synchronizeConflicts(param);
}
};
};
functions.addConflicts = function (param, list) {
// console.log("repair addConflicts");
var i;
list = list || [];
for (i = 0; i < list.length; i += 1) {
param.conflicts[list[i]] = true;
}
};
functions.makeResponsesStats = function (responses) {
// console.log("repair makeResponseStats");
var i, str_response;
for (i = 0; i < responses.count; i += 1) {
str_response = JSON.stringify(responses.list[i]);
if (responses.stats[str_response] === undefined) {
responses.stats[str_response] = [];
responses.stats_items.push([
str_response,
responses.stats[str_response]
]);
}
responses.stats[str_response].push(i);
}
};
functions.synchronizeAllSubStorage = function (param) {
// console.log("repair synchronizeAllSubStorage");
var i, j, len = param.responses.stats_items.length;
for (i = 0; i < len; i += 1) {
// browsing responses
for (j = 0; j < len; j += 1) {
// browsing storage list
if (i !== j) {
functions.synchronizeResponseToSubStorage(
param,
param.responses.stats_items[i][0],
param.responses.stats_items[j][1]
);
}
}
}
functions.finished_count -= 1;
};
functions.synchronizeResponseToSubStorage = function (
param,
response,
storage_list
) {
// console.log("repair synchronizeResponseToSubStorage");
var i, new_doc;
if (response === undefined) {
// no response to sync
return;
}
for (i = 0; i < storage_list.length; i += 1) {
new_doc = JSON.parse(response);
new_doc._revs = new_doc._revisions;
delete new_doc._rev;
delete new_doc._revisions;
delete new_doc._conflicts;
functions.finished_count += 1;
priv.send(
"put",
storage_list[i],
new_doc,
param.option,
functions.finished
);
}
};
functions.synchronizeConflicts = function (param) {
// console.log("repair synchronizeConflicts");
var rev, new_doc, new_option;
new_option = priv.clone(param.option);
new_option.synchronize_conflict = false;
for (rev in param.conflicts) {
if (param.conflicts.hasOwnProperty(rev)) {
new_doc = priv.clone(param.doc);
new_doc._rev = rev;
// no need to synchronize all the conflicts again, do it once
functions.getAllDocuments(functions.newParam(
new_doc,
new_option,
param.repair
));
}
}
};
functions.finished_count = 0;
functions.finished = function () {
// console.log("repair finished " + functions.finished_count);
functions.finished_count -= 1;
if (functions.finished_count === 0) {
// console.log("repair ended");
callback(undefined, {"ok": true, "id": doc._id});
}
};
functions.begin();
};
////////////////////////////////////////////////////////////////////////////////
/** /**
* Post the document metadata to all sub storages * Post the document metadata to all sub storages
* @method post * @method post
...@@ -397,6 +644,9 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -397,6 +644,9 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
} }
} }
that.success(response); that.success(response);
setTimeout(function () {
priv.repair({"_id": doc._id}, command.cloneOption(), true);
});
}; };
functions.error_count = 0; functions.error_count = 0;
functions.error = function (err) { functions.error = function (err) {
......
...@@ -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,836 @@ jIO.addStorageType('revision', function (spec, my) { ...@@ -70,1200 +81,836 @@ 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 console.log(string);
}); revision_history.start += 1;
if (document_tree.rev === revision) { revision_history.ids.unshift(priv.hashCode(string));
result = revs_info; doc._revs = revision_history;
return; doc._rev = revision_history.start + "-" + revision_history.ids[0];
revs_info.unshift({
"rev": doc._rev,
"status": deleted_flag ? "deleted" : "available"
});
doc._revs_info = revs_info;
return doc;
};
/**
* Gets the revs info from the document tree
* @method getRevisionInfo
* @param {string} revision The revision to search for
* @param {object} doc_tree The document tree
* @return {array} The revs info
*/
priv.getRevisionInfo = function (revision, doc_tree) {
var getRevisionInfoRec;
getRevisionInfoRec = function (doc_tree) {
var i, child, revs_info;
for (i = 0; i < doc_tree.children.length; i += 1) {
child = doc_tree.children[i];
if (child.rev === revision) {
return [{"rev": child.rev, "status": child.status}];
} }
} revs_info = getRevisionInfoRec(child);
// 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) {
selected_node = node;
return "node_selected";
} }
for (i = 0; i < node.children.length; i += 1) { if (doc_tree.children.length === 0) {
if (selectNode(node.children[i]) === "node_selected") { 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);
} }
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 console.log(doc);
* @return {boolean} true/false priv.send("put", doc, option, callback);
*/ };
priv.isRevisionALeaf = function (document_tree, revision) { priv.remove = function (doc, option, callback) {
var result, search; priv.send("remove", doc, option, callback);
result = undefined; };
// search method fills "result" with the good revs info priv.putAttachment = function (attachment, option, callback) {
search = function (document_tree) { priv.send("putAttachment", attachment, option, callback);
var i; };
if (document_tree.rev !== undefined) {
// node is not root priv.getDocument = function (doc, option, callback) {
if (document_tree.rev === revision) { doc = priv.clone(doc);
if (document_tree.children.length === 0) { doc._id = doc._id + "." + doc._rev;
// This node is a leaf delete doc._attachment;
result = true; delete doc._rev;
return; delete doc._revs;
} delete doc._revs_info;
result = false; priv.get(doc, option, callback);
};
priv.getAttachment = priv.get;
priv.putDocument = function (doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + "." + doc._rev;
delete doc._attachment;
delete doc._data;
delete doc._mimetype;
delete doc._rev;
delete doc._revs;
delete doc._revs_info;
priv.put(doc, option, callback);
};
priv.getRevisionTree = function (doc, option, callback) {
doc = priv.clone(doc);
doc._id = doc._id + priv.doc_tree_suffix;
priv.get(doc, option, callback);
};
priv.getAttachmentList = function (doc, option, callback) {
console.log("p getAttachmentList");
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. console.log("p putAttachmentList");
* @method getRevisionFromPosition var i, dealResults, state = "ok", count = 0, attachment;
* @param {array} revs_info The revs_info array attachment_list = attachment_list || [];
* @param {number} rev_pos The revision position number dealResults = function (index) {
* @return {string} The revision of the good position (empty string if fail) return function (err, response) {
*/ if (state !== "ok") {
priv.getRevisionFromPosition = function (revs_info, rev_pos) { return;
var i; }
for (i = revs_info.length - 1; i >= 0; i -= 1) { count -= 1;
if (priv.revisionToArray(revs_info[i].rev)[0] === rev_pos) { if (err) {
return revs_info[i].rev; state = "error";
return callback(err, undefined);
}
if (count === 0) {
state = "finished";
callback(undefined, {"id": doc._id, "ok": true});
}
};
};
for (i = 0; i < attachment_list.length; i += 1) {
attachment = attachment_list[i];
if (attachment !== undefined) {
count += 1;
attachment._id = doc._id + "." + doc._rev + "/" +
attachment._attachment;
delete attachment._attachment;
priv.putAttachment(attachment, option, dealResults(i));
} }
} }
return ''; if (count === 0) {
return callback(undefined, {"id": doc._id, "ok": true});
}
}; };
/** 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(); console.log("c begin");
if (option.max_retry === 0) { var check_error;
option.max_retry = 3; doc._id = doc._id || priv.generateUuid();
if (specific_parameter.revision_needed && !doc._rev) {
return onEnd(priv.conflictError(
"Document update conflict",
"No document revision was provided"
), undefined);
}
// check revision format
check_error = priv.checkDocumentRevisionFormat(doc);
if (check_error !== undefined) {
return onEnd(check_error, undefined);
}
priv.getRevisionTree(doc, option, callback.getRevisionTree);
};
callback.getRevisionTree = function (err, response) {
console.log("c getRevisionTree");
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, console.log(JSON.stringify(doc_tree));
option, winner_info = priv.getWinnerRevsInfo(doc_tree);
function (response) { console.log(winner_info);
doctree = response; if (winner_info.length === 0) {
f.updateRevsInfo(); return onEnd(priv.notFoundError(
f.getDocument(); "Document not found",
}, "missing"
function (err) { ), undefined);
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) {
f.updateRevsInfo = function () { prev_doc = {
if (doc._revs) { "_id": doc._id,
revs_info = priv.updateDocumentTree(doctree, doc._revs); "_rev": doc._revs_info[1].rev
} else { };
revs_info = priv.postToDocumentTree(doctree, doc); }
// 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) {
console.log("this one");
return onEnd(priv.notFoundError(
"Unable to remove an inexistent document",
"missing"
), undefined);
} }
priv.putDocument(doc, option, callback.putDocument);
}; };
f.postDocument = function (attachment_list) { callback.getDocument = function (err, res_doc) {
doc._id = docid + "." + revs_info[0].rev; console.log("c getDocument");
delete doc._rev; var k, conflicts;
delete doc._revs; if (err) {
that.addJob( if (err.status === 404) {
"post", if (specific_parameter.remove ||
priv.substorage, specific_parameter.removeAttachment) {
doc, return onEnd(priv.conflictError(
command.cloneOption(), "Document update conflict",
function () { "Document is missing"
var i; ), undefined);
if (attachment_list.length === 0) {
f.sendDocumentTree();
} else {
f.send_document_tree_count = attachment_list.length;
for (i = 0; i < attachment_list.length; i += 1) {
f.copyAttachment(attachment_list[i].id,
attachment_list[i].attachment);
}
} }
}, if (specific_parameter.get) {
function (err) { return onEnd(priv.notFoundError(
switch (err.status) { "Unable to find the document",
case 409: "missing"
// file already exists ), undefined);
f.sendDocumentTree();
break;
default:
err.message = "Cannot upload document";
f.error(err);
break;
} }
res_doc = {};
} else {
err.message = "Cannot get document";
return onEnd(err, undefined);
} }
);
};
f.copyAttachment = function (attachmentid, attachment) {
that.addJob(
"get",
priv.substorage,
prev_doc._id + "/" + attachmentid,
command.cloneOption(),
function (response) {
attachment._data = response;
that.addJob(
"putAttachment",
priv.substorage,
attachment,
command.cloneOption(),
function (response) {
f.sendDocumentTree();
},
function (err) {
err.message = "Cannot copy previous attachment";
f.error(err);
}
);
},
function (err) {
err.message = "Cannot get previous attachment";
f.error(err);
}
);
};
f.send_document_tree_count = 0;
f.sendDocumentTree = function () {
f.send_document_tree_count -= 1;
if (f.send_document_tree_count > 0) {
return;
} }
doctree._id = docid + priv.doctree_suffix; if (specific_parameter.get) {
that.addJob( res_doc._id = doc._id;
"put", res_doc._rev = doc._rev;
priv.substorage, if (option.conflicts === true) {
doctree, conflicts = priv.getConflicts(doc._rev, doc_tree);
command.cloneOption(), if (conflicts) {
function () { res_doc._conflicts = conflicts;
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);
} }
); if (option.revs === true) {
}; res_doc._revisions = doc._revs;
f.error = function (err) { }
f.error = function () {}; if (option.revs_info === true) {
that.error(err); res_doc._revs_info = doc._revs_info;
}; }
f.getDocumentTree(); return onEnd(undefined, res_doc);
};
/**
* Update the document metadata and update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
that.post(command);
};
/**
* Create/Update the document attachment and update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
var functions = {}, doc, doctree, revs_info, prev_doc;
doc = command.cloneDoc();
functions.begin = function () {
if (typeof doc._rev === "string" && !priv.checkRevisionFormat(doc._rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match " +
"^[0-9]+-[0-9a-zA-Z]+$",
"reason": "Previous revision is wrong"
});
return;
}
functions.getDocumentTree();
};
functions.getDocumentTree = function () {
var option = command.cloneOption();
if (option.max_retry === 0) {
option.max_retry = 3;
} }
that.addJob( if (specific_parameter.removeAttachment) {
"get", // copy metadata (not beginning by "_" to document
priv.substorage, for (k in res_doc) {
command.getDocId() + priv.doctree_suffix, if (res_doc.hasOwnProperty(k) && !k.match("^_")) {
option, doc[k] = res_doc[k];
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);
} }
}; if (specific_parameter.remove) {
functions.postEmptyDocument = function () { priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree);
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 { } else {
that.addJob( console.log("res_doc");
"get", console.log(res_doc);
priv.substorage, priv.getAttachmentList(res_doc, option, callback.getAttachmentList);
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) { callback.getAttachmentList = function (err, res_list) {
that.addJob( console.log("c getAttachmentList");
"post", var i, attachment_found = false;
priv.substorage, if (err) {
command.getDocId() + "." + revs_info[0].rev, err.message = "Cannot get attachment";
command.getOption(), return onEnd(err, undefined);
function (response) { }
var i; attachment_list = res_list || [];
if (attachment_list.length === 0) { if (specific_parameter.getAttachment) {
functions.postAttachment(); // getting specific attachment
} else { for (i = 0; i < attachment_list.length; i += 1) {
functions.post_attachment_count = attachment_list.length; if (attachment_list[i] &&
for (i = 0; i < attachment_list.length; i += 1) { doc._attachment ===
functions.copyAttachment(attachment_list[i].id, attachment_list[i]._attachment) {
attachment_list[i].attachment); return onEnd(attachment_list[i]._data);
}
} }
},
function (err) {
err.message = "Cannot upload document";
that.error(err);
} }
); return onEnd(priv.notFoundError(
}; "Unable to get an inexistent attachment",
functions.copyAttachment = function (attachmentid, attachment) { "missing"
that.addJob( ), undefined);
"get", }
priv.substorage, if (specific_parameter.remove_from_attachment_list) {
prev_doc._id + "/" + attachmentid, // removing specific attachment
command.cloneOption(), for (i = 0; i < attachment_list.length; i += 1) {
function (response) { if (attachment_list[i] &&
attachment._data = response; specific_parameter.remove_from_attachment_list._attachment ===
that.addJob( attachment_list[i]._attachment) {
"putAttachment", attachment_found = true;
priv.substorage, attachment_list[i] = undefined;
attachment, break;
command.cloneOption(), }
function (response) { }
functions.postAttachment(); if (!attachment_found) {
}, return onEnd(priv.notFoundError(
function (err) { "Unable to remove an inexistent attachment",
err.message = "Cannot copy previous attachment"; "missing"
functions.error(err); ), undefined);
}
);
},
function (err) {
err.message = "Cannot copy previous attachment";
functions.error(err);
} }
);
};
functions.post_attachment_count = 0;
functions.postAttachment = function () {
functions.post_attachment_count -= 1;
if (functions.post_attachment_count > 0) {
return;
} }
that.addJob( priv.putDocument(doc, option, callback.putDocument);
"putAttachment", };
priv.substorage, callback.putDocument = function (err, response) {
{ console.log("c putDocument");
"_id": command.getDocId() + "." + revs_info[0].rev + "/" + var i, attachment_found = false;
command.getAttachmentId(), if (err) {
"_mimetype": command.getAttachmentMimeType(), err.message = "Cannot post the document";
"_data": command.getAttachmentData() return onEnd(err, undefined);
}, }
command.cloneOption(), if (specific_parameter.add_to_attachment_list) {
function () { // adding specific attachment
functions.sendDocumentTree(); attachment_list = attachment_list || [];
}, for (i = 0; i < attachment_list.length; i += 1) {
function (err) { if (specific_parameter.add_to_attachment_list._attachment ===
switch (err.status) { attachment_list[i]._attachment) {
case 409: attachment_found = true;
// file already exists attachment_list[i] = specific_parameter.add_to_attachment_list;
functions.sendDocumentTree();
break;
default:
err.message = "Cannot upload attachment";
functions.error(err);
break; break;
} }
} }
); if (!attachment_found) {
}; attachment_list.unshift(specific_parameter.add_to_attachment_list);
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);
} }
}
priv.putAttachmentList(
doc,
option,
attachment_list,
callback.putAttachmentList
); );
}; };
functions.error = function (err) { callback.putAttachmentList = function (err, response) {
functions.error = function () {}; console.log("c putAttachmentList");
that.error(err); if (err) {
err.message = "Cannot copy attacments to the document";
return onEnd(err, undefined);
}
priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree);
};
callback.putDocumentTree = function (err, response) {
console.log("c putDocumentTree");
if (err) {
err.message = "Cannot update the document history";
return onEnd(err, undefined);
}
onEnd(undefined, {
"ok": true,
"id": doc._id + (specific_parameter.putAttachment ||
specific_parameter.removeAttachment ||
specific_parameter.getAttachment ?
"/" + doc._attachment : ""),
"rev": doc._rev
});
// if (option.keep_revision_history !== true) {
// // priv.remove(prev_doc, option, function () {
// // - change "available" status to "deleted"
// // - remove attachments
// // - done, no callback
// // });
// }
}; };
functions.begin(); callback.begin();
}; };
/** /**
* Get the document metadata or attachment. * Post the document metadata and create or update a document tree.
* Options: * Options:
* - {boolean} revs Add simple revision history (false by default). * - {boolean} keep_revision_history To keep the previous revisions
* - {boolean} revs_info Add revs info (false by default). * (false by default) (NYI).
* - {boolean} conflicts Add conflict object (false by default). * @method post
* @method get
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.get = function (command) { that.post = function (command) {
var f = {}, doctree, revs_info, prev_rev, option; priv.revisionGenericRequest(
option = command.cloneOption(); command.cloneDoc(),
if (option.max_retry === 0) { command.cloneOption(),
option.max_retry = 3; {},
} function (err, response) {
prev_rev = command.getDocInfo("_rev"); if (err) {
if (typeof prev_rev === "string") { return that.error(err);
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);
} }
); that.success(response);
}; }
if (command.getAttachmentId() && prev_rev !== undefined) { );
f.getDocument(command.getDocId() + "." + prev_rev +
"/" + command.getAttachmentId());
} else {
f.getDocumentTree();
}
}; };
/** /**
* Remove document or attachment. * 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
* @method remove * (false by default) (NYI).
* @method put
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.remove = function (command) { that.put = function (command) {
var f = {}, del_rev, option, new_doc, revs_info; priv.revisionGenericRequest(
option = command.cloneOption(); command.cloneDoc(),
if (option.max_retry === 0) { command.cloneOption(),
option.max_retry = 3; {},
} function (err, response) {
del_rev = command.getDoc()._rev; if (err) {
return that.error(err);
}
that.success(response);
}
);
};
f.removeDocument = function (docid, doctree) {
if (command.getOption("keep_revision_history") !== true) {
if (command.getAttachmentId() === undefined) {
// update tree
revs_info = priv.postToDocumentTree(
doctree,
command.getDoc(),
true
);
// remove revision
that.addJob(
"remove",
priv.substorage,
docid,
option,
function () {
// put tree
doctree._id = command.getDocId() + priv.doctree_suffix;
that.addJob(
"put",
priv.substorage,
doctree,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": command.getDocId(),
"rev": revs_info[0].rev
});
},
function () {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Cannot update document tree"
});
return;
}
);
},
function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "File not found",
"reason": "Document was not found"
});
return;
}
);
} else {
// get previsous document
that.addJob(
"get",
priv.substorage,
command.getDocId() + "." + del_rev,
option,
function (response) {
// update tree
revs_info = priv.postToDocumentTree(doctree, command.getDoc());
new_doc = response;
delete new_doc._attachments;
new_doc._id = new_doc._id + "." + revs_info[0].rev;
// post new document version that.putAttachment = function (command) {
that.addJob( priv.revisionGenericRequest(
"post", command.cloneDoc(),
priv.substorage, command.cloneOption(),
new_doc, {
command.cloneOption(), "doc_id": command.getDocId(),
function () { "attachment_id": command.getAttachmentId(),
// put tree "add_to_attachment_list": {
doctree._id = command.getDocId() + priv.doctree_suffix; "_attachment": command.getAttachmentId(),
that.addJob( "_mimetype": command.getAttachmentMimeType(),
"put", "_data": command.getAttachmentData()
priv.substorage, },
doctree, "putAttachment": true
command.cloneOption(), },
function () { function (err, response) {
that.success({ if (err) {
"ok": true, return that.error(err);
"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.remove = function (command) {
that.addJob( if (command.getAttachmentId()) {
"get", return that.removeAttachment(command);
priv.substorage, }
command.getDocId() + priv.doctree_suffix, priv.revisionGenericRequest(
option, command.cloneDoc(),
function (response) { command.cloneOption(),
response._conflicts = priv.getLeavesFromDocumentTree(response); {
"revision_needed": true,
"remove": true
},
function (err, response) {
if (err) {
return that.error(err);
}
that.success(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
...@@ -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 = {
...@@ -1264,7 +1269,8 @@ test ("Post", function(){ ...@@ -1264,7 +1269,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 +1299,92 @@ test ("Post", function(){ ...@@ -1293,8 +1299,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(){
...@@ -1562,7 +1652,7 @@ test("Put Attachment", function () { ...@@ -1562,7 +1652,7 @@ test("Put Attachment", function () {
// 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},
...@@ -1601,9 +1691,9 @@ test("Put Attachment", function () { ...@@ -1601,9 +1691,9 @@ test("Put Attachment", function () {
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},
...@@ -1646,9 +1736,9 @@ test("Put Attachment", function () { ...@@ -1646,9 +1736,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},
...@@ -1723,15 +1813,16 @@ test ("Get", function(){ ...@@ -1723,15 +1813,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 +1839,15 @@ test ("Get", function(){ ...@@ -1748,14 +1839,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"] = [{
...@@ -1780,6 +1872,7 @@ test ("Get", function(){ ...@@ -1780,6 +1872,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 +1886,16 @@ test ("Get", function(){ ...@@ -1793,18 +1886,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)");
...@@ -1820,13 +1911,13 @@ test ("Get", function(){ ...@@ -1820,13 +1911,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 +1926,7 @@ test ("Get", function(){ ...@@ -1835,7 +1926,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,188 +1953,135 @@ test ("Remove", function(){ ...@@ -1862,188 +1953,135 @@ 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({ deepEqual(localstorage.getItem(o.localpath + "/remove1.revision_tree.json"),
"rev":o.second_old_rev, o.doctree, "Check document tree");
"status":"available",
"children":[]
});
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1.revision_tree.json"
), o.testtree, "Check document tree");
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.second_new_rev+"/remove2"
), null, "Check attachment");
deepEqual(localstorage.getItem(
"jio/localstorage/urevrem/arevrem/remove1."+o.second_new_rev
), null, "Check document");
// remove document without revision
o.spy (o,"status", 409, "409 - Removing document (no revision)");
o.jio.remove({"_id":"remove1"}, o.f);
o.tick(o);
o.jio.stop(); o.jio.stop();
}); });
...@@ -2196,9 +2234,7 @@ test ("Scenario", function(){ ...@@ -2196,9 +2234,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;
...@@ -2285,223 +2321,223 @@ module ("JIO Replicate Revision Storage"); ...@@ -2285,223 +2321,223 @@ module ("JIO Replicate Revision Storage");
}, o.f); }, o.f);
o.tick(o); o.tick(o);
// 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.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Post document (with id)"); // "Post document (with id)");
o.jio.post(o.doc, o.f); // o.jio.post(o.doc, o.f);
o.tick(o); // o.tick(o);
// / // // /
// | // // |
// 1-1 // // 1-1
// check document // // check document
o.local_rev_hash = generateRevisionHash(o.doc, o.revision); // o.local_rev_hash = generateRevisionHash(o.doc, o.revision);
o.local_rev = "1-" + o.local_rev_hash; // o.local_rev = "1-" + o.local_rev_hash;
o.specific_rev_hash = o.local_rev_hash; // o.specific_rev_hash = o.local_rev_hash;
o.specific_rev = o.local_rev; // 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.local_rev;
suffix = "." + o.local_rev; // suffix = "." + o.local_rev;
} // }
deepEqual( // deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + // localstorage.getItem(generateLocalPath(storage_description) +
"/doc1" + suffix), // "/doc1" + suffix),
doc, "Check document" // doc, "Check document"
); // );
}); // });
// get the post document without revision // // get the post document without revision
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": "1-1",
"_revisions": {"start": 1, "ids": ["1"]}, // "_revisions": {"start": 1, "ids": ["1"]},
"_revs_info": [{"rev": "1-1", "status": "available"}] // "_revs_info": [{"rev": "1-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,
"revs": true, // "revs": true,
"revs_info": true // "revs_info": true
}, o.f); // }, o.f);
o.tick(o); // o.tick(o);
// 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.rev = "1-2";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, // o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"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);
// / // // /
// / \ // // / \
// 1-1 1-2 // // 1-1 1-2
// check document // // check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision); // 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.local_rev;
suffix = "." + o.local_rev; // suffix = "." + o.local_rev;
} // }
deepEqual( // deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + // localstorage.getItem(generateLocalPath(storage_description) +
"/doc1" + suffix), // "/doc1" + suffix),
doc, "Check document" // doc, "Check document"
); // );
}); // });
// 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.rev};
o.rev = "2-3"; // o.rev = "2-3";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, // o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"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);
// / // // /
// / \ // // / \
// 1-1 1-2 // // 1-1 1-2
// | // // |
// 2-3 // // 2-3
// check document // // check document
o.revision.start += 1; // o.revision.start += 1;
o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-")); // o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-"));
o.doc._rev = o.local_rev; // o.doc._rev = o.local_rev;
o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision); // o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision);
o.specific_rev_conflict = o.local_rev; // 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.local_rev;
suffix = "." + o.local_rev; // suffix = "." + o.local_rev;
} // }
deepEqual( // deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + // localstorage.getItem(generateLocalPath(storage_description) +
"/doc1" + suffix), // "/doc1" + suffix),
doc, "Check document" // doc, "Check document"
); // );
}); // });
// get the post document with revision // // get the post document with revision
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": "1-2",
"_revisions": {"start": 1, "ids": ["2"]}, // "_revisions": {"start": 1, "ids": ["2"]},
"_revs_info": [{"rev": "1-2", "status": "available"}], // "_revs_info": [{"rev": "1-2", "status": "available"}],
"_conflicts": ["1-1"] // "_conflicts": ["1-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": "1-2"}, {
"conflicts": true, // "conflicts": true,
"revs": true, // "revs": true,
"revs_info": true, // "revs_info": true,
}, o.f); // }, o.f);
o.tick(o); // o.tick(o);
// get the post document with specific revision // // get the post document with specific revision
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": o.specific_rev, // "_rev": o.specific_rev,
"_revisions": {"start": 1, "ids": [o.specific_rev_hash]}, // "_revisions": {"start": 1, "ids": [o.specific_rev_hash]},
"_revs_info": [{"rev": o.specific_rev, "status": "available"}], // "_revs_info": [{"rev": o.specific_rev, "status": "available"}],
"_conflicts": [o.specific_rev_conflict] // "_conflicts": [o.specific_rev_conflict]
}, "Get a previous document (with local storage revision)"); // }, "Get a previous document (with local storage revision)");
o.jio.get({"_id": "doc1", "_rev": o.specific_rev}, { // 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);
// put document without id // // put document without id
o.spy(o, "status", 20, "Put document without id") // o.spy(o, "status", 20, "Put document without id")
o.jio.put({}, o.f); // o.jio.put({}, o.f);
o.tick(o); // o.tick(o);
// 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.rev = "1-4";
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev}, // o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev},
"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);
// __/__ // // __/__
// / | \ // // / | \
// 1-1 1-2 1-4 // // 1-1 1-2 1-4
// | // // |
// 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": "1-4"};
o.rev = "2-5"; // o.rev = "2-5";
o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev}, // o.spy(o, "value", {"id": "doc1", "ok": true, "rev": o.rev},
"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);
// __/__ // // __/__
// / | \ // // / | \
// 1-1 1-2 1-4 // // 1-1 1-2 1-4
// | | // // | |
// 2-3 2-5 // // 2-3 2-5
// putAttachment to inexistent document // // putAttachment to inexistent document
// putAttachment // // putAttachment
// get document // // get document
// get attachment // // get attachment
// put document // // put document
// get document // // get document
// get attachment // // get attachment
// remove attachment // // remove attachment
// get document // // get document
// get inexistent attachment // // get inexistent attachment
// remove document and conflict // // remove document and conflict
o.rev = "3-6"; // o.rev = "3-6";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, // o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document"); // "Remove document");
o.jio.remove({"_id": "doc1", "_rev": "2-5"}, o.f); // o.jio.remove({"_id": "doc1", "_rev": "2-5"}, o.f);
o.tick(o); // o.tick(o);
// remove document and conflict // // remove document and conflict
o.rev = "3-7"; // o.rev = "3-7";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, // o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document"); // "Remove document");
o.jio.remove({"_id": "doc1", "_rev": "2-3"}, o.f); // o.jio.remove({"_id": "doc1", "_rev": "2-3"}, o.f);
o.tick(o); // o.tick(o);
// remove document // // remove document
o.rev = "2-8"; // o.rev = "2-8";
o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev}, // o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Remove document"); // "Remove document");
o.jio.remove({"_id": "doc1", "_rev": "1-1"}, o.f); // o.jio.remove({"_id": "doc1", "_rev": "1-1"}, o.f);
o.tick(o); // o.tick(o);
// get inexistent document // // get inexistent document
o.spy(o, "status", 404, "Get inexistent document"); // o.spy(o, "status", 404, "Get inexistent document");
o.jio.get({"_id": "doc1"}, { // o.jio.get({"_id": "doc1"}, {
"conflicts": true, // "conflicts": true,
"revs": true, // "revs": true,
"revs_info": true // "revs_info": true
}, o.f); // }, o.f);
o.tick(o); // o.tick(o);
o.jio.stop(); o.jio.stop();
}; };
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 +2550,7 @@ module ("JIO Replicate Revision Storage"); ...@@ -2514,7 +2550,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 +2566,27 @@ module ("JIO Replicate Revision Storage"); ...@@ -2530,27 +2566,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 +2625,159 @@ module ("JIO Replicate Revision Storage"); ...@@ -2589,6 +2625,159 @@ module ("JIO Replicate Revision Storage");
}] }]
}); });
}); });
test("Synchronisation", function () {
var o = generateTools(this);
o.jio = JIO.newJio({
"type": "replicaterevision",
"storage_list": [{
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc1",
"application_name": "asyncreprevlocloc1"
}
}, {
"type": "revision",
"sub_storage": {
"type": "local",
"username": "usyncreprevlocloc2",
"application_name": "asyncreprevlocloc2"
}
}]
});
o.localpath1 = "jio/localstorage/usyncreprevlocloc1/asyncreprevlocloc1";
o.localpath2 = "jio/localstorage/usyncreprevlocloc2/asyncreprevlocloc2";
// 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.localpath1 + "/" + o.doc1_1._id, o.doc1_1);
localstorage.setItem(o.localpath2 + "/" + o.doc1_1._id, o.doc1_1);
// no synchronisation
o.spy(o, "value", {"_id": "doc1", "_rev": "1-1", "title": "A"},
"Get document");
o.jio.get({"_id": "doc1"}, o.f);
o.tick(o);
// check documents from localstorage
deepEqual([
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
], [o.doctree1_1, o.doctree1_1], "Check revision trees, no synchro");
// 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"};
localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
o.doctree2_2);
localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id, o.doc2_2);
// document synchronisation without conflict
o.spy(o, "value", {"_id": "doc1", "_rev": "1-2", "title": "B"},
"Get document");
o.jio.get({"_id": "doc1"}, o.f);
o.tick(o, 50000);
// check documents from localstorage
deepEqual([
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
], [o.doctree2_2, o.doctree2_2], "Check revision trees, rev synchro");
// add documents to localstorage
o.doctree2_2.children[0].children.unshift({
"rev": "2-223",
"status": "available",
"children": []
});
o.doc2_2 = {"_id": "doc1.2-223", "title": "B"};
localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
o.doctree2_2);
localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id, o.doc2_2);
// document synchronisation with conflict
o.spy(o, "value", {"_id": "doc1", "_rev": "1-2", "title": "B"},
"Get document");
o.jio.get({"_id": "doc1"}, o.f);
o.tick(o, 50000);
// check documents from localstorage
deepEqual([
localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
], [o.doctree2_2, o.doctree2_2], "Check revision trees, rev synchro");
////////////////////////////////////////////////////////////////////////////////
// // 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"};
// localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
// o.doctree2_2);
// localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id, o.doc2_2);
// // document synchronisation without conflict
// o.spy(o, "value", {"_id": "doc1", "_rev": "1-2", "title": "B"},
// "Get document");
// o.jio.get({"_id": "doc1"}, o.f);
// o.tick(o, 50000);
// // check documents from localstorage
// deepEqual([
// localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
// localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
// ], [o.doctree2_2, o.doctree2_2], "Check revision trees, rev synchro");
// // add documents to localstorage
// o.doctree2_2.children[0].children.unshift({
// "rev": "2-223",
// "status": "available",
// "children": []
// });
// o.doc2_2 = {"_id": "doc1.2-223", "title": "B"};
// localstorage.setItem(o.localpath1 + "/doc1.revision_tree.json",
// o.doctree2_2);
// localstorage.setItem(o.localpath1 + "/" + o.doc2_2._id, o.doc2_2);
// // document synchronisation with conflict
// o.spy(o, "value", {"_id": "doc1", "_rev": "1-2", "title": "B"},
// "Get document");
// o.jio.get({"_id": "doc1"}, o.f);
// o.tick(o, 50000);
// // check documents from localstorage
// deepEqual([
// localstorage.getItem(o.localpath1 + "/doc1.revision_tree.json"),
// localstorage.getItem(o.localpath2 + "/doc1.revision_tree.json"),
// ], [o.doctree2_2, o.doctree2_2], "Check revision trees, rev synchro");
o.jio.stop();
});
/* /*
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