Commit eca94a2a authored by Sven Franck's avatar Sven Franck

POST and PUT working, created global storage utils

parent 31e65d17
......@@ -51,7 +51,7 @@
return fakeRevision;
};
that.post = function (command) {
setTimeout (function () {
......
......@@ -7,6 +7,487 @@
* - CryptedStorage ('crypted')
* - ConflictManagerStorage ('conflictmanager')
*
* @module cross-storage methods
*/
var utilities = {
/**
* @method isObjectEmpty - Check whether an object is empty
* @param {obj} object - object to test
* @returns {boolean} string- true/false
*/
isObjectEmpty : function(obj) {
var key;
if (obj.length && obj.length > 0){
return false;
}
if (obj.length && obj.length === 0){
return true;
}
for (key in obj) {
if ({}.hasOwnProperty.call(obj, key)){
return false;
}
}
return true;
},
/**
* @method isObjectSize - Check number of elements in object
* @param {obj} object - object to test
* @returns {size} integer - size
*/
isObjectSize : function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)){
size++;
}
}
return size;
},
/**
* @method isInObject - Check if revision is on tree
* @param {needle} string - revision
* @param {haystack} array - active leaves (versions of a document)
* @returns {boolean} string- true/false
*/
isInObject : function (needle, haystack){
var length = haystack.length;
for(var i = 0; i < length; i++) {
if(haystack[i] === needle){
return true;
}
}
return false;
},
/**
* @method isUUID - Check if docid is UUID
* @param {needle} string - docId
* @returns {boolean} string- true/false
*/
isUUID : function (documentId){
var reg = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( documentId );
return reg;
},
/**
* @method throwError - Creates the error object for all errors
* @param {code} string - the error code.
* @param {reason} string - the error reason
* @returns {e} object - error object
*/
throwError : function ( code, reason ) {
var statusText, error, message, errorObject;
switch( code ){
case 409:
statusText = 'Conflict';
error = 'conflict';
message = 'Document update conflict.';
break;
case 403:
statusText = 'Forbidden';
error = 'forbidden';
message = 'Forbidden';
break;
case 404:
statusText = 'Not found';
error = 'not found';
message = 'Document not found.';
break;
}
// create object
errorObject = ({
status:code,
statusText:statusText,
error:error,
message:message,
reason:reason
});
return errorObject;
},
/**
* 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
* @param {docpath} string - the path where to store the document
* @stored - 'jio/local/USR/APP/FILE_NAME'
* @returns {doc} object - document object
*/
createDocument : function (docId, docPath){
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
* @returns {doc} object - new document
*/
updateDocument : function (doc, docPath, previousRevision){
var now = Date.now(),
rev = utilities.generateNextRevision(previousRevision, ''+
doc+' '+now+'');
// 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;
},
// ==================== CREATE/UPDATE DOCUMENT TREE ==================
/**
* @method createDocumentTree- Creates a new document.tree
* @param {doc } object - the document object
* @info: - the tree will include
* @key {type} string - "branch" or "leaf"
* @key {status} string - "available" or "deleted"
* @key {rev} string - revision string of this node
* @key {spawns} object - child branches/leaves
* @stored - 'jio/local/USR/APP/FILE_NAME/tree_revision'
* @info - one active leaf is needed to keep tree alive
* @info - deleted versions/branches = "status deleted"
* @info - no active leaves will delete tree, too
*/
createDocumentTree : function (doc){
var tree = {
type:'leaf',
status:'available',
rev:doc._rev,
kids:[]
};
return tree;
},
/**
* @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 ){
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 );
} else {
// update an existing version of document = add a node to the tree
utilities.setTreeNode( docTreeNode, old_rev, new_rev );
}
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
*/
setTreeNode : function (docTreeNode, old_rev, new_rev){
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: 'available',
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);
}
}
}
}
}
return docTreeNode;
},
/**
* @method mergeRemoteTree - merge revs_info into current 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 - these revisions need to be checked and added
* @param {addNodes} object - array for nodes to add to the tree
* @info: - old_rev = null here and it's not needed
* because new_rev will be used as the revision
* for the new tree node (we are copy&pasting
* from another storage)
*/
mergeRemoteTree : function ( initialTree, docTreeNode, old_rev, new_rev,
newDocumentRevisions, addNodes, onTree ){
var sync_rev = newDocumentRevisions[0].rev,
current_tree_rev = docTreeNode['rev'],
kids = docTreeNode['kids'],
addNodesLen,
numberOfKids,
key,
i,
j;
for ( key in docTreeNode ){
if ( key === "rev" ){
// commeon ancestor? = does the revision on the current
// tree node match the currently checked remote tree
// revision
// match = common ancestor
if ( sync_rev === current_tree_rev ){
onTree = true;
// in order to loop we also add the revision of
// the common ancestor node to the array
// using push!
addNodes.unshift( current_tree_rev );
// get length, now that we need it
addNodesLen = utilities.isObjectSize( addNodes )-1;
// the addNodes array will now look like this
// [current_node, all_missing_nodes]
for ( j = 0; j < addNodesLen; j+=1 ){
utilities.setTreeNode( initialTree, addNodes[j],
addNodes[j+1]
);
}
// no match = continue down the tree
} else if ( utilities.isObjectEmpty( kids ) === false ){
numberOfKids = utilities.isObjectSize( kids );
for ( i = 0; i < numberOfKids; i+=1 ){
utilities.mergeRemoteTree( initialTree, kids[i], old_rev, new_rev,
newDocumentRevisions, addNodes, onTree );
}
// end of tree = start over checking the next remote revision
} else if ( onTree === false ){
// revision from _revs_info was not found in tree.
// add it to addNodes, remove it from newDocumentRevisions
// call mergeRemoteTree again with new modified arrays
// until a common ancestor is found
// remember to add this revision once an ancestor is found
// we use push here, because we later loop this from 0 to x
addNodes.unshift(sync_rev);
// pop it off the revs_info
newDocumentRevisions.shift();
// this should start over with the full document tree
// otherwise it will only continue on the current (last) node
utilities.mergeRemoteTree( initialTree, initialTree, old_rev, new_rev,
newDocumentRevisions, addNodes, onTree );
}
}
}
return docTreeNode;
},
/**
* @method getLeavesOnTree - finds all leaves on a tree
* @param {docTree} string - the tree for this document
* @returns {leaves} object - array with all leaves
* @info - find active (status = available ) leaves
*/
getLeavesOnTree : function ( docTreeNode ){
var revisions = [],
type = docTreeNode['type'],
status = docTreeNode['status'],
kids = docTreeNode['kids'],
rev = docTreeNode['rev'],
addLeaf, addLeaves, numberOfKids, i, key;
for ( key in docTreeNode ){
if ( key === "type" ){
// node is a leaf, then it will have no kids!
if ( type === 'leaf' && status !== 'deleted' ){
addLeaf = docTreeNode['rev'];
}
// node has kid(s), must be a branch
if ( utilities.isObjectEmpty( kids ) === false ){
numberOfKids = utilities.isObjectSize( kids );
for ( i = 0; i < numberOfKids; i+=1 ){
// recurse
addLeaves = utilities.getLeavesOnTree( kids[i] );
// single kid returns string 1-1234... = unshift
// multiple kids array [1-1234...,3-3412...] = concat
revisions = addLeaves === 'string' ?
revisions.unshift[ addLeaves ] :
revisions.concat( addLeaves );
}
}
}
}
// for recursiveness:
// no kids = passback string, multiple kids, pass back array
return ( addLeaf === undefined ? revisions : addLeaf );
},
/**
* @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 ( typeof kids === "object" &&
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
*/
(function(LocalOrCookieStorage, $, Base64, sjcl, hex_sha256, jIO) {
......@@ -22,80 +22,10 @@ var newLocalStorage = function ( spec, my ) {
}
};
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
priv.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
*/
priv.generateNextRev = function (previous_revision, string) {
return [parseInt(previous_revision.split('-')[0],10)+1,
priv.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
*/
priv.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] = priv.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
*/
priv.secureString = function (string) {
return priv.replaceSubString (string, [['/','%2F'],['%','%%']]);
};
/**
* It replaces all '%2F' by '/' and '%%' by '%'.
* @method unsecureString
* @param {string} string The string to convert
* @return {string} The converted string
*/
priv.unsecureString = function (string) {
return priv.replaceSubString (string, [['%%','%'],['%2F','/']]);
};
priv.username = spec.username || '';
priv.secured_username = priv.secureString(priv.username);
priv.secured_username = utilities.secureString(priv.username);
priv.applicationname = spec.applicationname || 'untitled';
priv.secured_applicationname = priv.secureString(priv.applicationname);
priv.secured_applicationname = utilities.secureString(priv.applicationname);
var storage_user_array_name = 'jio/local_user_array';
var storage_file_array_name = 'jio/local_file_name_array/' +
......@@ -234,177 +164,6 @@ var newLocalStorage = function ( spec, my ) {
}
};
/**
* @method throwError - Creates the error object for all errors
*
* @param {code} string - the error code.
* @param {reason} string - the error reason
*/
priv.throwError = function ( code, reason ) {
var statusText, error, message, e;
switch( code ){
case 409:
statusText = 'Conflict';
error = 'conflict';
message = 'Document update conflict.';
break;
case 403:
statusText = 'Forbidden';
error = 'forbidden';
message = 'Forbidden';
break;
case 404:
statusText = 'Not found';
error = 'not_found';
message = 'Document not found.';
break;
}
// create object
e = ({
status:code,
statusText:statusText,
error:error,
message:message,
reason:reason
});
return e;
};
/**
* @method createDocument - Creates a new document
*
* docid will be "" for POST and a string for PUT
*
* @param {docid} string - id for the new document
* @param {docpath} string - the path where to store the document
*
* @stored 'jio/local/USR/APP/FILE_NAME'
*/
priv.createDocument = function ( docId, docPath ) {
var now = Date.now(),
doc = {},
hash = priv.hashCode('' + doc + ' ' + now + ''),
docPathRev;
doc._id = docId;
doc._rev = '1-'+hash;
doc._revisions = {
start: 1,
ids: [hash]
};
doc._revs_info = [{
rev: '1-'+hash,
status: 'available'
}];
// allow to store multiple versions of a document by including rev
docPathRev = docPath + '/' + doc._rev;
// store
localstorage.setItem(docPathRev, doc);
return doc;
};
/**
* @method updateDocument - updates a document
*
* called from PUT or PUTATTACHMENT (or REMOVE?)
*
* @param {docid} string - id for the new document
* @param {docpath} string - the path where to store the document
* @param {prev_rev} string- the previous revision
*
*/
priv.updateDocument = function ( doc, docPath, prev_rev ) {
var now = Date.now(),
rev = priv.generateNextRev(prev_rev, ''+doc+' '+now+'');
// 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"
});
// store
localstorage.setItem(docPath, doc);
return doc;
};
/**
* @method createDocumentTree - Creates a new document.tree
*
* @param {docId} string - id for the new document
* @param {doc } object - the document object
* @param {docPath} string - the path where to store the document
*
* the tree will include
* @key {type} string - branch or leaf
* @key {status} string - available or deleted
* @key {rev} string - revision string of this node
* @key {spawns} object - child branches/leaves
*
* @stored 'jio/local/USR/APP/FILE_NAME/TREE_revision'.
*
* the tree is maintained as long as more than one leaf exists(!)
* a leaf set to status "deleted" implies a deleted document version
* When all leaves have been set to "deleted", the tree is also deleted.
*
*/
priv.createDocumentTree = function ( doc, docId, docPath ){
var tree = {
type:'leaf',
status:'available',
rev:doc._rev,
kids:{}
},
treePath = docPath+'/revision_tree';
// store
localstorage.setItem(treePath, tree);
};
/**
* @method updateDocumentTree - update a document tree
*
* @param {docId} string - id for the new document
* @param {doc } object - the document object
* @param {docPath} string - the path where to store the document
*
* a tree can be grown (update a document) or split (when creating
* a new version). Growing the tree means changing a leaf into
* a branch and creating a new leaf. This is done here.
*
*/
priv.updateDocumentTree = function ( ){
};
/**
* @method getLastTreeRevision - find a leaf
*
* @param {docTree} string - the tree for this document
*
* this method should get the last leaf on the tree.
* If there are multiple leaves that come into question
* we select same as COUCHDB, highest rev counter, than
* compare ASCII. We will return only a single document
*
*/
priv.getLastTreeRevision = function ( docTree ){
};
/**
* @method post
*
......@@ -420,39 +179,43 @@ var newLocalStorage = function ( spec, my ) {
setTimeout (function () {
var docId = command.getDocId(),
var doc,
docId = command.getDocId(),
docPath = 'jio/local/'+priv.secured_username+'/'+
priv.secured_applicationname+'/'+docId,
tree,
treePath = docPath+'/revision_tree',
docTree = localstorage.getItem(treePath),
doc = localstorage.getItem(docPath),
reg;
reg = utilities.isUUID(docId);
// no attachments allowed
if (command.getAttachmentId()) {
that.error( priv.throwError( 403,
that.error( utilities.throwError( 403,
'Attachment cannot be added with a POST request')
);
return;
}
// check for UUID
reg = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( docId );
if ( reg !== true ) {
// id was supplied, use PUT
that.error( priv.throwError( 403,
that.error( utilities.throwError( 403,
'ID cannot be supplied with a POST request. Please use PUT')
);
return;
} else {
// create and store new document
doc = priv.createDocument( docId, docPath );
// create new document
doc = utilities.createDocument( docId, docPath );
// store
localstorage.setItem(docPath + '/' + doc._rev, doc);
// create and store new document.tree
priv.createDocumentTree( doc, docId, docPath );
tree = utilities.createDocumentTree( doc );
// store
localstorage.setItem(treePath, tree);
// add user
if (!priv.doesUserExist (priv.secured_username)) {
......@@ -491,29 +254,16 @@ var newLocalStorage = function ( spec, my ) {
prev_rev = command.getDocInfo('_rev'),
docPath ='jio/local/'+priv.secured_username+'/'+
priv.secured_applicationname+'/'+docId,
docPathRev = docPath +'/'+prev_rev,
treePath = docPath+'/revision_tree',
treePath = docPath+'/revision_tree',tree,
docTree = localstorage.getItem(treePath),
doc,
reg;
doc, docPathRev, activeLeaves, reg = utilities.isUUID(docId), newDocTree;
// no tree = create document or error
if (!docTree) {
// check UUID
reg = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( docId );
// id/revision provided = update, revision must be incorrect
if ( prev_rev !== undefined && reg === false ){
that.error( priv.throwError( 409,
'Incorrect Revision or ID')
);
return;
}
// revision provided = update, wrong revision or missing id
if ( prev_rev !== undefined ){
that.error( priv.throwError( 404,
that.error( utilities.throwError( 404,
'Document not found, please check revision and/or ID')
);
return;
......@@ -521,7 +271,7 @@ var newLocalStorage = function ( spec, my ) {
// no revision and UUID = create, no id provided
if ( prev_rev === undefined && reg === true){
that.error( priv.throwError( 409,
that.error( utilities.throwError( 409,
'Missing Document ID and or Revision')
);
return;
......@@ -534,10 +284,16 @@ var newLocalStorage = function ( spec, my ) {
// be the case.
// create and store new document
doc = priv.createDocument( docId, docPath );
doc = utilities.createDocument( docId, docPath );
// store
localstorage.setItem(docPath + '/' + doc._rev, doc);
// create and store new document.tree
priv.createDocumentTree( doc, docId, docPath );
tree = utilities.createDocumentTree( doc );
// store
localstorage.setItem(treePath, tree);
// add user
if (!priv.doesUserExist (priv.secured_username)) {
......@@ -556,71 +312,114 @@ var newLocalStorage = function ( spec, my ) {
);
} else {
// we found a tree
/*
console.log( docTree );
console.log( prev_rev );
console.log( docId );
console.log( docPath );
console.log( docPathRev );
*/
doc = localstorage.getItem(docPathRev);
console.log( doc );
// check if rev_supplied is on the tree
// check if last node
// check if multiple leaves or one (do I need to???)
// if on tree and only leaf = expand
// if on tree and multiple leaves = ???
// if on tree, but not a leaf = error
// if not on tree, can be error or new version
// get revs_info from new document
// not available = error on PUT
// available = compare revs_info current and supplied
// start @root
// if nodes are the same and BRANCH, make sure they are branch and deleted if not the last nodes
// if nodes are not the same STOP
// add new kid to last branch
// continue with supplied revs_info to last version and add all the nodes on supplied revs_info
// this should "copy" the tree from supplied revs_info into the current document tree
// we have a tree, we know the last revision
//priv.getLastTreeRevision( docTree );
if (!doc) {
}
if (doc._rev !== prev_rev) {
that.error( priv.throwError( 409,
'Revision supplied is not the latest revision')
);
return;
}
// update ...?
priv.documentObjectUpdate(doc,command.cloneDoc());
// update document (and get it back)
doc = priv.updateDocument( doc, docPath, prev_rev );
// update document tree
priv.updateDocumentTree();
// found a tree - get active leaves
activeLeaves = utilities.getLeavesOnTree( docTree );
// this should return an array of all active leaves
// or a single leaf, which needs to be put into an array
activeLeaves = typeof activeLeaves === "string" ?
[activeLeaves] : activeLeaves;
// check if revision is on doc-tree and is an active leaf
if ( !utilities.isInObject( prev_rev, activeLeaves ) ) {
// check if it's a branch/dead leaf (deleted/updated version)
if ( utilities.isDeadLeaf( prev_rev, docTree ) ){
that.error( utilities.throwError( 409,
'Revision supplied is not the latest revision')
);
return;
}
// maybe a sync-PUT from another storage, we must
// have revs_info option, otherwise we cannot know
// where to put the file and update the storage tree
if ( !utilities.isDeadLeaf( prev_rev, docTree ) &&
command.getDocInfo('_revs_info') === undefined ){
that.error( utilities.throwError( 409,
'Missing revs_info required for sync-put')
);
return;
} else {
// SYNC PUT
// revs_info is provided, this is a new version
// store this document and merge
// get the new document
doc = command.getDoc();
// we are not updating, this is a copy&paste sync
// therefore the path should have the revision of
// the current document. No new revision hash
// needs to be created
docPathRev = docPath +'/'+doc._revs_info[0].rev;
// update ...???
priv.documentObjectUpdate(doc,command.cloneDoc());
// store the new item.
localstorage.setItem( docPathRev, doc );
// update tree and store
localstorage.setItem(treePath,
utilities.updateDocumentTree( docTree, prev_rev, null,
doc._revs_info )
);
that.success (
priv.manageOptions(
{ok:true,id:docId,rev:prev_rev},
command,
doc
)
);
}
} else {
that.success (
priv.manageOptions(
{ok:true,id:docId,rev:doc._rev},
command,
doc
)
);
}
});
// revision matches a currently active leaf
// update of an existing document version
// get doc
docPathRev = docPath +'/'+prev_rev;
doc = localstorage.getItem(docPathRev);
if (!doc ){
// documen not available, should not happen!
that.error( utilities.throwError( 404,
'Referenced document not found')
);
return;
} else {
// update ...?
priv.documentObjectUpdate(doc,command.cloneDoc());
// update document
doc = utilities.updateDocument( doc, docPath, prev_rev );
// store new doc (.../DOCID/new_REVISION)
localstorage.setItem(docPath+'/'+doc._rev, doc);
// delete old doc (.../DOCID/old_REVISION)
localstorage.deleteItem(docPath+'/'+prev_rev);
// update tree and store
localstorage.setItem(treePath,
utilities.updateDocumentTree(
docTree, prev_rev, doc._rev, undefined )
);
that.success (
priv.manageOptions(
{ok:true,id:docId,rev:doc._rev},
command,
doc
)
);
} // found a doc to update
} // updating existing document
} // found a tree
}); // set timeout
}; // end put
/**
......@@ -694,7 +493,7 @@ var newLocalStorage = function ( spec, my ) {
}
// rev = [number, hash]
rev = priv.generateNextRev(prev_rev, ''+doc+' '+now+'');
rev = utilities.generateNextRevision(prev_rev, ''+doc+' '+now+'');
doc._rev = rev.join('-');
doc._revisions.ids.unshift(rev[1]);
doc._revisions.start = rev[0];
......
......@@ -75,6 +75,13 @@ var command = function(spec, my) {
that.getContent = function () {
return priv.content;
};
/**
* @method getDoc returns the label of the command.
* @return {object} the document.
*/
that.getDoc = function() {
return priv.doc;
};
/**
* Returns an information about the document.
......
......@@ -201,11 +201,11 @@ isUUID = function( _id ){
},
isEmptyObject = function( obj) {
var key;
if (obj.length && obj.length > 0){
return false;
}
if (obj.length && obj.length === 0){
if (obj.length && obj.length === 0){
return true;
}
for (key in obj) {
......@@ -214,9 +214,47 @@ isEmptyObject = function( obj) {
}
}
return true;
};
},
//// end tools
//// test methods ////
checkReply = function(o,tick,fun){
basic_tick_function(o,tick,fun);
},
checkFile = function (response, o, tick, value, fun) {
o.tmp = localstorage.getItem('jio/local/'+o.username+'/jiotests/'+
response.id+'/'+response.rev );
// remove everything not needed for basic response
o.tmp.ok = true;
delete o.tmp._revisions;
delete o.tmp._revs_info;
delete o.tmp.content;
if (o.tmp) {
deepEqual (o.tmp,{
"ok":response.ok,
"_id":response.id,
"_rev":response.rev,
},'document was created or updated');
} else {
ok (false, 'document was not created or updated');
}
},
checkTreeNode = function (response,o,tick,value,fun) {
o.tmp = localstorage.getItem('jio/local/'+o.username+'/jiotests/'+
response.id+'/revision_tree' );
if (o.tmp) {
deepEqual (o.tmp,o.buildTestTree,'tree node was created');
} else {
ok (false, 'tree node was not created');
}
};
//// QUnit Tests ////
module ('Jio Global tests');
......@@ -286,11 +324,12 @@ test ('All tests', function () {
// All Ok Dummy Storage
o.jio = JIO.newJio({'type':'dummyallok'});
// post empty
o.jio.post({},
function(err, response) {
o.spy(o,'value',{"ok":true, "id":response.id, "rev":response.rev},'dummyallok post/create empty object');
o.spy(o,'value',{"ok":true, "id":response.id, "rev":response.rev},
'dummyallok post/create empty object');
o.f(response);
});
o.tick(o);
......@@ -298,7 +337,8 @@ test ('All tests', function () {
// post
o.jio.post({"content":"basic_content"},
function(err, response) {
o.spy(o,'value',{"ok":true, "id":response.id, "rev":response.rev},'dummyallok post/create object');
o.spy(o,'value',{"ok":true, "id":response.id, "rev":response.rev},
'dummyallok post/create object');
o.f(response);
});
o.tick(o);
......@@ -306,7 +346,8 @@ test ('All tests', function () {
// put
o.jio.put({"_id":"file","content":"basic_content"},
function(err, response) {
o.spy(o,'value',{"ok":true, "id":"file", "rev":response.rev},'dummyallok put create object');
o.spy(o,'value',{"ok":true, "id":"file", "rev":response.rev},
'dummyallok put create object');
o.f(response);
});
o.tick(o);
......@@ -399,7 +440,7 @@ test ('All tests', function () {
o.jio.allDocs (o.f);
o.tick(o);
o.jio.stop();
*/
});
/*
......@@ -592,125 +633,118 @@ module ( 'Jio LocalStorage' );
// ============================== POST ==========================
test ('Post', function(){
// runs following assertions
// a) POST with id - should be an error
// b) POST with attachment - should be an error
// c) POST with content
// d) check that document is created with UUID.revision
// e) check that document revision tree is created
var o = {};
o.t = this;
// 1) POST with id - should be an error
// 2) POST with attachment - should be an error
// 3) POST CREATE with content
// 4) check that document is created with UUID.revision
// 5) check that document revision tree is created
// 6) POST UPDATE
var o = {};
o.t = this;
o.clock = o.t.sandbox.useFakeTimers(),
localstorage = {
getItem: function (item) {
return JSON.parse (localStorage.getItem(item));
},
setItem: function (item,value) {
return localStorage.setItem(item,JSON.stringify (value));
return localStorage.setItem(item,JSON.stringify(value));
},
deleteItem: function (item) {
delete localStorage[item];
}
};
};
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.clean = clean_up_local_storage_function();
// test functions
o.checkReply = function(o,tick,fun){
basic_tick_function(o,tick,fun);
};
o.checkFile = function (response, o, tick, value, fun) {
o.tmp = localstorage.getItem('jio/local/MrPost/jiotests/'+ response.id+'/'+response.rev );
// fake it
o.tmp.ok = true;
delete o.tmp._revisions;
delete o.tmp._revs_info;
if (o.tmp) {
deepEqual (o.tmp,{
"ok":response.ok,
"_id":response.id,
"_rev":response.rev,
},'document was created');
} else {
ok (false, 'document was not created');
}
};
o.checkTree = function ( response, o, tick, value, fun) {
o.tmp = localstorage.getItem('jio/local/MrPost/jiotests/'+ response.id+'/revision_tree' );
if (o.tmp) {
deepEqual (o.tmp,{
"type":'leaf',
"status":'available',
"rev":response.rev,
"kids":{}
},'document tree was created');
} else {
ok (false, 'document tree was not created');
}
};
o.username = 'MrPost';
o.testBuildTree = [];
o.testRevisionStorage = [];
// let's go
o.jio = JIO.newJio({ type:'local', username:'MrPost',
o.jio = JIO.newJio({ type:'local', username:o.username,
applicationname:'jiotests' });
o.spy (o,'value',{
o.spy (o,'value',{
"error": 'forbidden',
"message": 'Forbidden',
"reason": 'ID cannot be supplied with a POST request. Please use PUT',
"status": 403,
"statusText": 'Forbidden'
},'POST with id = 403 forbidden');
o.jio.post({"_id":'file',"content":'content'},o.f);
// TEST a) POST with id
o.checkReply(o,null,true);
o.spy (o,'value',{
// TEST 1) POST with id
checkReply(o,null,true);
o.spy (o,'value',{
"error": 'forbidden',
"message": 'Forbidden',
"reason": 'Attachment cannot be added with a POST request',
"status": 403,
"statusText": 'Forbidden'
},'POST attachment = 403 forbidden');
o.jio.post({
"_id":'file/ABC',
"mimetype":'text/html',
"content":'<b>hello</b>'},o.f);
// TEST b) POST attachment
o.checkReply(o,null,true);
// TEST 2) POST attachment
checkReply(o,null,true);
o.jio.post({"content":'content'},
function(err, response) {
o.spy(o,'value',{"ok":true,"id":response.id,"rev":response.rev},
'POST content = ok');
o.f(response);
// TEST d) check if document is created and correct
o.checkFile(response, o, null, true);
// TEST e) check if document tree is created and correct
o.checkTree(response, o, null, true);
// store falseRevision
o.falseRevision = response.rev;
// build tree manually
o.testRevisionStorage.push(response.rev);
o.buildTestTree = {"kids":[],"rev":o.testRevisionStorage[0],
"status":'available',"type":'leaf'};
// TEST 4) check if document is created and correct
checkFile(response, o, null, true);
// TEST 5) check if document tree is created and correct
checkTreeNode(response, o, null, true);
});
// c) TEST POST content
o.checkReply(o,null,true);
// 3) TEST POST content
checkReply(o,null,true);
o.jio.stop();
o.clean;
});
// ============================== PUT ==========================
/*
test ('Put', function(){
// runs following assertions
// a)
// 1) PUT without ID = 409
// 2) PUT with wrong ID/rev = 404
// 3) PUT CREATE
// 4) check file was created
// 5) check tree was created
// 6) PUT UPDATE
// 7) check file was replaced
// 8) check tree was updated
// 9) PUT UPDATE 2
// 10) check file was replaced
// 11) check tree was updated
// 12) PUT UPDATE false revision = 409
// 13) SYNC-PUT no revs_info = 409
// 14) SYNC-PUT revs_info
var o = {};
o.t = this;
o.t = this;
o.clock = o.t.sandbox.useFakeTimers(),
o.falseRevision,
localstorage = {
getItem: function (item) {
return JSON.parse (localStorage.getItem(item));
......@@ -721,83 +755,224 @@ test ('Put', function(){
deleteItem: function (item) {
delete localStorage[item];
}
};
};
o.clock.tick(base_tick);
o.spy = basic_spy_function;
o.clean = clean_up_local_storage_function();
// test functions
o.checkReply = function(o,tick,fun){
basic_tick_function(o,tick,fun);
};
o.checkFile = function (response, o, tick, value, fun) {
o.tmp =
localstorage.getItem('jio/local/MrSaveName/jiotests/'+ response.id+'/'+response.rev );
// fake it
o.tmp.ok = true;
delete o.tmp._revisions;
delete o.tmp._revs_info;
if (o.tmp) {
deepEqual (o.tmp,{
"ok":response.ok,
"_id":response.id,
"_rev":response.rev,
},'document was created');
} else {
ok (false, 'document was not created');
}
};
o.checkTree = function ( response, o, tick, value, fun) {
o.tmp =
localstorage.getItem('jio/local/MrPut/jiotests/'+ response.id+'/revision_tree' );
if (o.tmp) {
deepEqual (o.tmp,{
"type":'leaf',
"status":'available',
"rev":response.rev,
"kids":{}
},'document tree was created');
} else {
ok (false, 'document tree was not created');
}
};
o.username = 'MrPutt';
o.testBuildTree = [];
o.testRevisionStorage = [];
// let's go
o.jio = JIO.newJio({ type:'local', username:'MrPut',
o.jio = JIO.newJio({ type:'local', username:o.username,
applicationname:'jiotests' });
o.spy (o,'value',{ },'');
o.jio.post({"_id":'file',"content":'content'},o.f);
// a) TEST
o.checkReply(o,null,true);
o.jio.post({"content":'content'},
// TEST 1) PUT without ID
o.spy (o,'value',{
"error": 'conflict',
"message": 'Document update conflict.',
"reason": 'Missing Document ID and or Revision',
"status": 409,
"statusText": 'Conflict'
},'PUT without id = 409 Conflict');
o.jio.put({"content":'content'},o.f);
checkReply(o,null,true);
// TEST 2) PUT wrong id/rev
o.spy (o,'value',{
"error": 'not found',
"message": 'Document not found.',
"reason": 'Document not found, please check revision and/or ID',
"status": 404,
"statusText": 'Not found'
},'PUT with wrong id/revision = 404 Not found');
o.jio.put({"content":'content',"_id":'myDoc',
"_rev":'1-ABCDEFG'},o.f);
checkReply(o,null,true);
// start adding content
o.jio.put({"content":'content',"_id":'myDoc'},
function(err, response) {
o.spy(o,'value',{"ok":true,"id":response.id,"rev":response.rev},
'POST content = ok');
'PUT content = ok');
o.f(response);
// d) check document is created and correct
o.checkFile(response, o, null, true);
// e) check document tree is created and correct
o.checkTree(response, o, null, true);
// store falseRevision
o.falseRevision = response.rev;
// build tree manually
o.testRevisionStorage.unshift(response.rev);
o.buildTestTree = {"kids":[],"rev":o.testRevisionStorage[0],
"status":'available',"type":'leaf'};
// TEST 4) check file was created
checkFile(response, o, null, true);
// TEST 5) check tree was created
checkTreeNode(response, o, null, true);
});
// c) POST content - o.spy must be in callback to access response
o.checkReply(o,null,true);
o.jio.stop();
o.clean;
// 3) TEST PUT content
// no idea why this is working for 3/6/9/12, but it does...
checkReply(o,null,true);
// update document
o.jio.put({"content":'content_modified',"_id":'myDoc',
"_rev":o.testRevisionStorage[0]},
function(err, response) {
o.spy(o,'value',{"ok":true,"id":response.id,"rev":response.rev},
'PUT content = ok');
o.f(response);
o.testRevisionStorage.unshift(response.rev);
o.buildTestTree = {"kids":[{"kids":[],"rev":
o.testRevisionStorage[0],"status":'available',
"type":'leaf'}],"rev":o.testRevisionStorage[1],
"status":'deleted',"type":'branch'};
// TEST 7) check document was replaced
checkFile(response, o, null, true);
// TEST 8) check tree was updated
checkTreeNode(response, o, null, true);
});
// 6) TEST PUT UPDATE
checkReply(o,null,true);
// update document 2nd time
o.jio.put({"content":'content_modified_again',
"_id":'myDoc',
"_rev":o.testRevisionStorage[0]},
function(err, response) {
var fake_rev_0,fake_rev_1,fake_id_0,fake_id_1;
o.spy(o,'value',{"ok":true,"id":response.id,
"rev":response.rev}, 'PUT content = ok');
o.f(response);
o.testRevisionStorage.unshift(response.rev);
o.buildTestTree = {"kids":[{"kids":[{"kids":[],
"rev":o.testRevisionStorage[0],"status":'available',
"type":'leaf'}],"rev":o.testRevisionStorage[1],
"status":'deleted',"type":'branch'}],
"rev":o.testRevisionStorage[2],"status":'deleted',
"type":'branch'};
// TEST 10) check document was replaced
checkFile(response, o, null, true);
// TEST 11) check tree was updated
checkTreeNode(response, o, null, true);
});
// 9) TEST PUT UPDATE
checkReply(o,null,true);
// continue to work with this instance
//o.jio.stop();
//o.clean;
// try updating with false revision
o.spy (o,'value',{
"error": 'conflict',
"message": 'Document update conflict.',
"reason":
'Revision supplied is not the latest revision',
"status": 409,
"statusText": 'Conflict'
},'PUT false revision = 409 Conflict');
o.jio.put({"content":'content_modified_false',
"_id":'myDoc',
"_rev":o.falseRevision},o.f);
// TEST 12) PUT false revision
checkReply(o,null,true);
// try updating without revs_info
o.spy (o,'value',{
"error": 'conflict',
"message": 'Document update conflict.',
"reason":
'Missing revs_info required for sync-put',
"status": 409,
"statusText": 'Conflict'
},'PUT no sync info = 409 Conflict');
o.jio.put({"content":'content_modified_false',
"_id":'myDoc',
"_rev":'1-abcdefg'},o.f);
// TEST 13) SYNC-PUT no revs_info
checkReply(o,null,true);
// add a new document version with fake revs_info
// the new document has the same origin and first edit,
// then it was changed to a new version (3-a9d...),
// which was changed to a fourth version (4-b5bb...),
// the tree must merge on o.testRevisionStorage[1]
// and add the two new dummy revisions into the final
// tree. Also the new document should be stored
// in local storage.
fake_rev_1 = o.testRevisionStorage[1];
fake_rev_0 = o.testRevisionStorage[0];
fake_id_0 = o.testRevisionStorage[0].split('-')[1];
fake_id_1 = o.testRevisionStorage[1].split('-')[1];
// put a new document version
o.jio.put({
"content":'a_new_version',
"_id":'myDoc',
"_rev":"4-b5bb2f1657ac5ac270c14b2335e51ef1ffccc0a7259e14bce46380d6c446eb89",
"_revs_info":[
{"rev":"4-b5bb2f1657ac5ac270c14b2335e51ef1ffccc0a7259e14bce46380d6c446eb89","status":"available"},
{"rev":"3-a9dac9ff5c8e1b2fce58e5397e9b6a8de729d5c6eff8f26a7b71df6348986123","status":"deleted"},
{"rev":fake_rev_1,"status":"deleted"},
{"rev":fake_rev_0,"status":"deleted"}
],
"_revisions":{
"start":4,
"ids":[
"b5bb2f1657ac5ac270c14b2335e51ef1ffccc0a7259e14bce46380d6c446eb89",
"a9dac9ff5c8e1b2fce58e5397e9b6a8de729d5c6eff8f26a7b71df6348986123",
fake_id_1,
fake_id_0
]}
},
function(err, response) {
//o.testRevisionStorage.unshift(response.rev);
o.buildTestTree = {
"kids":[
{
"kids":[
{"kids":[],"rev":o.testRevisionStorage[0],"status":'available',"type":'leaf'},
{"kids":[{
"kids":[],
"rev":"4-b5bb2f1657ac5ac270c14b2335e51ef1ffccc0a7259e14bce46380d6c446eb89",
"status":'available', "type":'leaf'
}],
"rev":"3-a9dac9ff5c8e1b2fce58e5397e9b6a8de729d5c6eff8f26a7b71df6348986123",
"status":'deleted',"type":'branch'
}],
"rev":o.testRevisionStorage[1],"status":'deleted',"type":'branch'}],
"rev":o.testRevisionStorage[2],"status":'deleted',"type":'branch'
};
o.spy(o,'value',{"ok":true,"id":response.id,
"rev":response.rev}, 'PUT SYNC = ok');
o.f(response);
// TEST 15) check document was stored
checkFile(response, o, null, true);
// TEST 16) check tree was updated
checkTreeNode(response, o, null, true);
});
// 14) TEST PUT UPDATE
checkReply(o,null,true);
});
*/
/*
......
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