Commit 8e35d3a6 authored by Tristan Cavelier's avatar Tristan Cavelier

JIO Basic Storage revision managing removed! Improving modularity!

parent 3fd058c3
...@@ -7,381 +7,6 @@ ...@@ -7,381 +7,6 @@
* - CryptedStorage ('crypted') * - CryptedStorage ('crypted')
* - ConflictManagerStorage ('conflictmanager') * - ConflictManagerStorage ('conflictmanager')
* *
* @module cross-storage methods
*/
var utilities = {
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
hashCode : function (string) {
return hex_sha256(string);
},
/**
* Generates the next revision of [previous_revision]. [string] helps us
* to generate a hash code.
* @methode generateNextRev
* @param {string} previous_revision The previous revision
* @param {string} string String to help generate hash code
* @return {array} 0:The next revision number and 1:the hash code
*/
generateNextRevision : function (previous_revision, string) {
return [parseInt(previous_revision.split('-')[0],10)+1,
utilities.hashCode(previous_revision + string)];
},
/**
* Replace substrings to others substring following a [list_of_replacement].
* It will be executed recusively to replace substrings which are not
* replaced substrings.
* It starts from the last element of the list of replacement.
* @method replaceSubString
* @param {string} string The string to replace
* @param {array} list_of_replacement A list containing arrays with 2
* values:
* - {string} The substring to replace
* - {string} The new substring
* ex: [['b', 'abc'], ['abc', 'cba']]
* @return {string} The new string
*/
replaceSubString : function (string, list_of_replacement) {
var i, split_string = string.split(list_of_replacement[0][0]);
if (list_of_replacement[1]) {
for (i = 0; i < split_string.length; i += 1) {
split_string[i] = utilities.replaceSubString (
split_string[i],
list_of_replacement.slice(1)
);
}
}
return split_string.join(list_of_replacement[0][1]);
},
/**
* It secures the [string] replacing all '%' by '%%' and '/' by '%2F'.
* @method secureString
* @param {string} string The string to secure
* @return {string} The secured string
*/
secureString : function (string) {
return utilities.replaceSubString (string, [['/','%2F'],['%','%%']]);
},
/**
* It replaces all '%2F' by '/' and '%%' by '%'.
* @method unsecureString
* @param {string} string The string to convert
* @return {string} The converted string
*/
unsecureString : function (string) {
return utilities.replaceSubString (string, [['%%','%'],['%2F','/']]);
},
// ============================ CREATE/UPDATE DOCUMENT =====================
/**
* @method createDocument - Creates a new document
* @info - docid POST = "" for POST, PUT = string
* @param {docid} string - id for the new document
* @stored - 'jio/local/USR/APP/FILE_NAME'
* @returns {doc} object - document object
*/
createDocument : function (docId) {
var now = Date.now(),
doc = {},
hash = utilities.hashCode('' + doc + ' ' + now + '');
doc._id = docId;
doc._rev = '1-'+hash;
doc._revisions = {
start: 1,
ids: [hash]
};
doc._revs_info = [{
rev: '1-'+hash,
status: 'available'
}];
return doc;
},
/**
* @method updateDocument - updates a document
* @info - called from PUT or PUTATTACHMENT
* @info - deletes old document (purge & replace)
* @param {docid} string - id for the new document
* @param {docpath} string - the path where to store the document
* @param {previousRevision} string - the previous revision
* @param {attachmentId} - string - in case attachments are handled
* @returns {doc} object - new document
*/
updateDocument : function (doc, docPath, previousRevision, attachmentId) {
var now = Date.now(),
rev = utilities.generateNextRevision(previousRevision, ''+
doc+' '+now+'');
// in case the update is made because of an attachment
if (attachmentId !== undefined) {
// create _attachments
if (doc._attachments === undefined){
doc._attachments = {};
}
// create _attachments object for this attachment
if (doc._attachments[attachmentId] === undefined){
doc._attachments[attachmentId] = {};
}
// set revpos
doc._attachments[attachmentId].revpos =
parseInt(doc._rev.split('-')[0],10);
}
// update document
doc._rev = rev.join('-');
doc._revisions.ids.unshift(rev[1]);
doc._revisions.start = rev[0];
doc._revs_info[0].status = 'deleted';
doc._revs_info.unshift({
"rev": rev.join('-'),
"status": "available"
});
return doc;
},
/**
* @method updateDocumentTree- update a document tree
* @param {docTreeNode} object - document tree
* @param {old_rev} string - revision of the tree node to set to "branch"
* @param {new_rev } string - revison of the tree node to add as leaf
* @param {revs_info} object- history of new_rev to merge with remote tree
*/
updateDocumentTree : function (docTreeNode, old_rev, new_rev,
revs_info, deletedLeaf ) {
if (typeof revs_info === "object") {
// a new document version is being stored from another storage
utilities.mergeRemoteTree(docTreeNode, docTreeNode, old_rev, new_rev,
revs_info, [], false, deletedLeaf);
} else {
// update an existing version of document = add a node to the tree
utilities.setTreeNode(docTreeNode, old_rev, new_rev, 'available');
}
return docTreeNode;
},
// ==================== SET/MERGE/CHECK TREE NODES ==================
/**
* @method setTreeNode - adds a new tree node/changes leaf to branch
* @param {docTreeNode} object - document tree
* @param {old_rev} string - revision of the tree node to set to "branch"
* @param {new_rev } string - revison of the tree node to add as leaf
* @param {new_status}string- status the new node should have
* @info - status is necessary, because we may also
* add deleted nodes to the tree from a
* remote storage
*/
setTreeNode : function (docTreeNode, old_rev, new_rev, new_status){
var kids = docTreeNode['kids'],
rev = docTreeNode['rev'],
numberOfKids,
i,
key;
for(key in docTreeNode){
if (key === "rev"){
// grow the tree
if (old_rev === rev && new_rev !== rev) {
docTreeNode.type = 'branch';
docTreeNode.status = 'deleted';
docTreeNode.kids.push({
type:'leaf',
status:new_status,
rev:new_rev,
kids:[]
});
} else {
// traverse until correct node is found!
if ( utilities.isObjectEmpty( kids ) === false ) {
numberOfKids = utilities.isObjectSize(kids);
for ( i = 0; i < numberOfKids; i+=1 ){
utilities.setTreeNode(kids[i], old_rev, new_rev, new_status);
}
}
}
}
}
return docTreeNode;
},
/**
* Merges document object trees
* @method mergeDocumentTree
* @param {object} document_tree_list The document tree array
*/
mergeDocumentTree: function (document_tree_list) {
var arrayConcat, each, synchronizeFrom, tree_list;
// arrayConcat([1,2], [3,4]) = [1,2,3,4]
arrayConcat = function () {
var i,newlist=[];
for (j=0; j<arguments.length; ++j) {
for (i=0; i<arguments[j].length; ++i) {
newlist.push(arguments[j][i]);
}
}
return newlist;
};
// "each" executes "fun" on each values of "array"
// if return false, then stop browsing
each = function (array, fun, start) {
var i;
for (i = start || 0; i < array.length; i += 1) {
if (fun(array[i], i) === false) {
return false;
}
}
return true;
};
// merges all trees
synchronize = function (tree_list) {
var new_children;
new_children = [];
each(tree_list, function (tree, tree_index) {
var res;
if (new_children.length === 0) {
new_children.push(tree);
return;
}
res = each(new_children, function (child, child_index) {
if (tree.rev === child.rev) {
new_children[child_index].children = synchronize(
arrayConcat(
tree.children,
child.children
)
)
return false;
}
});
if (res === true) {
new_children.push(tree);
}
});
return new_children;
};
tree_list = [];
each(document_tree_list, function (tree) {
tree_list = arrayConcat(tree_list, tree.children);
});
return {"children":synchronize(tree_list)};
},
/**
* 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 {string} The winner revision
*/
getWinnerRevisionFromDocumentTree: function (document_tree) {
var i, result, search;
result = {"deep":-1,"revision":''};
// search method fills "result" with the winner revision
search = function (document_tree, deep) {
var i;
if (document_tree.children.length === 0) {
// This node is a leaf
if (result.deep < deep) {
// The leaf is deeper than result
result = {"deep":deep,"revision":document_tree.rev};
}
return;
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i], deep+1);
}
};
search(document_tree, 0);
return result.rev;
},
/**
* Gets an array of leaves revisions from document tree
* @method getLeavesFromDocumentTree
* @param {object} document_tree The document tree
* @return {array} The array of leaves revisions
*/
getLeavesFromDocumentTree : function (document_tree) {
var i, result, search;
result = [];
// search method fills [result] with the winner revision
search = function (document_tree) {
var i;
if (document_tree.children.length === 0) {
// This node is a leaf
result.push(document_tree.rev);
return;
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i]);
}
};
search(document_tree);
return result;
},
/**
* @method isDeadLeaf - Check if revision is branch or status deleted
* @param {node} string - revision
* @param {tree} object - active leaves (versions of a document)
* @returns - true/false
*/
isDeadLeaf : function (prev_rev, docTreeNode ){
var type = docTreeNode['type'],
status = docTreeNode['status'],
kids = docTreeNode['kids'],
rev = docTreeNode['rev'],
result = false,
numberOfKids,
i,
key;
for ( key in docTreeNode ){
if ( key === "rev" ){
// if prev_rev is found, check if deleted or branch
if ( prev_rev === rev &&
( type === 'branch' || status === 'deleted' ) ){
result = true;
}
if ( utilities.isObjectEmpty( kids ) === false ){
numberOfKids = utilities.isObjectSize( kids );
for ( i = 0; i < numberOfKids; i+=1 ){
// recurse
if ( utilities.isDeadLeaf( prev_rev, kids[i] ) === true ){
result = true;
}
}
}
return result;
}
}
}
};
/*
* @module JIOStorages * @module JIOStorages
*/ */
(function(LocalOrCookieStorage, $, Base64, sjcl, hex_sha256, jIO) { (function(LocalOrCookieStorage, $, Base64, sjcl, hex_sha256, jIO) {
...@@ -26,7 +26,7 @@ var newLocalStorage = function (spec, my) { ...@@ -26,7 +26,7 @@ var newLocalStorage = function (spec, my) {
storage_user_array_name, storage_user_array_name,
storage_file_array_name; storage_file_array_name;
// attributes // attributes
priv.username = spec.username || ''; priv.username = spec.username || '';
priv.applicationname = spec.applicationname || 'untitled'; priv.applicationname = spec.applicationname || 'untitled';
......
var _allDocsCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return '_allDocs';
};
that.executeOn = function(storage) {
storage._allDocs (that);
};
that.canBeRestored = function() {
return false;
};
that.validateState = function () {
return true;
};
return that;
};
var _getCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return '_get';
};
that.validateState = function() {
if (!that.getDocId()) {
that.error({
status:20,statusText:'Document Id Required',
error:'document_id_required',
message:'No document id.',reason:'no document id'
});
return false;
}
return true;
};
that.executeOn = function(storage) {
storage._get (that);
};
that.canBeRestored = function() {
return false;
};
return that;
};
var _postCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
// Methods //
that.getLabel = function() {
return '_post';
};
that.executeOn = function(storage) {
storage._post (that);
};
return that;
};
var _putAttachmentCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return '_putAttachment';
};
that.executeOn = function (storage) {
storage._putAttachment (that);
};
that.validateState = function () {
if (typeof that.getContent() !== 'string') {
that.error({
status:22,statusText:'Content Required',
error:'content_required',
message:'No data to put.',reason:'no data to put'
});
return false;
}
return true;
};
return that;
};
var _putCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
var priv = {};
// Methods //
that.getLabel = function() {
return '_put';
};
/**
* Validates the storage handler.
* @param {object} handler The storage handler
*/
that.validate = function () {
return that.validateState();
};
that.executeOn = function(storage) {
storage._put (that);
};
return that;
};
var _removeCommand = function(spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function() {
return '_remove';
};
that.executeOn = function(storage) {
storage._remove (that);
};
return that;
};
...@@ -11,12 +11,6 @@ var command = function(spec, my) { ...@@ -11,12 +11,6 @@ var command = function(spec, my) {
'remove':removeCommand, 'remove':removeCommand,
'allDocs':allDocsCommand, 'allDocs':allDocsCommand,
'putAttachment':putAttachmentCommand 'putAttachment':putAttachmentCommand
'_post':_postCommand,
'_put':_putCommand,
'_get':_getCommand,
'_remove':_removeCommand,
'_allDocs':_allDocsCommand,
'_putAttachment':_putAttachmentCommand
}; };
// creates the good command thanks to his label // creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) { if (spec.label && priv.commandlist[spec.label]) {
......
...@@ -10,14 +10,6 @@ var putCommand = function(spec, my) { ...@@ -10,14 +10,6 @@ var putCommand = function(spec, my) {
return 'put'; return 'put';
}; };
/**
* Validates the storage handler.
* @param {object} handler The storage handler
*/
that.validate = function () {
return that.validateState();
};
that.executeOn = function(storage) { that.executeOn = function(storage) {
storage.put (that); storage.put (that);
}; };
......
...@@ -14,214 +14,6 @@ var storage = function(spec, my) { ...@@ -14,214 +14,6 @@ var storage = function(spec, my) {
} }
}); });
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
that.generateUuid = function () {
var S4 = function () {
var i, string = Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0'+string;
}
return string;
};
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
};
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
that.hashCode = function (string) {
return hex_sha256(string);
};
/**
* Returns an array version of a revision string
* @method revisionToArray
* @param {string} revision The revision string
* @return {array} Array containing a revision number and a hash
*/
that.revisionToArray = function (revision) {
if (typeof revision === "string") {
return [parseInt(revision.split('-')[0],10),
revision.split('-')[1]]
}
return revision;
};
/**
* Generates the next revision of [previous_revision]. [string] helps us
* to generate a hash code.
* @methode generateNextRev
* @param {string} previous_revision The previous revision
* @param {string} string String to help generate hash code
* @return {array} 0:The next revision number and 1:the hash code
*/
that.generateNextRevision = function (previous_revision, string) {
if (typeof previous_revision === "number") {
return [previous_revision + 1, that.hashCode(string)];
}
previous_revision = that.revisionToArray(previous_revision);
return [previous_revision[0]+1, that.hashCode(string)];
};
/**
* Checks a revision format
* @method checkRevisionFormat
* @param {string} revision The revision string
* @return {boolean} True if ok, else false
*/
that.checkRevisionFormat = function (revision) {
return /^[0-9]+-[0-9a-zA-Z]+$/.test(revision);
};
/**
* Creates the error object for all errors
* @method createErrorObject
* @param {number} error_code The error code
* @param {string} error_name The error name
* @param {string} message The error message
* @param {object} error_object The error object (optional)
* @return {object} Error object
*/
that.createErrorObject = function (error_code, error_name,
message, error_object) {
error_object = error_object || {};
error_okject["status"] = error_code || 0;
error_object["statusText"] = error_name;
error_object["error"] = error_name.toLowerCase().split(' ').join('_');
error_object["message"] = error_object["error"] = message;
return error_object;
};
/**
* Creates an empty document tree
* @method createDocumentTree
* @param {array} children An array of children (optional)
* @return {object} The new document tree
*/
that.createDocumentTree = function(children) {
return {"children":children || []};
};
/**
* Creates a new document tree node
* @method createDocumentTreeNode
* @param {string} revision The node revision
* @param {string} status The node status
* @param {array} children An array of children (optional)
* @return {object} The new document tree node
*/
that.createDocumentTreeNode = function(revision,status,children) {
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 {string} The winner revision
*/
that.getWinnerRevisionFromDocumentTree = function (document_tree) {
var i, result, search;
result = {"deep":-1,"revision":''};
// search method fills "result" with the winner revision
search = function (document_tree, deep) {
var i;
if (document_tree.children.length === 0) {
// This node is a leaf
if (result.deep < deep) {
// The leaf is deeper than result
result = {"deep":deep,"revision":document_tree.rev};
}
return;
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i], deep+1);
}
};
search(document_tree, 0);
return result.rev;
};
/**
* Gets an array of leaves revisions from document tree
* @method getLeavesFromDocumentTree
* @param {object} document_tree The document tree
* @return {array} The array of leaves revisions
*/
that.getLeavesFromDocumentTree = function (document_tree) {
var i, result, search;
result = [];
// search method fills [result] with the winner revision
search = function (document_tree) {
var i;
if (document_tree.children.length === 0) {
// This node is a leaf
result.push(document_tree.rev);
return;
}
// This node has children
for (i = 0; i < document_tree.children.length; i += 1) {
// searching deeper to find the deeper leaf
search(document_tree.children[i]);
}
};
search(document_tree);
return result;
};
that.createDocument = function (doc, id, prev_rev) {
var hash, rev;
if (typeof prev_rev === "undefined") {
hash = that.hashCode(doc);
doc._rev = "1-"+hash;
doc._id = id;
doc._revisions = {
"start": 1,
"ids": [hash]
};
doc._revs_info = [{
"rev": "1-"+hash,
"status": "available"
}];
return doc;
} else {
// xxx do not hash _key of doc!
prev_rev = that.revisionToArray(prev_rev);
rev = that.generateNextRevision(prev_rev,doc);
doc._rev = rev.join('-');
doc._id = id;
doc._revisions = {
"start": rev[0],
"ids": [rev[1],prev_rev[1]]
};
doc._revs_info = [{
"rev": rev.join('-'),
"status": "available"
},{
"rev": prev_rev.join('-'),
"status": "missing"
}];
return doc;
}
};
/** /**
* Execute the command on this storage. * Execute the command on this storage.
* @method execute * @method execute
...@@ -289,82 +81,7 @@ var storage = function(spec, my) { ...@@ -289,82 +81,7 @@ var storage = function(spec, my) {
return ''; return '';
}; };
that.post = function (command) { that.post = function () {
setTimeout(function () {
var f, options, document_tree, doc, prev_rev;
f = {};
options = command.cloneOption();
options["max_retry"] = options["max_retry"] || 3;
f.begin = function () {
prev_rev = command.getDocInfo("_rev");
if (typeof prev_rev === "string" &&
!that.checkRevisionFormat(prev_rev)) {
// if the previous revision given is bad
that.error(that.createErrorObject(
400, "Bad Request", "Invalid rev format"
));
return;
}
doc = that.createDocument(
command.getDoc() || {},
command.getDocId() || that.generateUuid(),
prev_rev
);
// the previous revision is correct
prev_rev = that.revisionToArray(prev_rev);
f.getDocumentTree();
};
// check if the tree already exists
f.getDocumentTree = function () {
that.addJob(
'_get',
that.serialized(),
doc._id+'.tree.json',
options,
function (response) {
// if the tree exists
document_tree = response;
f.postDocument();
},function (error) {
if (error.status === 404) {
// if the tree does not exists yet
document_tree = that.createDocumentTree();
f.postDocument();
} else {
that.error(that.createErrorObject(
error.status, error.statusText,
"Unable to get the revision tree"
));
}
}
);
};
f.postDocument = function () {
that.addJob(
'_post',
that.serialized(),
doc._id+'.'+doc._rev,
options,
function (response) {
f.putDocumentTree()
},function (error) {
that.error(that.createErrorObject(
error.status, error.statusText, error
));
}
);
};
f.putDocumentTree = function () {
if (!that.addDocumentToDocumentTree(doc)) {
// conflict!
}
// xxx
};
f.begin();
});
};
that._post = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Post\" command is not implemented" 0,"Not Implemented Yet","\"Post\" command is not implemented"
...@@ -372,7 +89,7 @@ var storage = function(spec, my) { ...@@ -372,7 +89,7 @@ var storage = function(spec, my) {
}); });
}; };
that._put = function () { that.put = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Put\" command is not implemented" 0,"Not Implemented Yet","\"Put\" command is not implemented"
...@@ -380,7 +97,7 @@ var storage = function(spec, my) { ...@@ -380,7 +97,7 @@ var storage = function(spec, my) {
}); });
}; };
that._putAttachment = function () { that.putAttachment = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet", 0,"Not Implemented Yet",
...@@ -389,7 +106,7 @@ var storage = function(spec, my) { ...@@ -389,7 +106,7 @@ var storage = function(spec, my) {
}); });
}; };
that._get = function () { that.get = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Get\" command is not implemented" 0,"Not Implemented Yet","\"Get\" command is not implemented"
...@@ -397,7 +114,7 @@ var storage = function(spec, my) { ...@@ -397,7 +114,7 @@ var storage = function(spec, my) {
}); });
}; };
that._allDocs = function () { that.allDocs = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet", 0,"Not Implemented Yet",
...@@ -406,7 +123,7 @@ var storage = function(spec, my) { ...@@ -406,7 +123,7 @@ var storage = function(spec, my) {
}); });
}; };
that._remove = function () { that.remove = function () {
setTimeout(function () { setTimeout(function () {
that.error(that.createErrorObject( that.error(that.createErrorObject(
0,"Not Implemented Yet", 0,"Not Implemented Yet",
......
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