Commit fdb950be authored by Sven Franck's avatar Sven Franck

revisionstorage get/remove

parent 0c109099
......@@ -117,13 +117,6 @@ jIO.addStorageType('revision', function (spec, my) {
return {"rev":revision,"status":status,"children":children || []};
};
/**
* Gets the winner revision from a document tree.
* The winner is the deeper revision on the left.
* @method getWinnerRevisionFromDocumentTree
* @param {object} document_tree The document tree
* @return {array} The winner revs info array
*/
/**
* Gets the winner revision from a document tree.
* The winner is the deeper revision on the left.
......@@ -163,7 +156,11 @@ jIO.addStorageType('revision', function (spec, my) {
}
};
search(document_tree, 0);
return result;
// xxx shouldn't this return only a single revision
// from the document tree? Currently multiple revisions
// are passed back
// return result;
return [result[0]];
};
/**
......@@ -171,12 +168,19 @@ jIO.addStorageType('revision', function (spec, my) {
* @method postToDocumentTree
* @param {object} doctree The document tree object
* @param {object} doc The document object
* @param {boolean} set_node_to_deleted true/false
* @return {array} The added document revs_info
*/
priv.postToDocumentTree = function (doctree, doc) {
var i, revs_info, next_rev, next_rev_str, selectNode, selected_node;
revs_info = [];
priv.postToDocumentTree = function (doctree, doc, set_node_to_deleted) {
var i, revs_info, next_rev, next_rev_str, next_rev_status,
selectNode, selected_node,
revs_info = [],
selected_node = doctree;
if (doc._rev === undefined && priv.missing_revision){
doc._rev = priv.missing_revision;
}
selectNode = function (node) {
var i;
if (typeof node.rev !== "undefined") {
......@@ -214,15 +218,17 @@ jIO.addStorageType('revision', function (spec, my) {
next_rev = priv.generateNextRevision(
doc._rev || 0, JSON.stringify(doc) + JSON.stringify(revs_info));
next_rev_str = next_rev.join("-");
next_rev_status = set_node_to_deleted === true ? "deleted" : "available";
// 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": "available"
"status": next_rev_status
});
if (selected_node.children[i].status !== "available") {
selected_node.children[i].status = "available";
if (selected_node.children[i].status !== next_rev_status) {
selected_node.children[i].status = next_rev_status;
}
return revs_info;
}
......@@ -270,6 +276,23 @@ jIO.addStorageType('revision', function (spec, my) {
return result;
};
/**
* Check if revision is a leaf
* @method isRevisionALeaf
* @param {string} revision revision to check
* @param {array} leaves all leaves on tree
* @return {boolean} true/false
*/
priv.isRevisionALeaf = function (revision, leaves) {
var i;
for (i = 0; i < leaves.length; i+=1) {
if (leaves[i] === revision){
return true;
}
}
return false;
};
/**
* Convert revs_info to a simple revisions history
* @method revsInfoToHistory
......@@ -299,6 +322,7 @@ jIO.addStorageType('revision', function (spec, my) {
var f = {}, doctree, revs_info, doc, docid;
doc = command.cloneDoc();
docid = command.getDocId();
if (typeof doc._rev === "string" &&
!priv.checkRevisionFormat(doc._rev)) {
that.error({
......@@ -329,6 +353,7 @@ jIO.addStorageType('revision', function (spec, my) {
docid+priv.doctree_suffix,
option,
function (response) {
doctree = response;
if (priv.update_doctree_allowed) {
f.postDocument("put");
......@@ -356,8 +381,12 @@ jIO.addStorageType('revision', function (spec, my) {
);
};
f.postDocument = function (doctree_update_method) {
revs_info = priv.postToDocumentTree(doctree, doc);
revs_info = priv.postToDocumentTree(doctree, doc,
priv.update_doctree_on_remove);
// I don't understand why?
doc._id = docid+"."+revs_info[0].rev;
that.addJob(
"post",
priv.substorage,
......@@ -365,7 +394,8 @@ jIO.addStorageType('revision', function (spec, my) {
command.cloneOption(),
function (response) {
f.sendDocumentTree (doctree_update_method);
}, function (err) {
},
function (err) {
switch(err.status) {
case 409:
// file already exists
......@@ -387,12 +417,14 @@ jIO.addStorageType('revision', function (spec, my) {
doctree,
command.cloneOption(),
function (response) {
that.success({
"ok":true,
"id":docid,
"rev":revs_info[0].rev
})
}, function (err) {
});
},
function (err) {
// xxx do we try to delete the posted document ?
err.message = "Cannot save document revision tree";
that.error(err);
......@@ -427,7 +459,7 @@ jIO.addStorageType('revision', function (spec, my) {
* @param {object} command The JIO command
*/
that.get = function (command) {
var f = {}, doctree, revs_info, prev_rev, revs_info, option;
var f = {}, doctree, revs_info, prev_rev, rev_path, option;
option = command.cloneOption();
if (option["max_retry"] === 0) {
option["max_retry"] = 3;
......@@ -479,7 +511,6 @@ jIO.addStorageType('revision', function (spec, my) {
docid,
option,
function (response) {
var i, conflict_array;
if (typeof response !== "string") {
response._id = command.getDocId();
response._rev = prev_rev;
......@@ -506,11 +537,218 @@ jIO.addStorageType('revision', function (spec, my) {
);
};
if (command.getAttachmentId()) {
f.getDocument(command.getDocId()+"/"+command.getAttachmentId());
// xxx: no revision passed = get tree and winning revision
if ( prev_rev === undefined ){
that.addJob(
"get",
priv.substorage,
command.getDocId()+priv.doctree_suffix,
option,
function (response) {
rev_path = "."+priv.getWinnerRevisionFromDocumentTree(
response)[0].rev;
f.getDocument(command.getDocId()+
rev_path+"/"+command.getAttachmentId());
},
function (err) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Document tree not found, please check document ID",
"reason": "Incorrect document ID"
});
return;
}
);
} else {
f.getDocument(command.getDocId()+"."+
prev_rev+"/"+command.getAttachmentId());
}
} else {
f.getDocumentTree();
}
};
/**
* Remove document or attachment.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
var f = {}, del_rev, option, i,
revision_found = false, revision_count = 0, correct_revision;
option = command.cloneOption();
if (option["max_retry"] === 0) {
option["max_retry"] = 3;
}
del_rev = command.getDoc()._rev;
f.removeDocument = function (docid) {
if (command.getOption("keep_revision_history") !== true) {
that.addJob(
"remove",
priv.substorage,
docid,
option,
function (response) {
if ( command.getAttachmentId() === undefined ) {
priv.update_doctree_on_remove = true;
} else {
priv.update_doctree_on_remove = false;
}
that.post(command);
},
function (err) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "File not found",
"reason": "Document was not found"
});
return;
}
);
} else {
// keep history = update document tree only
if (command.getAttachmentId() === undefined ) {
priv.update_doctree_on_remove = true;
} else {
priv.update_doctree_on_remove = false;
}
that.post(command);
}
};
if (typeof del_rev === "string") {
if (!priv.checkRevisionFormat(del_rev)) {
that.error({
"status": 31,
"statusText": "Wrong Revision Format",
"error": "wrong_revision_format",
"message": "The document previous revision does not match "+
"[0-9]+-[0-9a-zA-Z]+",
"reason": "Previous revision is wrong"
});
return;
}
}
// get doctree
that.addJob(
"get",
priv.substorage,
command.getDocId()+priv.doctree_suffix,
option,
function (response) {
response._conflicts = priv.getLeavesFromDocumentTree(response);
// really necessary...?
if (del_rev === undefined) {
// single leaf = can be deleted
if (response._conflicts.length === 1) {
f.removeDocument(command.getDocId()+"."+
response._conflicts[0]);
delete response._conflicts;
} else {
// multiple leaves = only if deleting attachment,
// because unique document.revision/attachment
if (typeof command.getAttachmentId() === "string"){
for (i = 0; i < response._conflicts.length; i += 1){
del_rev = response._conflicts[i];
that.addJob(
"get",
priv.substorage,
command.getDocId()+"."+response._conflicts[i],
option,
function (nested_response) {
if (typeof nested_response._attachments === "object") {
if (nested_response._attachments[command.getAttachmentId()] !== undefined){
revision_found = true;
correct_revision = del_rev;
}
}
if ( revision_count === response._conflicts.length-1 &&
revision_found !== true ){
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Attachment not found, please check attachment ID",
"reason": "Incorrect Attachment ID"
});
return;
}
if (revision_found === true ){
priv.missing_revision = correct_revision;
delete response._conflicts;
f.removeDocument(command.getDocId()+"."+
correct_revision+"/"+command.getAttachmentId());
}
revision_count += 1;
},
function (err) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Attachment not found, please check document ID",
"reason": "Incorrect document ID"
});
}
);
}
} else {
// conflict
// return conflict message here, so user can pick a document version
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Cannot delete a document without revision when multiple versions exist"
});
return;
}
}
} else {
// revision provided
if (typeof command.getAttachmentId() === "string"){
f.removeDocument(command.getDocId()+"."+del_rev+"/"+
command.getAttachmentId());
} else {
if (priv.isRevisionALeaf(del_rev, response._conflicts)){
f.removeDocument(command.getDocId()+"."+del_rev);
} else {
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Document update conflict.",
"reason": "Trying to remove an outdated revision"
});
return;
}
}
}
},
function (err) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Document tree not found, please check document ID",
"reason": "Incorrect document ID"
});
return;
}
);
};
return that;
});
......@@ -1228,11 +1228,11 @@ test ("Get", function(){
// adding two documents
o.doctree = {"children":[{
"rev": "1-rev1", "status": "available", "children": []
},{
"rev": "1-rev2", "status": "available", "children": [{
"rev": "1-rev1", "status": "available", "children": [{
"rev": "2-rev3", "status": "available", "children": []
}]
},{
"rev": "1-rev2", "status": "available", "children": []
}]};
o.doc_myget2 = {"_id": "get1", "title": "myGet2"};
o.doc_myget3 = {"_id": "get1", "title": "myGet3"};
......@@ -1260,16 +1260,8 @@ test ("Get", function(){
o.tick(o);
delete o.doc_myget2["_rev"];
// adding an attachment
o.attmt_myget2 = {
"get2": {
"length": 3,
"digest": "md5-dontcare"
}
};
o.doctree["children"][1]["attachment"] = o.attmt_myget2;
localstorage.setItem(o.localpath+"/get1.1-rev2", o.doc_myget2);
localstorage.setItem(o.localpath+"/get1.1-rev2/get2", "abc");
// localstorage.setItem(o.localpath+"/get1.1-rev2/get2", "abc");
localstorage.setItem(o.localpath+"/get1.2-rev3/get2", "abc");
// get attachment winner
o.spy(o, "value", "abc", "Get attachment (winner)");
......@@ -1283,13 +1275,24 @@ test ("Get", function(){
// get attachment specific rev
o.spy(o, "value", "abc", "Get attachment (specific revision)");
o.jio.get("get1/get2", {"rev": "1-rev2"}, o.f);
o.jio.get("get1/get2", {"rev": "2-rev3"}, o.f);
o.tick(o);
// get document with attachment (specific revision)
o.attmt_myget2["get2"]["revpos"] = 1;
// adding an attachment
o.attmt_myget2 = {
"get2": {
"length": 3,
"digest": "md5-dontcare"
},
"revpos":1,
};
o.doc_myget2["_rev"] = "1-rev2";
o.doc_myget2["_attachments"] = o.attmt_myget2;
o.doctree["children"][1]["attachment"] = o.attmt_myget2;
localstorage.setItem(o.localpath+"/get1.1-rev2", o.doc_myget2);
// get document with attachment (specific revision)
o.spy(o, "value", o.doc_myget2,
"Get document attachment (specific revision)");
o.jio.get("get1", {"rev": "1-rev2"}, o.f);
......@@ -1300,6 +1303,10 @@ test ("Get", function(){
// get document with attachment (winner)
o.doc_myget3["_rev"] = "2-rev3";
o.doc_myget3["_attachments"] = o.attmt_myget2;
o.doctree["children"][1]["attachment"] = o.attmt_myget3;
localstorage.setItem(o.localpath+"/get1.2-rev3", o.doc_myget3);
o.spy(o, "value", o.doc_myget3, "Get document attachment (winner)");
o.jio.get("get1", o.f);
o.tick(o);
......@@ -1311,6 +1318,250 @@ test ("Get", function(){
});
test ("Remove", function(){
var o = generateTools(this);
o.jio = JIO.newJio({
"type": "revision",
"secondstorage": {
"type": "local",
"username": "urevrem",
"applicationname": "arevrem"
}
});
o.localpath = "jio/localstorage/urevrem/arevrem";
// adding two documents
o.doc_myremove1 = {"_id": "remove1", "title": "myRemove1"};
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2"};
// revs_info 1-rev1 document version
o.revs_info = [];
o.very_old_rev = "1-"+hex_sha256(JSON.stringify(o.doc_myremove1)+JSON.stringify(o.revs_info));
localstorage.setItem(o.localpath+"/remove1."+o.very_old_rev, o.doc_myremove1);
localstorage.setItem(o.localpath+"/remove1.1-rev2", o.doc_myremove1);
// add attachment
o.attmt_myremove1 = {
"remove2": {
"length": 3,
"digest": "md5-dontcare"
},
"revpos":1
};
o.doc_myremove1 = {"_id": "remove1", "title": "myRemove1",
"_rev":o.very_old_rev, "_attachments":o.attmt_myremove1};
o.revs_info = [{"rev": o.very_old_rev, "status": "available"}];
o.old_rev = "2-"+hex_sha256(JSON.stringify(o.doc_myremove1)+JSON.stringify(o.revs_info));
localstorage.setItem(o.localpath+"/remove1."+o.old_rev, o.doc_myremove1);
localstorage.setItem(o.localpath+"/remove1."+o.old_rev+"/remove2", "xyz");
o.doctree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": []
}]
},{
"rev": "1-rev2", "status": "available", "children": []
}]};
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree);
// 1. remove non existing attachment with revision
o.spy(o, "status", 404, "Remove non existing attachment (with revision)");
o.jio.remove({"_id":"remove1.1-rev2/remove0","_rev":o.old_rev}, o.f);
o.tick(o);
o.revs_info = [
{"rev": o.old_rev, "status": "available"},
{"rev": o.very_old_rev, "status": "available"}
];
// xxx on remove, doc only includes the parameters passed in the remove callback
// to generate correct hash, need to modify here
o.doc_myremove1 = {"_id":"remove1/remove2","_rev":o.old_rev};
o.rev = "3-"+hex_sha256(JSON.stringify(o.doc_myremove1)+JSON.stringify(o.revs_info));
o.doctree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": [{
"rev": o.rev, "status": "available", "children":[]
}]
}]
},{
"rev": "1-rev2", "status": "available", "children": []
}]};
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree);
// 2. remove existing attachment with revision
o.spy (o, "value", {"ok": true, "id": "remove1", "rev": o.rev},
"Remove attachment (with revision)");
o.jio.remove({"_id":"remove1/remove2","_rev":o.old_rev}, o.f);
o.tick(o);
// add another attachment
o.attmt_myremove2 = {
"remove3": {
"length": 3,
"digest": "md5-hello123"
},
"revpos":1
};
o.doc_myremove2 = {"_id": "remove1", "title": "myRemove2",
"_rev":"1-rev2", "_attachments":o.attmt_myremove2};
o.second_old_rev = "2-"+hex_sha256(JSON.stringify(o.doc_myremove1)+JSON.stringify(o.revs_info));
o.revs_info = [
{"rev":o.second_old_rev, "status":"available"},
{"rev": "1-rev2", "status": "available"}
];
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev, o.doc_myremove2);
localstorage.setItem(o.localpath+"/remove1."+o.second_old_rev+"/remove3", "stu");
o.doctree = {"children":[{
"rev": o.very_old_rev, "status": "available", "children": [{
"rev": o.old_rev, "status": "available", "children": [{
"rev": o.rev, "status": "available", "children":[]
}]
}]
},{
"rev": "1-rev2", "status": "available", "children": [{
"rev": o.second_old_rev, "status": "available", "children":[]
}]
}]};
localstorage.setItem(o.localpath+"/remove1.revision_tree.json", o.doctree);
// 3. remove non existing attachment without revision
o.spy (o,"status", 404, "Remove non existing attachment (without revision)")
o.jio.remove({"_id":"remove1/remove0"}, o.f);
o.tick(o);
o.revs_info = [
{"rev": o.second_old_rev, "status": "available"},
{"rev": "1-rev2", "status": "available"}
];
o.doc_myremove3 = {"_id":"remove1/remove3","_rev":o.second_old_rev};
o.second_rev = "3-"+hex_sha256(JSON.stringify(o.doc_myremove3)+JSON.stringify(o.revs_info));
// 4. remove existing attachment without revision
o.spy (o, "value", {"ok": true, "id": "remove1", "rev": o.second_rev},
"Remove attachment (without revision)");
o.jio.remove({"_id":"remove1/remove3"}, o.f);
o.tick(o);
// 5. remove wrong revision
o.spy (o,"status", 409, "Removing wrong revision (not latest)");
o.jio.remove({"_id":"remove1","_rev":o.second_old_rev}, o.f);
o.tick(o);
o.revs_info = [
{"rev": o.second_rev, "status":"available"},
{"rev": o.second_old_rev, "status": "available"},
{"rev": "1-rev2", "status": "available"}
];
o.doc_myremove4 = {"_id":"remove1","_rev":o.second_rev};
o.second_new_rev = "4-"+hex_sha256(JSON.stringify(o.doc_myremove4)+JSON.stringify(o.revs_info));
// 6. remove revision
o.spy (o, "value", {"ok": true, "id": "remove1", "rev": o.second_new_rev},
"Remove attachment (with revision)");
o.jio.remove({"_id":"remove1", "_rev":o.second_rev}, o.f);
o.tick(o);
// 7. remove document without revision
o.spy (o,"status", 409, "Removing document without revision and multiple existing versions");
o.jio.remove({"_id":"remove1"}, o.f);
o.tick(o);
o.jio.stop();
});
module ( "Test Revisionstorage + Localstorage" );
test ("sample", function(){
var o = generateTools(this);
o.jio = JIO.newJio({
"type": "revision",
"secondstorage": {
"type": "local",
"username": "usam1",
"applicationname": "asam1"
}
});
o.localpath = "jio/localstorage/usam1/asam1";
// 1. put non empty document A-1
o.doc = {"_id": "sample1", "title": "mySample1"};
o.revs_info = [];
o.rev = "1-"+hex_sha256(JSON.stringify(o.doc)+JSON.stringify(o.revs_info));
o.spy (o, "value", {"ok": true, "id": "sample1", "rev": o.rev}, "Put non empty document A-1");
o.jio.put(o.doc, o.f);
o.tick(o);
// 2. put non empty document A-2
o.doc_b = {"_id": "sample1", "title": "mySample2"};
o.revs_info_b = [];
o.rev_b = "1-"+hex_sha256(JSON.stringify(o.doc)+JSON.stringify(o.revs_info));
o.spy (o,"status", 409, "409 Try to put non empty document A-2");
o.jio.put(o.doc, o.f);
o.tick(o);
// 3. FAKE IT
o.doc_f = {"_id": "sample1", "title": "mySample2"};
o.revs_info_f = [];
o.rev_f = "1-"+hex_sha256(JSON.stringify(o.doc_f)+JSON.stringify(o.revs_info_f));
o.doc_f2 = {"_id": "sample1."+o.rev_f, "title": "mySample2"};
localstorage.setItem(o.localpath+"/sample1."+o.rev_f, o.doc_f2);
o.doctree = {"children":[
{ "rev": o.rev, "status": "available", "children": []},
{ "rev": o.rev_f, "status": "available", "children": []}
]};
localstorage.setItem(o.localpath+"/sample1.revision_tree.json", o.doctree);
// 3. GET first version
o.spy(o, "value", {"_id": "sample1", "title": "mySample1", "_rev": o.rev},
"Get first document (using revison)");
o.jio.get("sample1", {"rev": o.rev}, o.f);
o.tick(o);
// 4. MODFIY first version
o.doc_2 = {"_id": "sample1", "_rev": o.rev, "title": "mySample1_modified"};
o.revs_info_2 = [{"rev": o.rev, "status": "available"}];
o.rev_2 = "2-"+hex_sha256(JSON.stringify(o.doc_2)+JSON.stringify(o.revs_info_2));
o.spy (o, "value", {"id":"sample1", "ok":true, "rev": o.rev_2}, "Modify first document");
o.jio.put(o.doc_2, o.f);
o.tick(o);
// 5. GET second version
o.spy(o, "value", {"_id": "sample1", "title": "mySample2", "_rev": o.rev_f},
"Get second document (using revison)");
o.jio.get("sample1", {"rev": o.rev_f}, o.f);
o.tick(o);
// 6. MODFIY second version
o.doc_f2 = {"_id": "sample1", "_rev": o.rev_f, "title": "mySample2_modified"};
o.revs_info_f2 = [{"rev": o.rev_f, "status": "available"}];
o.rev_f2 = "2-"+hex_sha256(JSON.stringify(o.doc_f2)+JSON.stringify(o.revs_info_f2));
o.spy (o, "value", {"id":"sample1", "ok":true, "rev": o.rev_f2}, "Modify second document");
o.jio.put(o.doc_f2, o.f);
o.tick(o);
// 7. GET document with conflict!
o.spy(o, "value", {"_id": "sample1", "title": "mySample2", "_rev": o.rev_f},
"Get second document (using revison)");
o.jio.get("sample1", o.f);
o.tick(o);
o.jio.stop();
});
/*
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