Commit e862dbff authored by Tristan Cavelier's avatar Tristan Cavelier

Merge branch 'attachments' into revision_storage

parents 3635597f 4a43816a
......@@ -138,6 +138,7 @@ var clearlog = function () {
<div id="log">
</div>
<script type="text/javascript" src="../localorcookiestorage.js"></script>
<script type="text/javascript" src="../lib/md5/md5.js"></script>
<script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../lib/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../lib/base64/base64.js"></script>
......@@ -217,7 +218,7 @@ var callback = function (err,val,begin_date) {
log ('return : ' + JSON.stringify (val));
};
var command = function (method) {
var begin_date = Date.now(), doc = {}, opts = {}, other = {};
var begin_date = Date.now(), doc = {}, opts = {};
log (method);
if (!my_jio) {
return error ('no jio set');
......@@ -230,14 +231,11 @@ var command = function (method) {
switch (method) {
case 'putAttachment':
other.docid = $('#document_id').attr('value');
other.revision = $('#prev_rev').attr('value');
other.content = $('#content').attr('value');
other.mimetype = $('#mimetype').attr('value');
log ('docid: '+ other.docid);
log ('revision: '+ other.revision);
log ('content: '+ other.content);
log ('mimetype: '+ other.mimetype);
doc.id = $('#document_id').attr('value');
doc.rev = $('#prev_rev').attr('value');
doc.data = $('#content').attr('value');
doc.mimetype = $('#mimetype').attr('value');
log ('attachment: '+ JSON.stringify (doc));
break;
case 'post':
case 'put':
......@@ -260,6 +258,7 @@ var command = function (method) {
log ('opts: ' + JSON.stringify (opts));
switch (method) {
case "putAttachment":
case 'remove':
case 'post':
case 'put':
......@@ -273,14 +272,6 @@ var command = function (method) {
callback(err,val,begin_date);
});
break;
case 'putAttachment':
my_jio[method](
other.docid, other.revision, other.content,
other.mimetype, opts, function (err, val) {
callback(err,val,begin_date);
}
);
break;
}
};
var post = function () {
......
......@@ -17,6 +17,7 @@ var log = function (o) {
<div id="log">
</div>
<script type="text/javascript" src="../localorcookiestorage.js"></script>
<script type="text/javascript" src="../lib/md5/md5.js"></script>
<script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../lib/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../lib/base64/base64.js"></script>
......@@ -28,29 +29,50 @@ var log = function (o) {
var my_jio = null;
log ('Welcome to the jIO example.html!')
log ('--> Create jIO instance');
log ('-> Create jIO instance');
my_jio = jIO.newJio({
type: 'local', username: 'jIOtest', applicationname: 'example'
});
log ('--> put "hello" document to localStorage');
// careful ! asynchronous method
my_jio.put({_id:'hello',content:'world'},function (val) {
log ('done');
// careful ! asynchronous methods
log ('-> post "video" document metadata to localStorage');
my_jio.post({_id:'video', title:'My Video Title', codec:'vorbis', language:'en', description: 'Image compilation'}, function (val) {
log ('-> put "thumbnail" attachment to localStorage');
my_jio.putAttachment({id:"video/thumb.jpg", data:"BASE64DATA", mimetype:'image/jpeg'}, function (val) {
log ('-> put "video" attachment to localStorage');
my_jio.putAttachment({id:"video/myvideo.ogg", data:"BASE64DATATOO", mimetype:'video/ogg'}, function (val) {
log ('Done! Refresh the page to see get and remove command.');
}, function (err) {
log ('Error! '+ err.reason);
});
}, function (err) {
log ('Error! ' + err.reason);
});
}, function (err) {
log ('error!');
});
log ('Error! ' + err.reason);
log ('-> get "video" document metadata from localStorage');
my_jio.get('video', function (val) {
log ('Title is: "' + val["title"] + '"');
setTimeout ( function () {
log ('--> get "hello" document from localStorage');
log ('-> remove "video" document from localStorage');
my_jio.remove({_id:'video'}, function (val) {
log ('Done! Refresh the page to see post and putAttachment command.');
}, function (err) {
log ('Error! ' + err.reason);
});
my_jio.get('hello',function (val) {
log ('done, content: ' + val.content);
}, function (err) {
log ('error!');
log ('Error! ' + err.reason);
});
}, 500);
});
//-->
</script>
</body>
......
......@@ -88,6 +88,7 @@ module.exports = function(grunt) {
},
globals: {
LocalOrCookieStorage: true,
hex_md5: true,
console: true,
unescape: true,
// Needed to avoid "not defined error" with requireJs
......
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
......@@ -7,381 +7,6 @@
* - CryptedStorage ('crypted')
* - 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
*/
(function(LocalOrCookieStorage, $, Base64, sjcl, hex_sha256, jIO) {
(function (jIO, $, Base64, sjcl, hex_sha256) {
......@@ -5,8 +5,9 @@
var newLocalStorage = function (spec, my) {
spec = spec || {};
var that = my.basicStorage(spec, my),
priv = {},
var that, priv, localstorage;
that = my.basicStorage(spec, my);
priv = {};
/*
* Wrapper for the localStorage used to simplify instion of any kind of
......@@ -22,110 +23,16 @@ var newLocalStorage = function (spec, my) {
deleteItem: function (item) {
delete localStorage[item];
}
},
storage_user_array_name,
storage_file_array_name;
};
// attributes
priv.username = spec.username || '';
priv.applicationname = spec.applicationname || 'untitled';
storage_user_array_name = 'jio/local_user_array';
storage_file_array_name = 'jio/local_file_name_array/' +
priv.localpath = 'jio/localstorage/' +
priv.username + '/' + priv.applicationname;
/**
* Returns a list of users.
* @method getUserArray
* @return {array} The list of users.
*/
priv.getUserArray = function () {
return localstorage.getItem(storage_user_array_name) || [];
};
/**
* Adds a user to the user list.
* @method addUser
* @param {string} user_name The user name.
*/
priv.addUser = function (user_name) {
var user_array = priv.getUserArray();
user_array.push(user_name);
localstorage.setItem(storage_user_array_name, user_array);
};
/**
* checks if a user exists in the user array.
* @method doesUserExist
* @param {string} user_name The user name
* @return {boolean} true if exist, else false
*/
priv.doesUserExist = function (user_name) {
var user_array = priv.getUserArray(), i, l;
for (i = 0, l = user_array.length; i < l; i += 1) {
if (user_array[i] === user_name) {
return true;
}
}
return false;
};
/**
* Returns the file names of all existing files owned by the user.
* @method getFileNameArray
* @return {array} All the existing file paths.
*/
priv.getFileNameArray = function () {
return localstorage.getItem(storage_file_array_name) || [];
};
/**
* Adds a file name to the local file name array.
* @method addFileName
* @param {string} file_name The new file name.
*/
priv.addFileName = function (file_name) {
var file_name_array = priv.getFileNameArray();
file_name_array.push(file_name);
localstorage.setItem(storage_file_array_name, file_name_array);
};
/**
* Removes a file name from the local file name array.
* @method removeFileName
* @param {string} file_name The file name to remove.
*/
priv.removeFileName = function (file_name) {
var i, l, array = priv.getFileNameArray(), new_array = [];
for (i = 0, l = array.length; i < l; i+= 1) {
if (array[i] !== file_name) {
new_array.push(array[i]);
}
}
localstorage.setItem(storage_file_array_name, new_array);
};
/**
* Extends [obj] adding 0 to 3 values according to [command] options.
* @method manageOptions
* @param {object} obj The obj to extend
* @param {object} command The JIO command
* @param {object} doc The document object
*/
priv.manageOptions = function (obj, command, doc) {
obj = obj || {};
if (command.getOption('revs')) {
obj.revisions = doc._revisions;
}
if (command.getOption('revs_info')) {
obj.revs_info = doc._revs_info;
}
if (command.getOption('conflicts')) {
obj.conflicts = {total_rows:0, rows:[]};
}
return obj;
};
// ==================== Tools ====================
/**
* Update [doc] the document object and remove [doc] keys
* which are not in [new_doc]. It only changes [doc] keys not starting
......@@ -151,72 +58,17 @@ var newLocalStorage = function (spec, my) {
};
/**
* Create a new document
* @method setDocument
* @param {object} command Command object
* @param {string} trigger Put/Post
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv.setDocument = function (command, trigger) {
var doc = command.getDoc(),
document_id = doc._id,
document_rev = doc._rev,
document_path = 'jio/local/'+priv.username+'/'+
priv.applicationname+'/'+document_id+
'/'+document_rev;
if (trigger === 'put'){
priv.documentObjectUpdate(doc, command.cloneDoc());
}
localstorage.setItem(document_path, doc);
if (trigger === 'post'){
if (!priv.doesUserExist(priv.username)) {
priv.addUser(priv.username);
}
priv.addFileName(document_id);
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
return false;
}
return priv.manageOptions(
{ok:true,id:document_id,rev:document_rev}, command, doc);
};
/**
* get a document or attachment
* @method getDocument
* @param {object} command Command object
*/
priv.getDocument = function (command) {
var doc = command.getDoc();
document_id = doc._id;
document_rev = doc._rev;
document_path = 'jio/local/'+priv.username+'/'+
priv.applicationname+'/'+document_id+
'/'+document_rev;
localStorage.getItem( document_path );
return priv.manageOptions(
{ok:true,id:document_id,rev:document_rev}, command, doc);
};
/**
* delete a document or attachment
* @method getDocument
* @param {object} command Command object
*/
priv.deleteDocument = function (command) {
var doc = command.getDoc();
document_id = doc._id;
document_rev = doc._rev;
document_path = 'jio/local/'+priv.username+'/'+
priv.applicationname+'/'+document_id+
'/'+document_rev;
localStorage.deleteItem( document_path );
return priv.manageOptions(
{ok:true,id:document_id,rev:document_rev}, command, doc);
return true;
};
// ===================== overrides ======================
......@@ -228,68 +80,121 @@ var newLocalStorage = function (spec, my) {
};
that.validateState = function() {
if (priv.username) {
if (typeof priv.username === "string" &&
priv.username !== '') {
return '';
}
return 'Need at least one parameter: "username".';
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method _post
* @method post
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
*
*/
that._post = function (command) {
that.post = function (command) {
setTimeout (function () {
that.success(priv.setDocument(command,'post'));
var doc = command.getDocId();
if (!(typeof doc === "string" && doc !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
doc = localstorage.getItem(
priv.localpath + "/" + doc);
if (doc === null) {
// the document does not exists
localstorage.setItem(
priv.localpath + "/" + command.getDocId(),
command.cloneDoc());
that.success({"ok":true,"id":command.getDocId()});
} else {
// the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
});
}
});
};
/**
* Create or update a document in local storage.
* @method _put
* @method put
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
*
*/
that._put = function (command) {
that.put = function (command) {
setTimeout(function () {
var docid = command.getDocId(),
path = 'jio/local/'+priv.username+'/'+
priv.applicationname+'/'+docid,
doc = localstorage.getItem(path);
if (!doc) {
that.success(priv.setDocument(command, 'post'));
var doc;
doc = localstorage.getItem(
priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exists
doc = command.cloneDoc();
} else {
that.success(priv.documentUpdate(command, 'put'));
// the document already exists
priv.documentObjectUpdate(doc, command.cloneDoc());
}
// write
localstorage.setItem(
priv.localpath + "/" + command.getDocId(),
doc);
that.success({"ok":true,"id":command.getDocId()});
});
};
/**
* Add an attachment to a document
* @method _putAttachment
* @method putAttachment
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
*/
that._putAttachment = function (command) {
that.putAttachment = function (command) {
setTimeout(function () {
that.success(priv.setDocument(command, 'put');
var doc;
doc = localstorage.getItem(
priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exists
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
return;
} else {
// the document already exists
doc["_attachments"] = doc["_attachments"] || {};
doc["_attachments"][command.getAttachmentId()] = {
"content_type": command.getAttachmentMimeType(),
"digest": "md5-"+command.md5SumAttachmentData(),
"length": command.getAttachmentLength()
};
}
// upload data
localstorage.setItem(
priv.localpath + "/" + command.getDocId() + "/" +
command.getAttachmentId(),
command.getAttachmentData()
);
// write document
localstorage.setItem(
priv.localpath + "/" + command.getDocId(),
doc);
that.success({
"ok":true,
"_id":command.getDocId()+"/"+command.getAttachmentId()
});
});
};
......@@ -297,75 +202,115 @@ var newLocalStorage = function (spec, my) {
* Get a document or attachment
* @method get
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
*/
that._get = function (command) {
that.get = function (command) {
setTimeout (function () {
that.success( priv.getDocument(command) );
var doc;
if (typeof command.getAttachmentId() === "string") {
// seeking for an attachment
doc = localstorage.getItem(
priv.localpath + "/" + command.getDocId() + "/" +
command.getAttachmentId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment ",
"reason": "attachment does not exists"
});
}
} else {
// seeking for a document
doc = localstorage.getItem(
priv.localpath + "/" + command.getDocId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exists"
});
}
}
});
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
*/
that._remove = function (command) {
that.remove = function (command) {
setTimeout (function () {
that.success( priv.deleteDocument(command) );
var doc;
doc = localstorage.getItem(
priv.localpath + "/" + command.getDocId());
if (typeof command.getAttachmentId() === "string") {
// seeking for an attachment
localstorage.deleteItem(
priv.localpath + "/" + command.getDocId() + "/" +
command.getAttachmentId());
// remove attachment from document
if (doc !== null && typeof doc === "object" &&
typeof doc["_attachments"] === "object") {
delete doc["_attachments"][command.getAttachmentId()];
if (priv.objectIsEmpty(doc["_attachments"])) {
delete doc["_attachments"];
}
localstorage.setItem(
priv.localpath + "/" + command.getDocId(),
doc);
}
that.success({
"ok": true,
"id": command.getDocId()+"/"+command.getAttachmentId()
});
} else {
// seeking for a document
var attachment_list = [], i;
if (doc !== null && typeof doc === "object" &&
typeof doc["_attachments"] === "object") {
// prepare list of attachments
for (i in doc["_attachments"]) {
attachment_list.push(i);
}
}
localstorage.deleteItem(
priv.localpath + "/" + command.getDocId());
// delete all attachments
for (i = 0; i < attachment_list.length; i += 1) {
localstorage.deleteItem(
priv.localpath+"/"+command.getDocId()+"/"+
attachment_list[i]);
}
that.success({
"ok": true,
"id": command.getDocId()
});
}
});
};
/**
* get all filenames belonging to a user from the document index
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
*
* Available options:
* - {boolean} conflicts Add a conflicts object to the response
* - {boolean} revs Add the revisions history of the document
* - {boolean} revs_info Add revisions informations
* - {boolean} include_docs Include documents with index
*/
that._allDocs = function (command) {
that.allDocs = function (command) {
setTimeout(function () {
var new_array = [],
array = priv.getFileNameArray(),
i,l,
path = 'jio/local/'+priv.username+'/'+priv.applicationname,
include_docs = command.getOption('include_docs'),
doc, item;
for (i = 0, l = array.length; i < l; i += 1) {
item = array[i];
if (include_docs === true){
doc = that._get(path+'/'+item.id+'/'+item.value.key );
new_array.push({
"id":item.id,
"key":item.key,
"value":item.value,
"doc":doc
});
} else {
new_array.push({
"id":item.id,
"key":item.key,
"value":item.value
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Your are not allowed to use this command",
"reason": "LocalStorage forbids AllDocs command executions"
});
}
}
that.success ({total_rows:new_array.length,rows:new_array});
});
};
......
}( LocalOrCookieStorage, jQuery, Base64, sjcl, hex_sha256, jIO ));
}(jIO, jQuery, Base64, sjcl, hex_sha256));
......@@ -14,7 +14,7 @@ var activityUpdater = (function(spec, my) {
* @method touch
*/
priv.touch = function() {
LocalOrCookieStorage.setItem ('jio/id/'+priv.id, Date.now());
localstorage.setItem ('jio/id/'+priv.id, Date.now());
};
/**
......@@ -72,4 +72,3 @@ var activityUpdater = (function(spec, my) {
return that;
}());
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) {
'remove':removeCommand,
'allDocs':allDocsCommand,
'putAttachment':putAttachmentCommand
'_post':_postCommand,
'_put':_putCommand,
'_get':_getCommand,
'_remove':_removeCommand,
'_allDocs':_allDocsCommand,
'_putAttachment':_putAttachmentCommand
};
// creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) {
......@@ -27,7 +21,7 @@ var command = function(spec, my) {
priv.tried = 0;
priv.doc = spec.doc || {};
priv.docid = spec.docid || spec.doc._id || '';
priv.docid = spec.docid || priv.doc._id;
priv.option = spec.options || {};
priv.callbacks = spec.callbacks || {};
priv.success = priv.callbacks.success || function (){};
......@@ -42,6 +36,20 @@ var command = function(spec, my) {
priv.on_going = false;
// Methods //
/**
* Returns a serialized version of this command.
* @method super_serialized
* @return {object} The serialized command.
*/
that.super_serialized = function () {
var o = that.serialized() || {};
o["label"] = that.getLabel();
o["tried"] = priv.tried;
o["doc"] = that.cloneDoc();
o["option"] = that.cloneOption();
return o;
};
/**
* Returns a serialized version of this command.
* Override this function.
......@@ -49,10 +57,7 @@ var command = function(spec, my) {
* @return {object} The serialized command.
*/
that.serialized = function() {
return {label:that.getLabel(),
tried:priv.tried,
doc:that.cloneDoc(),
option:that.cloneOption()};
return {};
};
/**
......@@ -70,6 +75,9 @@ var command = function(spec, my) {
* @return {string} The document id
*/
that.getDocId = function () {
if (typeof priv.docid !== "string") {
return undefined;
}
return priv.docid.split('/')[0];
};
......@@ -79,6 +87,9 @@ var command = function(spec, my) {
* @return {string} The attachment id
*/
that.getAttachmentId = function () {
if (typeof priv.docid !== "string") {
return undefined;
}
return priv.docid.split('/')[1];
};
......@@ -91,6 +102,42 @@ var command = function(spec, my) {
return priv.doc;
};
/**
* Returns the data of the attachment
* @method getAttachmentData
* @return {string} The data
*/
that.getAttachmentData = function () {
return priv.doc._data;
};
/**
* Returns the data length of the attachment
* @method getAttachmentLength
* @return {number} The length
*/
that.getAttachmentLength = function () {
return priv.doc._data.length;
};
/**
* Returns the mimetype of the attachment
* @method getAttachmentMimeType
* @return {string} The mimetype
*/
that.getAttachmentMimeType = function () {
return priv.doc._mimetype;
};
/**
* Generate the md5sum of the attachment data
* @method md5SumAttachmentData
* @return {string} The md5sum
*/
that.md5SumAttachmentData = function () {
return hex_md5(priv.doc._data);
};
/**
* Returns an information about the document.
* @method getDocInfo
......@@ -116,7 +163,8 @@ var command = function(spec, my) {
* @param {object} storage The storage.
*/
that.validate = function (storage) {
if (!(priv.docid || priv.doc._id).match(/^[^\/]+([\/][^\/]+)?$/)) {
if (typeof priv.docid === "string" &&
!priv.docid.match(/^[^\/]+([\/][^\/]+)?$/)) {
that.error({
status:21,statusText:'Invalid Document Id',
error:'invalid_document_id',
......@@ -253,11 +301,7 @@ var command = function(spec, my) {
* @return {object} The clone of the command options.
*/
that.cloneOption = function () {
var k, o = {};
for (k in priv.option) {
o[k] = priv.option[k];
}
return o;
return JSON.parse(JSON.stringify(priv.option));
};
/**
......@@ -266,14 +310,7 @@ var command = function(spec, my) {
* @return {object} The clone of the document.
*/
that.cloneDoc = function () {
if (priv.docid) {
return priv.docid;
}
var k, o = {};
for (k in priv.doc) {
o[k] = priv.doc[k];
}
return o;
return JSON.parse(JSON.stringify(priv.doc));
};
return that;
......
......@@ -9,14 +9,28 @@ var getCommand = function(spec, my) {
};
that.validateState = function() {
if (!that.getDocId()) {
if (!(typeof that.getDocId() === "string" && that.getDocId() !== "")) {
that.error({
status:20,statusText:'Document Id Required',
error:'document_id_required',
message:'No document id.',reason:'no document id'
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() === "string") {
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return false;
}
return true;
};
......
......@@ -10,6 +10,21 @@ var postCommand = function(spec, my) {
return 'post';
};
that.validateState = function () {
if (typeof that.getAttachmentId() !== "undefined") {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters "+
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true;
};
that.executeOn = function(storage) {
storage.post (that);
};
......
......@@ -11,15 +11,27 @@ var putAttachmentCommand = function(spec, my) {
that.executeOn = function (storage) {
storage.putAttachment (that);
};
that.validateState = function () {
if (typeof that.getContent() !== 'string') {
if (typeof that.getAttachmentId() !== "string") {
that.error({
status:22,statusText:'Content Required',
error:'content_required',
message:'No data to put.',reason:'no data to put'
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return true;
};
......
......@@ -10,12 +10,29 @@ var putCommand = function(spec, my) {
return 'put';
};
/**
* Validates the storage handler.
* @param {object} handler The storage handler
*/
that.validate = function () {
return that.validateState();
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !== "")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() !== "undefined") {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters "+
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true;
};
that.executeOn = function(storage) {
......
......@@ -8,6 +8,32 @@ var removeCommand = function(spec, my) {
return 'remove';
};
that.validateState = function() {
if (!(typeof that.getDocId() === "string" && that.getDocId() !== "")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() === "string") {
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return false;
}
return true;
};
that.executeOn = function(storage) {
storage.remove (that);
};
......
var jIO = (function () {
(function (scope, hex_md5) {
"use strict";
var localstorage;
if (typeof localStorage !== "undefined") {
localstorage = {
getItem: function (item) {
return JSON.parse(localStorage.getItem(item));
},
setItem: function (item, value) {
return localStorage.setItem(item, JSON.stringify(value));
},
deleteItem: function (item) {
delete localStorage[item];
},
clone: function () {
return JSON.parse(JSON.stringify(localStorage));
}
};
} else {
(function () {
var pseudo_localStorage = {};
localstorage = {
getItem: function (item) {
return JSON.parse(pseudo_localStorage[item]);
},
setItem: function (item, value) {
return pseudo_localStorage[item] = JSON.stringify(value);
},
deleteItem: function (item) {
delete pseudo_localStorage[item];
},
clone: function () {
return JSON.parse(JSON.stringify(pseudo_localStorage));
}
};
}());
}
......@@ -12,7 +12,7 @@
// Initialize the jio id and add the new id to the list
if (priv.id === null) {
var i, jio_id_a =
LocalOrCookieStorage.getItem (jio_id_array_name) || [];
localstorage.getItem (jio_id_array_name) || [];
priv.id = 1;
for (i = 0; i < jio_id_a.length; i+= 1) {
if (jio_id_a[i] >= priv.id) {
......@@ -20,7 +20,7 @@
}
}
jio_id_a.push(priv.id);
LocalOrCookieStorage.setItem (jio_id_array_name,jio_id_a);
localstorage.setItem (jio_id_array_name,jio_id_a);
activityUpdater.setId(priv.id);
jobManager.setId(priv.id);
}
......@@ -143,6 +143,8 @@
} else {
param.callback = callback1;
}
} else {
param.callback = function () {};
}
};
......@@ -176,7 +178,8 @@
/**
* Post a document.
* @method post
* @param {object} doc The document {"content":}.
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id (optional), "/" are forbidden
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
......@@ -206,7 +209,8 @@
/**
* Put a document.
* @method put
* @param {object} doc The document {"_id":,"_rev":,"content":}.
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id, "/" are forbidden
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
......@@ -236,7 +240,7 @@
/**
* Get a document.
* @method get
* @param {string} docid The document id (the path).
* @param {string} docid The document id: "doc_id" or "doc_id/attachmt_id".
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {string} rev The revision we want to get.
......@@ -267,7 +271,8 @@
/**
* Remove a document.
* @method remove
* @param {object} doc The document {"_id":,"_rev":}.
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id: "doc_id" or "doc_id/attachment_id"
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
......@@ -312,7 +317,7 @@
configurable:false,enumerable:false,writable:false,value:
function(options, success, error) {
var param = priv.parametersToObject(
[options,success.error],
[options,success,error],
{max_retry: 3}
);
......@@ -326,10 +331,11 @@
/**
* Put an attachment to a document.
* @method putAttachment
* @param {string} id The attachment id ("document/attachment").
* @param {string} rev The document revision.
* @param {string} doc Base64 attachment content.
* @param {string} mimetype The attachment mimetype
* @param {object} doc The document object. Contains at least:
* - {string} id The document id: "doc_id/attchment_id"
* - {string} data Base64 attachment data
* - {string} mimetype The attachment mimetype
* - {string} rev The attachment revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
......@@ -342,14 +348,18 @@
*/
Object.defineProperty(that,"putAttachment",{
configurable:false,enumerable:false,writable:false,value:
function(id, rev, doc, mimetype, options, success, error) {
var param = priv.parametersToObject(
function(doc, options, success, error) {
var param, k, doc_with_underscores = {};
param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
for (k in doc) {
doc_with_underscores["_"+k] = doc[k];
}
console.log (doc_with_underscores);
priv.addJob(putAttachmentCommand,{
doc:{_id:id,content:doc,_rev:rev,mimetype:mimetype},
doc:doc_with_underscores,
options:param.options,
callbacks:{success:param.success,error:param.error}
});
......
......@@ -25,7 +25,7 @@ var jobManager = (function(spec) {
* @return {array} The job array.
*/
priv.getJobArray = function() {
return LocalOrCookieStorage.getItem(priv.getJobArrayName())||[];
return localstorage.getItem(priv.getJobArrayName())||[];
};
/**
......@@ -37,7 +37,7 @@ var jobManager = (function(spec) {
for (i = 0; i < priv.job_array.length; i+= 1) {
new_a.push(priv.job_array[i].serialized());
}
LocalOrCookieStorage.setItem(priv.getJobArrayName(),new_a);
localstorage.setItem(priv.getJobArrayName(),new_a);
};
/**
......@@ -90,7 +90,7 @@ var jobManager = (function(spec) {
clearInterval(priv.interval_id);
priv.interval_id = null;
if (priv.job_array.length === 0) {
LocalOrCookieStorage.deleteItem(priv.getJobArrayName());
localstorage.deleteItem(priv.getJobArrayName());
}
}
};
......@@ -105,7 +105,7 @@ var jobManager = (function(spec) {
var i, jio_id_a;
priv.lastrestore = priv.lastrestore || 0;
if (priv.lastrestore > (Date.now()) - 2000) { return; }
jio_id_a = LocalOrCookieStorage.getItem('jio/id_array')||[];
jio_id_a = localstorage.getItem('jio/id_array')||[];
for (i = 0; i < jio_id_a.length; i+= 1) {
priv.restoreOldJioId(jio_id_a[i]);
}
......@@ -119,7 +119,7 @@ var jobManager = (function(spec) {
*/
priv.restoreOldJioId = function(id) {
var jio_date;
jio_date = LocalOrCookieStorage.getItem('jio/id/'+id)||0;
jio_date = localstorage.getItem('jio/id/'+id)||0;
if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec
priv.restoreOldJobFromJioId(id);
priv.removeOldJioId(id);
......@@ -134,7 +134,7 @@ var jobManager = (function(spec) {
*/
priv.restoreOldJobFromJioId = function(id) {
var i, jio_job_array;
jio_job_array = LocalOrCookieStorage.getItem('jio/job_array/'+id)||[];
jio_job_array = localstorage.getItem('jio/job_array/'+id)||[];
for (i = 0; i < jio_job_array.length; i+= 1) {
var command_object = command(jio_job_array[i].command);
if (command_object.canBeRestored()) {
......@@ -152,14 +152,14 @@ var jobManager = (function(spec) {
*/
priv.removeOldJioId = function(id) {
var i, jio_id_array, new_array = [];
jio_id_array = LocalOrCookieStorage.getItem('jio/id_array')||[];
jio_id_array = localstorage.getItem('jio/id_array')||[];
for (i = 0; i < jio_id_array.length; i+= 1) {
if (jio_id_array[i] !== id) {
new_array.push(jio_id_array[i]);
}
}
LocalOrCookieStorage.setItem('jio/id_array',new_array);
LocalOrCookieStorage.deleteItem('jio/id/'+id);
localstorage.setItem('jio/id_array',new_array);
localstorage.deleteItem('jio/id/'+id);
};
/**
......@@ -168,7 +168,7 @@ var jobManager = (function(spec) {
* @param {number} id The jio id.
*/
priv.removeJobArrayFromJioId = function(id) {
LocalOrCookieStorage.deleteItem('jio/job_array/'+id);
localstorage.deleteItem('jio/job_array/'+id);
};
/**
......
return jioNamespace;
}());
Object.defineProperty(scope,"jIO",{
configurable:false,enumerable:false,writable:false,value:jioNamespace
});
}(window, hex_md5));
......@@ -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.
* @method execute
......@@ -278,7 +70,7 @@ var storage = function(spec, my) {
*/
that.serialized = function () {
return {};
}
};
/**
* Validate the storage state. It returns a empty string all is ok.
......@@ -289,82 +81,7 @@ var storage = function(spec, my) {
return '';
};
that.post = function (command) {
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 () {
that.post = function () {
setTimeout(function () {
that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Post\" command is not implemented"
......@@ -372,7 +89,7 @@ var storage = function(spec, my) {
});
};
that._put = function () {
that.put = function () {
setTimeout(function () {
that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Put\" command is not implemented"
......@@ -380,7 +97,7 @@ var storage = function(spec, my) {
});
};
that._putAttachment = function () {
that.putAttachment = function () {
setTimeout(function () {
that.error(that.createErrorObject(
0,"Not Implemented Yet",
......@@ -389,7 +106,7 @@ var storage = function(spec, my) {
});
};
that._get = function () {
that.get = function () {
setTimeout(function () {
that.error(that.createErrorObject(
0,"Not Implemented Yet","\"Get\" command is not implemented"
......@@ -397,7 +114,7 @@ var storage = function(spec, my) {
});
};
that._allDocs = function () {
that.allDocs = function () {
setTimeout(function () {
that.error(that.createErrorObject(
0,"Not Implemented Yet",
......@@ -406,7 +123,7 @@ var storage = function(spec, my) {
});
};
that._remove = function () {
that.remove = function () {
setTimeout(function () {
that.error(that.createErrorObject(
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