aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js')
-rw-r--r--tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js263
1 files changed, 263 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js
new file mode 100644
index 0000000..b719e27
--- /dev/null
+++ b/tools/addon-sdk-1.4/packages/addon-kit/lib/simple-storage.js
@@ -0,0 +1,263 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et filetype=javascript
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Drew Willcoxon <adw@mozilla.com> (Original Author)
+ * Irakli Gozalishvili <gozala@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const {Cc,Ci} = require("chrome");
+const file = require("api-utils/file");
+const prefs = require("api-utils/preferences-service");
+const jpSelf = require("self");
+const timer = require("api-utils/timer");
+const unload = require("api-utils/unload");
+const { EventEmitter } = require("api-utils/events");
+const { Trait } = require("api-utils/traits");
+
+const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
+const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+const QUOTA_DEFAULT = 5242880; // 5 MiB
+
+const JETPACK_DIR_BASENAME = "jetpack";
+
+
+// simpleStorage.storage
+exports.__defineGetter__("storage", function () manager.root);
+exports.__defineSetter__("storage", function (val) manager.root = val);
+
+// simpleStorage.quotaUsage
+exports.__defineGetter__("quotaUsage", function () manager.quotaUsage);
+
+// A generic JSON store backed by a file on disk. This should be isolated
+// enough to move to its own module if need be...
+function JsonStore(options) {
+ this.filename = options.filename;
+ this.quota = options.quota;
+ this.writePeriod = options.writePeriod;
+ this.onOverQuota = options.onOverQuota;
+ this.onWrite = options.onWrite;
+
+ unload.ensure(this);
+
+ this.writeTimer = timer.setInterval(this.write.bind(this),
+ this.writePeriod);
+}
+
+JsonStore.prototype = {
+ // The store's root.
+ get root() {
+ return this.isRootInited ? this._root : {};
+ },
+
+ // Performs some type checking.
+ set root(val) {
+ let types = ["array", "boolean", "null", "number", "object", "string"];
+ if (types.indexOf(typeof(val)) < 0) {
+ throw new Error("storage must be one of the following types: " +
+ types.join(", "));
+ }
+ this._root = val;
+ return val;
+ },
+
+ // True if the root has ever been set (either via the root setter or by the
+ // backing file's having been read).
+ get isRootInited() {
+ return this._root !== undefined;
+ },
+
+ // Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
+ // Undefined if there is no quota.
+ get quotaUsage() {
+ return this.quota > 0 ?
+ JSON.stringify(this.root).length / this.quota :
+ undefined;
+ },
+
+ // Removes the backing file and all empty subdirectories.
+ purge: function JsonStore_purge() {
+ try {
+ // This'll throw if the file doesn't exist.
+ file.remove(this.filename);
+ let parentPath = this.filename;
+ do {
+ parentPath = file.dirname(parentPath);
+ // This'll throw if the dir isn't empty.
+ file.rmdir(parentPath);
+ } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
+ }
+ catch (err) {}
+ },
+
+ // Initializes the root by reading the backing file.
+ read: function JsonStore_read() {
+ try {
+ let str = file.read(this.filename);
+
+ // Ideally we'd log the parse error with console.error(), but logged
+ // errors cause tests to fail. Supporting "known" errors in the test
+ // harness appears to be non-trivial. Maybe later.
+ this.root = JSON.parse(str);
+ }
+ catch (err) {
+ this.root = {};
+ }
+ },
+
+ // If the store is under quota, writes the root to the backing file.
+ // Otherwise quota observers are notified and nothing is written.
+ write: function JsonStore_write() {
+ if (this.quotaUsage > 1)
+ this.onOverQuota(this);
+ else
+ this._write();
+ },
+
+ // Cleans up on unload. If unloading because of uninstall, the store is
+ // purged; otherwise it's written.
+ unload: function JsonStore_unload(reason) {
+ timer.clearInterval(this.writeTimer);
+ this.writeTimer = null;
+
+ if (reason === "uninstall")
+ this.purge();
+ else
+ this._write();
+ },
+
+ // True if the root is an empty object.
+ get _isEmpty() {
+ if (this.root && typeof(this.root) === "object") {
+ let empty = true;
+ for (let key in this.root) {
+ empty = false;
+ break;
+ }
+ return empty;
+ }
+ return false;
+ },
+
+ // Writes the root to the backing file, notifying write observers when
+ // complete. If the store is over quota or if it's empty and the store has
+ // never been written, nothing is written and write observers aren't notified.
+ _write: function JsonStore__write() {
+ // Don't write if the root is uninitialized or if the store is empty and the
+ // backing file doesn't yet exist.
+ if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
+ return;
+
+ // If the store is over quota, don't write. The current under-quota state
+ // should persist.
+ if (this.quotaUsage > 1)
+ return;
+
+ // Finally, write.
+ let stream = file.open(this.filename, "w");
+ try {
+ stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
+ if (err)
+ console.error("Error writing simple storage file: " + this.filename);
+ else if (this.onWrite)
+ this.onWrite(this);
+ }.bind(this));
+ }
+ catch (err) {
+ // writeAsync closes the stream after it's done, so only close on error.
+ stream.close();
+ }
+ }
+};
+
+
+// This manages a JsonStore singleton and tailors its use to simple storage.
+// The root of the JsonStore is lazy-loaded: The backing file is only read the
+// first time the root's gotten.
+let manager = Trait.compose(EventEmitter, Trait.compose({
+ jsonStore: null,
+
+ // The filename of the store, based on the profile dir and extension ID.
+ get filename() {
+ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ storeFile.append(JETPACK_DIR_BASENAME);
+ storeFile.append(jpSelf.id);
+ storeFile.append("simple-storage");
+ file.mkpath(storeFile.path);
+ storeFile.append("store.json");
+ return storeFile.path;
+ },
+
+ get quotaUsage() {
+ return this.jsonStore.quotaUsage;
+ },
+
+ get root() {
+ if (!this.jsonStore.isRootInited)
+ this.jsonStore.read();
+ return this.jsonStore.root;
+ },
+
+ set root(val) {
+ return this.jsonStore.root = val;
+ },
+
+ unload: function manager_unload() {
+ this._removeAllListeners("OverQuota");
+ this._removeAllListeners("error");
+ },
+
+ constructor: function manager_constructor() {
+ // Log unhandled errors.
+ this.on("error", console.exception.bind(console));
+ unload.ensure(this);
+
+ this.jsonStore = new JsonStore({
+ filename: this.filename,
+ writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
+ quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
+ onOverQuota: this._emitOnObject.bind(this, exports, "OverQuota")
+ });
+ }
+}))();
+
+exports.on = manager.on;
+exports.removeListener = manager.removeListener;