/* * 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 define, jIO, setTimeout, complex_queries */ // define([module_name], [dependencies], module); (function (dependencies, module) { "use strict"; if (typeof define === 'function' && define.amd) { return define(dependencies, module); } module(jIO); }(['jio'], function (jIO) { var storage = {}; /** * Returns 4 hexadecimal random characters. * * @return {String} The characters */ function S4() { return ('0000' + Math.floor( Math.random() * 0x10000 /* 65536 */ ).toString(16)).slice(-4); } /** * An Universal Unique ID generator * * @return {String} The new UUID. */ function generateUuid() { return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4(); } /** * Checks if an object has no enumerable keys * * @param {Object} obj The object * @return {Boolean} true if empty, else false */ function objectIsEmpty(obj) { var k; for (k in obj) { if (obj.hasOwnProperty(k)) { return false; } } return true; } /** * JIO Ram Storage. Type = 'ram'. * Memory "database" storage. * * Storage Description: * * { * "type": "ram", * "namespace": <string>, // default 'default' * } * * Document are stored in path * 'namespace/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 RamStorage */ function ramStorage(spec, my) { var that, priv = {}, ramstorage; that = my.basicStorage(spec, my); /* * Wrapper for the localStorage used to simplify instion of any kind of * values */ ramstorage = { getItem: function (item) { var value = storage[item]; return value === undefined ? null : JSON.parse(value); }, setItem: function (item, value) { storage[item] = JSON.stringify(value); }, removeItem: function (item) { delete storage[item]; } }; // attributes if (typeof spec.namespace !== 'string') { priv.namespace = 'default'; } else { priv.namespace = spec.namespace; } // ===================== overrides ====================== that.specToStore = function () { return { "namespace": priv.namespace }; }; that.validateState = function () { return ''; }; // ==================== 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 = generateUuid(); } doc = ramstorage.getItem(priv.namespace + "/" + doc_id); if (doc === null) { // the document does not exist doc = command.cloneDoc(); doc._id = doc_id; delete doc._attachments; ramstorage.setItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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 ramstorage.setItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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 ramstorage.setItem(priv.namespace + "/" + command.getDocId() + "/" + command.getAttachmentId(), command.getAttachmentData()); // write document ramstorage.setItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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" }); } ramstorage.removeItem(priv.namespace + "/" + command.getDocId()); // delete all attachments for (i = 0; i < attachment_list.length; i += 1) { ramstorage.removeItem(priv.namespace + "/" + 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 = ramstorage.getItem(priv.namespace + "/" + 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; } ramstorage.setItem(priv.namespace + "/" + command.getDocId(), doc); ramstorage.removeItem(priv.namespace + "/" + 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; document_list = []; path_re = new RegExp( "^" + complex_queries.stringEscapeRegexpCharacters(priv.namespace) + "/[^/]+$" ); 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 storage) { if (storage.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 = ramstorage.getItem(i); } rows.push(row); } } } that.success({"rows": rows, "total_rows": rows.length}); } else { // create complex query object from returned results for (i in storage) { if (storage.hasOwnProperty(i)) { if (path_re.test(i)) { document_list.push(ramstorage.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; } jIO.addStorageType('ram', ramStorage); }));