/* * Copyright 2013, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ /*global jIO: true, localStorage: true, setTimeout: true, complex_queries, define */ /** * JIO Local Storage. Type = 'local'. * Local browser "database" storage. * * Storage Description: * * { * "type": "local", * "username": <non empty string>, // to define user space * "application_name": <string> // default 'untitled' * } * * Document are stored in path * 'jio/localstorage/username/application_name/document_id' like this: * * { * "_id": "document_id", * "_attachments": { * "attachment_name": { * "length": data_length, * "digest": "md5-XXX", * "content_type": "mime/type" * }, * "attachment_name2": {..}, ... * }, * "metadata_name": "metadata_value" * "metadata_name2": ... * ... * } * * Only "_id" and "_attachments" are specific metadata keys, other one can be * added without loss. * * @class LocalStorage */ // define([module_name], [dependencies], module); (function (dependencies, module) { "use strict"; if (typeof define === 'function' && define.amd) { return define(dependencies, module); } module(jIO, complex_queries); }(['jio', 'complex_queries'], function (jIO, complex_queries) { "use strict"; jIO.addStorageType('local', function (spec, my) { spec = spec || {}; var that, priv, localstorage; that = my.basicStorage(spec, my); priv = {}; /* * Wrapper for the localStorage used to simplify instion of any kind of * values */ localstorage = { getItem: function (item) { var value = localStorage.getItem(item); return value === null ? null : JSON.parse(value); }, setItem: function (item, value) { return localStorage.setItem(item, JSON.stringify(value)); }, removeItem: function (item) { return localStorage.removeItem(item); } }; // attributes priv.username = spec.username || ''; priv.application_name = spec.application_name || 'untitled'; priv.localpath = 'jio/localstorage/' + priv.username + '/' + priv.application_name; // ==================== Tools ==================== /** * Generate a new uuid * @method generateUuid * @return {string} The new uuid */ priv.generateUuid = function () { var S4 = function () { /* 65536 */ var i, string = Math.floor( Math.random() * 0x10000 ).toString(16); for (i = string.length; i < 4; i += 1) { string = '0' + string; } return string; }; return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4(); }; /** * Checks if an object has no enumerable keys * @method objectIsEmpty * @param {object} obj The object * @return {boolean} true if empty, else false */ priv.objectIsEmpty = function (obj) { var k; for (k in obj) { if (obj.hasOwnProperty(k)) { return false; } } return true; }; // ===================== overrides ====================== that.specToStore = function () { return { "application_name": priv.application_name, "username": priv.username }; }; that.validateState = function () { if (typeof priv.username === "string" && priv.username !== '') { return ''; } return 'Need at least one parameter: "username".'; }; // ==================== commands ==================== /** * Create a document in local storage. * @method post * @param {object} command The JIO command */ that.post = function (command) { setTimeout(function () { var doc, doc_id = command.getDocId(); if (!doc_id) { doc_id = priv.generateUuid(); } doc = localstorage.getItem(priv.localpath + "/" + doc_id); if (doc === null) { // the document does not exist doc = command.cloneDoc(); doc._id = doc_id; delete doc._attachments; localstorage.setItem(priv.localpath + "/" + doc_id, doc); that.success({ "ok": true, "id": doc_id }); } 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 * @param {object} command The JIO command */ that.put = function (command) { setTimeout(function () { var doc, tmp; doc = localstorage.getItem(priv.localpath + "/" + command.getDocId()); if (doc === null) { // the document does not exist doc = command.cloneDoc(); delete doc._attachments; } else { // the document already exists tmp = command.cloneDoc(); tmp._attachments = doc._attachments; doc = tmp; } // write localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc); that.success({ "ok": true, "id": command.getDocId() }); }); }; /** * Add an attachment to a document * @method putAttachment * @param {object} command The JIO command */ that.putAttachment = function (command) { setTimeout(function () { var doc; doc = localstorage.getItem(priv.localpath + "/" + command.getDocId()); if (doc === null) { // the document does not exist that.error({ "status": 404, "statusText": "Not Found", "error": "not_found", "message": "Impossible to add attachment", "reason": "Document not found" }); return; } // 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(), "attachment": command.getAttachmentId() }); }); }; /** * Get a document * @method get * @param {object} command The JIO command */ that.get = function (command) { setTimeout(function () { var 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 exist" }); } }); }; /** * Get a attachment * @method getAttachment * @param {object} command The JIO command */ that.getAttachment = function (command) { setTimeout(function () { var 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 exist" }); } }); }; /** * Remove a document * @method remove * @param {object} command The JIO command */ that.remove = function (command) { setTimeout(function () { var doc, i, attachment_list; doc = localstorage.getItem(priv.localpath + "/" + command.getDocId()); attachment_list = []; if (doc !== null && typeof doc === "object") { if (typeof doc._attachments === "object") { // prepare list of attachments for (i in doc._attachments) { if (doc._attachments.hasOwnProperty(i)) { attachment_list.push(i); } } } } else { return that.error({ "status": 404, "statusText": "Not Found", "error": "not_found", "message": "Document not found", "reason": "missing" }); } localstorage.removeItem(priv.localpath + "/" + command.getDocId()); // delete all attachments for (i = 0; i < attachment_list.length; i += 1) { localstorage.removeItem(priv.localpath + "/" + command.getDocId() + "/" + attachment_list[i]); } that.success({ "ok": true, "id": command.getDocId() }); }); }; /** * Remove an attachment * @method removeAttachment * @param {object} command The JIO command */ that.removeAttachment = function (command) { setTimeout(function () { var doc, error, i, attachment_list; error = function (word) { that.error({ "status": 404, "statusText": "Not Found", "error": "not_found", "message": word + " not found", "reason": "missing" }); }; doc = localstorage.getItem(priv.localpath + "/" + command.getDocId()); // remove attachment from document if (doc !== null && typeof doc === "object" && typeof doc._attachments === "object") { if (typeof doc._attachments[command.getAttachmentId()] === "object") { delete doc._attachments[command.getAttachmentId()]; if (priv.objectIsEmpty(doc._attachments)) { delete doc._attachments; } localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc); localstorage.removeItem(priv.localpath + "/" + command.getDocId() + "/" + command.getAttachmentId()); that.success({ "ok": true, "id": command.getDocId(), "attachment": command.getAttachmentId() }); } else { error("Attachment"); } } else { error("Document"); } }); }; /** * Get all filenames belonging to a user from the document index * @method allDocs * @param {object} command The JIO command */ that.allDocs = function (command) { var i, row, path_re, rows, document_list, option, document_object; rows = []; document_list = []; path_re = new RegExp( "^" + complex_queries.stringEscapeRegexpCharacters(priv.localpath) + "/[^/]+$" ); option = command.cloneOption(); if (typeof complex_queries !== "object" || (option.query === undefined && option.sort_on === undefined && option.select_list === undefined && option.include_docs === undefined)) { rows = []; for (i in localStorage) { if (localStorage.hasOwnProperty(i)) { // filter non-documents if (path_re.test(i)) { row = { value: {} }; row.id = i.split('/').slice(-1)[0]; row.key = row.id; if (command.getOption('include_docs')) { row.doc = JSON.parse(localStorage.getItem(i)); } rows.push(row); } } } that.success({"rows": rows, "total_rows": rows.length}); } else { // create complex query object from returned results for (i in localStorage) { if (localStorage.hasOwnProperty(i)) { if (path_re.test(i)) { document_list.push(localstorage.getItem(i)); } } } option.select_list = option.select_list || []; option.select_list.push("_id"); if (option.include_docs === true) { document_object = {}; document_list.forEach(function (meta) { document_object[meta._id] = meta; }); } complex_queries.QueryFactory.create(option.query || ""). exec(document_list, option); document_list = document_list.map(function (value) { var o = { "id": value._id, "key": value._id }; if (option.include_docs === true) { o.doc = document_object[value._id]; delete document_object[value._id]; } delete value._id; o.value = value; return o; }); that.success({"total_rows": document_list.length, "rows": document_list}); } }; return that; }); }));