From 2ee114f169cb7632ce4cd3b70401b67f504699a8 Mon Sep 17 00:00:00 2001
From: Tristan Cavelier <tristan.cavelier@tiolive.com>
Date: Wed, 28 Aug 2013 18:28:30 +0200
Subject: [PATCH] localstorage updated to new jio design

---
 src/jio.storage/localstorage.js | 711 ++++++++++++++++----------------
 1 file changed, 356 insertions(+), 355 deletions(-)

diff --git a/src/jio.storage/localstorage.js b/src/jio.storage/localstorage.js
index 700aad5..24c59bc 100644
--- a/src/jio.storage/localstorage.js
+++ b/src/jio.storage/localstorage.js
@@ -5,7 +5,8 @@
  */
 
 /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
-/*global jIO, localStorage, setTimeout, complex_queries, define */
+/*global jIO, localStorage, setTimeout, complex_queries, window, define,
+  exports, require */
 
 /**
  * JIO Local Storage. Type = 'local'.
@@ -52,33 +53,17 @@
   if (typeof define === 'function' && define.amd) {
     return define(dependencies, module);
   }
-  module(jIO, complex_queries);
-}(['jio', 'complex_queries'], function (jIO, complex_queries) {
-  "use strict";
-
-  /**
-   * 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();
+  if (typeof exports === 'object') {
+    return module(exports, require('jio'), require('complex_queries'));
   }
+  window.local_storage = {};
+  module(window.local_storage, jIO, complex_queries);
+}([
+  'exports',
+  'jio',
+  'complex_queries'
+], function (exports, jIO, complex_queries) {
+  "use strict";
 
   /**
    * Checks if an object has no enumerable keys
@@ -132,363 +117,379 @@
     }
   };
 
-  jIO.addStorageType('local', function (spec, my) {
-
-    spec = spec || {};
-    var that, priv;
-    that = my.basicStorage(spec, my);
-    priv = {};
-
-    // attributes
-    priv.username = spec.username || '';
-    priv.application_name = spec.application_name || 'untitled';
-
-    priv.localpath = 'jio/localstorage/' + priv.username + '/' +
-      priv.application_name;
-
+  function LocalStorage(spec) {
+    if (typeof spec.username !== 'string' && !spec.username) {
+      throw new TypeError("LocalStorage 'username' must be a string " +
+                          "which contains more than one character.");
+    }
+    this._localpath = 'jio/localstorage/' + spec.username + '/' + (
+      spec.application_name === null || spec.application_name ===
+        undefined ? 'untitled' : spec.application_name.toString()
+    );
     switch (spec.mode) {
     case "memory":
-      priv.database = ram;
-      priv.storage = memorystorage;
-      priv.mode = "memory";
+      this._database = ram;
+      this._storage = memorystorage;
+      this._mode = "memory";
       break;
     default:
-      priv.database = localStorage;
-      priv.storage = localstorage;
-      priv.mode = "localStorage";
+      this._database = localStorage;
+      this._storage = localstorage;
+      this._mode = "localStorage";
       break;
     }
+  }
 
 
-    // ===================== overrides ======================
-    that.specToStore = function () {
-      return {
-        "application_name": priv.application_name,
-        "username": priv.username,
-        "mode": priv.mode
-      };
-    };
+  /**
+   * Create a document in local storage.
+   *
+   * @method post
+   * @param  {Object} command The JIO command
+   * @param  {Object} metadata The metadata to store
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.post = function (command, metadata) {
+    var doc, doc_id = metadata._id;
+    if (!doc_id) {
+      doc_id = jIO.util.generateUuid();
+    }
+    doc = this._storage.getItem(this._localpath + "/" + doc_id);
+    if (doc === null) {
+      // the document does not exist
+      doc = jIO.util.deepClone(metadata);
+      doc._id = doc_id;
+      delete doc._attachments;
+      this._storage.setItem(this._localpath + "/" + doc_id, doc);
+      command.success({"id": doc_id});
+    } else {
+      // the document already exists
+      command.error(
+        "conflict",
+        "document exists",
+        "Cannot create a new document"
+      );
+    }
+  };
 
-    that.validateState = function () {
-      if (typeof priv.username === "string" && priv.username !== '') {
-        return '';
-      }
-      return 'Need at least one parameter: "username".';
-    };
+  /**
+   * Create or update a document in local storage.
+   *
+   * @method put
+   * @param  {Object} command The JIO command
+   * @param  {Object} metadata The metadata to store
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.put = function (command, metadata) {
+    var doc, tmp;
+    doc = this._storage.getItem(this._localpath + "/" + metadata._id);
+    if (doc === null) {
+      //  the document does not exist
+      doc = jIO.util.deepClone(metadata);
+      delete doc._attachments;
+    } else {
+      // the document already exists
+      tmp = jIO.util.deepClone(metadata);
+      tmp._attachments = doc._attachments;
+      doc = tmp;
+    }
+    // write
+    this._storage.setItem(this._localpath + "/" + metadata._id, doc);
+    command.success();
+  };
 
-    // ==================== 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 = priv.storage.getItem(priv.localpath + "/" + doc_id);
-        if (doc === null) {
-          // the document does not exist
-          doc = command.cloneDoc();
-          doc._id = doc_id;
-          delete doc._attachments;
-          priv.storage.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"
-          });
-        }
-      });
-    };
+  /**
+   * Add an attachment to a document
+   *
+   * @method putAttachment
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.putAttachment = function (command, param) {
+    var that = this, doc;
+    doc = this._storage.getItem(this._localpath + "/" + param._id);
+    if (doc === null) {
+      //  the document does not exist
+      return command.error(
+        "not_found",
+        "missing",
+        "Impossible to add attachment"
+      );
+    }
 
-    /**
-     * 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 = priv.storage.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
-        priv.storage.setItem(priv.localpath + "/" + command.getDocId(), doc);
-        that.success({
-          "ok": true,
-          "id": command.getDocId()
-        });
-      });
-    };
+    // the document already exists
+    // download data
+    jIO.util.blobAsBinaryString(param._blob).then(function (data) {
+      doc._attachments = doc._attachments || {};
+      doc._attachments[param._attachment] = {
+        "content_type": param._blob.type,
+        "digest": jIO.util.makeBinaryStringDigest(data),
+        "length": param._blob.size
+      };
 
-    /**
-     * Add an attachment to a document
-     * @method  putAttachment
-     * @param  {object} command The JIO command
-     */
-    that.putAttachment = function (command) {
-      setTimeout(function () {
-        var doc;
-        doc = priv.storage.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;
-        }
+      that._storage.setItem(that._localpath + "/" + param._id + "/" +
+                            param._attachment, data);
+      that._storage.setItem(that._localpath + "/" + param._id, doc);
+      command.success({"hash": doc._attachments[param._attachment].digest});
+    }, function () {
+      command.error(
+        "request_timeout",
+        "blob error",
+        "Unable to download blob content"
+      );
+    }, function () {
+      command.notify(50); // XXX get percentage
+    });
+  };
 
-        // the document already exists
-        doc._attachments = doc._attachments || {};
-        doc._attachments[command.getAttachmentId()] = {
-          "content_type": command.getAttachmentMimeType(),
-          "digest": "md5-" + command.md5SumAttachmentData(),
-          "length": command.getAttachmentLength()
-        };
+  /**
+   * Get a document
+   *
+   * @method get
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.get = function (command, param) {
+    var doc = this._storage.getItem(
+      this._localpath + "/" + param._id
+    );
+    if (doc !== null) {
+      command.success({"data": doc});
+    } else {
+      command.error(
+        "not_found",
+        "missing",
+        "Cannot find document"
+      );
+    }
+  };
 
-        // upload data
-        priv.storage.setItem(priv.localpath + "/" + command.getDocId() + "/" +
-                             command.getAttachmentId(),
-                             command.getAttachmentData());
-        // write document
-        priv.storage.setItem(priv.localpath + "/" + command.getDocId(), doc);
-        that.success({
-          "ok": true,
-          "id": command.getDocId(),
-          "attachment": command.getAttachmentId()
-        });
-      });
-    };
+  /**
+   * Get an attachment
+   *
+   * @method getAttachment
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.getAttachment = function (command, param) {
+    var doc;
+    doc = this._storage.getItem(this._localpath + "/" + param._id);
+    if (doc === null) {
+      return command.error(
+        "not_found",
+        "missing document",
+        "Cannot find document"
+      );
+    }
 
-    /**
-     * Get a document
-     * @method get
-     * @param  {object} command The JIO command
-     */
-    that.get = function (command) {
-      setTimeout(function () {
-        var doc = priv.storage.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"
-          });
-        }
-      });
-    };
+    if (typeof doc._attachments !== 'object' ||
+        typeof doc._attachments[param._attachment] !== 'object') {
+      return command.error(
+        "not_found",
+        "missing attachment",
+        "Cannot find attachment"
+      );
+    }
 
-    /**
-     * Get a attachment
-     * @method getAttachment
-     * @param  {object} command The JIO command
-     */
-    that.getAttachment = function (command) {
-      setTimeout(function () {
-        var doc = priv.storage.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"
-          });
-        }
-      });
-    };
+    command.success({
+      "data": this._storage.getItem(
+        this._localpath + "/" + param._id +
+          "/" + param._attachment
+      ) || "",
+      "content_type": doc._attachments[param._attachment].content_type || ""
+    });
+  };
 
-    /**
-     * Remove a document
-     * @method remove
-     * @param  {object} command The JIO command
-     */
-    that.remove = function (command) {
-      setTimeout(function () {
-        var doc, i, attachment_list;
-        doc = priv.storage.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);
-              }
-            }
+  /**
+   * Remove a document
+   *
+   * @method remove
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.remove = function (command, param) {
+    var doc, i, attachment_list;
+    doc = this._storage.getItem(this._localpath + "/" + param._id);
+    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"
-          });
         }
-        priv.storage.removeItem(priv.localpath + "/" + command.getDocId());
-        // delete all attachments
-        for (i = 0; i < attachment_list.length; i += 1) {
-          priv.storage.removeItem(priv.localpath + "/" + command.getDocId() +
-                                  "/" + attachment_list[i]);
-        }
-        that.success({
-          "ok": true,
-          "id": command.getDocId()
-        });
-      });
-    };
+      }
+    } else {
+      return command.error(
+        "not_found",
+        "missing",
+        "Document not found"
+      );
+    }
+    this._storage.removeItem(this._localpath + "/" + param._id);
+    // delete all attachments
+    for (i = 0; i < attachment_list.length; i += 1) {
+      this._storage.removeItem(this._localpath + "/" + param._id +
+                               "/" + attachment_list[i]);
+    }
+    command.success();
+  };
 
-    /**
-     * Remove an attachment
-     * @method removeAttachment
-     * @param  {object} command The JIO command
-     */
-    that.removeAttachment = function (command) {
-      setTimeout(function () {
-        var doc, error;
-        error = function (word) {
-          that.error({
-            "status": 404,
-            "statusText": "Not Found",
-            "error": "not_found",
-            "message": word + " not found",
-            "reason": "missing"
-          });
-        };
-        doc = priv.storage.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 (objectIsEmpty(doc._attachments)) {
-              delete doc._attachments;
-            }
-            priv.storage.setItem(priv.localpath + "/" + command.getDocId(),
-                                 doc);
-            priv.storage.removeItem(priv.localpath + "/" + command.getDocId() +
-                                    "/" + command.getAttachmentId());
-            that.success({
-              "ok": true,
-              "id": command.getDocId(),
-              "attachment": command.getAttachmentId()
-            });
-          } else {
-            error("Attachment");
-          }
-        } else {
-          error("Document");
-        }
-      });
-    };
+  /**
+   * Remove an attachment
+   *
+   * @method removeAttachment
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.removeAttachment = function (command, param) {
+    var doc = this._storage.getItem(this._localpath + "/" + param._id);
+    if (typeof doc !== 'object') {
+      return command.error(
+        "not_found",
+        "missing document",
+        "Document not found"
+      );
+    }
+    if (typeof doc._attachments !== "object" ||
+        typeof doc._attachments[param._attachment] !== "object") {
+      return command.error(
+        "not_found",
+        "missing attachment",
+        "Attachment not found"
+      );
+    }
 
-    /**
-     * 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;
+    delete doc._attachments[param._attachment];
+    if (objectIsEmpty(doc._attachments)) {
+      delete doc._attachments;
+    }
+    this._storage.setItem(this._localpath + "/" + param._id, doc);
+    this._storage.removeItem(this._localpath + "/" + param._id +
+                             "/" + param._attachment);
+    command.success();
+  };
+
+  /**
+   * Get all filenames belonging to a user from the document index
+   *
+   * @method allDocs
+   * @param  {Object} command The JIO command
+   * @param  {Object} param The given parameters
+   * @param  {Object} options The command options
+   */
+  LocalStorage.prototype.allDocs = function (command, param, options) {
+    var i, row, path_re, rows, document_list, document_object;
+    rows = [];
+    document_list = [];
+    path_re = new RegExp(
+      "^" + complex_queries.stringEscapeRegexpCharacters(this._localpath) +
+        "/[^/]+$"
+    );
+    if (options.query === undefined && options.sort_on === undefined &&
+        options.select_list === undefined &&
+        options.include_docs === undefined) {
       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 priv.database) {
-          if (priv.database.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(priv.storage.getItem(i));
-              }
-              rows.push(row);
+      for (i in this._database) {
+        if (this._database.hasOwnProperty(i)) {
+          // filter non-documents
+          if (path_re.test(i)) {
+            row = { value: {} };
+            row.id = i.split('/').slice(-1)[0];
+            row.key = row.id;
+            if (options.include_docs) {
+              row.doc = JSON.parse(this._storage.getItem(i));
             }
+            rows.push(row);
           }
         }
-        that.success({"rows": rows, "total_rows": rows.length});
-      } else {
-        // create complex query object from returned results
-        for (i in priv.database) {
-          if (priv.database.hasOwnProperty(i)) {
-            if (path_re.test(i)) {
-              document_list.push(priv.storage.getItem(i));
-            }
+      }
+      command.success({"data": {"rows": rows, "total_rows": rows.length}});
+    } else {
+      // create complex query object from returned results
+      for (i in this._database) {
+        if (this._database.hasOwnProperty(i)) {
+          if (path_re.test(i)) {
+            document_list.push(this._storage.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;
+      }
+      options.select_list = options.select_list || [];
+      options.select_list.push("_id");
+      if (options.include_docs === true) {
+        document_object = {};
+        document_list.forEach(function (meta) {
+          document_object[meta._id] = meta;
         });
-        that.success({"total_rows": document_list.length,
-                      "rows": document_list});
       }
+      complex_queries.QueryFactory.create(options.query || "").
+        exec(document_list, options);
+      document_list = document_list.map(function (value) {
+        var o = {
+          "id": value._id,
+          "key": value._id
+        };
+        if (options.include_docs === true) {
+          o.doc = document_object[value._id];
+          delete document_object[value._id];
+        }
+        delete value._id;
+        o.value = value;
+        return o;
+      });
+      command.success({"data": {
+        "total_rows": document_list.length,
+        "rows": document_list
+      }});
+    }
+  };
+
+  jIO.addStorage('local', LocalStorage);
+
+  //////////////////////////////////////////////////////////////////////
+  // Tools
+
+  /**
+   * Tool to help users to create local storage description for JIO
+   *
+   * @param  {String} username The username
+   * @param  {String} [application_name] The application_name
+   * @return {Object} The storage description
+   */
+  function createDescription(username, application_name) {
+    var description = {
+      "type": "local",
+      "username": username.toString()
     };
+    if (application_name !== undefined) {
+      description.application_name = application_name.toString();
+    }
+    return description;
+  }
+  exports.createDescription = createDescription;
+
+  function clear() {
+    var k;
+    for (k in localStorage) {
+      if (localStorage.hasOwnProperty(k)) {
+        if (/^jio\/localstorage\//.test(k)) {
+          localStorage.removeItem(k);
+        }
+      }
+    }
+  }
+  exports.clear = clear;
+  exports.clearLocalStorage = clear;
+
+  function clearMemoryStorage() {
+    jIO.util.dictClear(ram);
+  }
+  exports.clearMemoryStorage = clearMemoryStorage;
 
-    return that;
-  });
 }));
-- 
2.30.9