]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/storage.jsm
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / modules / storage.jsm
index 0f4cc06e7a83a41c0ca3e89dada9b422948c613e..5ff94561bde8e2ba2e97c259cb2424bad9d7dbbc 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2008-2012 Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k at Gmail>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
@@ -6,16 +6,20 @@
 
 defineModule("storage", {
     exports: ["File", "Storage", "storage"],
-    require: ["services", "util"]
+    require: ["promises", "services", "util"]
 });
 
 lazyRequire("config", ["config"]);
 lazyRequire("io", ["IO"]);
 lazyRequire("overlay", ["overlay"]);
 
+lazyRequire("resource://gre/modules/osfile.jsm", ["OS"]);
+
 var win32 = /^win(32|nt)$/i.test(services.runtime.OS);
 var myObject = JSON.parse("{}").constructor;
 
+var global = Cu.getGlobalForObject(this);
+
 var StoreBase = Class("StoreBase", {
     OPTIONS: ["privateData", "replacer"],
 
@@ -23,25 +27,33 @@ var StoreBase = Class("StoreBase", {
 
     get serial() JSON.stringify(this._object, this.replacer),
 
-    init: function (name, store, load, options) {
+    init: function init(name, store, load, options) {
         this._load = load;
         this._options = options;
 
-        this.__defineGetter__("store", function () store);
-        this.__defineGetter__("name", function () name);
+        this.__defineGetter__("store", () => store);
+        this.__defineGetter__("name", () => name);
         for (let [k, v] in Iterator(options))
             if (this.OPTIONS.indexOf(k) >= 0)
                 this[k] = v;
         this.reload();
     },
 
-    clone: function (storage) {
+    clone: function clone(storage) {
         let store = storage.privateMode ? false : this.store;
         let res = this.constructor(this.name, store, this._load, this._options);
         res.storage = storage;
         return res;
     },
 
+    makeOwn: function makeOwn(val) {
+        if (typeof val != "object")
+            return val;
+        if (Cu.getGlobalForObject(val) == global)
+            return val;
+        return JSON.parse(JSON.stringify(val, this.replacer));
+    },
+
     changed: function () { this.timer && this.timer.tell(); },
 
     reload: function reload() {
@@ -52,7 +64,8 @@ var StoreBase = Class("StoreBase", {
     delete: function delete_() {
         delete storage.keys[this.name];
         delete storage[this.name];
-        storage.infoPath.child(this.name).remove(false);
+        return OS.File.remove(
+            storage.infoPath.child(this.name).path);
     },
 
     save: function () { (self.storage || storage)._saveData(this); },
@@ -67,7 +80,7 @@ var ArrayStore = Class("ArrayStore", StoreBase, {
 
     set: function set(index, value, quiet) {
         var orig = this._object[index];
-        this._object[index] = value;
+        this._object[index] = this.makeOwn(value);
         if (!quiet)
             this.fireEvent("change", index);
 
@@ -75,7 +88,7 @@ var ArrayStore = Class("ArrayStore", StoreBase, {
     },
 
     push: function push(value) {
-        this._object.push(value);
+        this._object.push(this.makeOwn(value));
         this.fireEvent("push", this._object.length);
     },
 
@@ -96,6 +109,7 @@ var ArrayStore = Class("ArrayStore", StoreBase, {
     },
 
     insert: function insert(value, ord) {
+        value = this.makeOwn(value);
         if (ord == 0)
             this._object.unshift(value);
         else
@@ -120,7 +134,8 @@ var ArrayStore = Class("ArrayStore", StoreBase, {
     mutate: function mutate(funcName) {
         var _funcName = funcName;
         arguments[0] = this._object;
-        this._object = Array[_funcName].apply(Array, arguments);
+        this._object = Array[_funcName].apply(Array, arguments)
+                                       .map(this.makeOwn.bind(this));
         this.fireEvent("change", null);
     },
 
@@ -136,11 +151,13 @@ var ObjectStore = Class("ObjectStore", StoreBase, {
     },
 
     get: function get(key, default_) {
-        return key in this._object  ? this._object[key] :
+        return this.has(key)        ? this._object[key] :
                arguments.length > 1 ? this.set(key, default_) :
                                       undefined;
     },
 
+    has: function has(key) hasOwnProperty(this._object, key),
+
     keys: function keys() Object.keys(this._object),
 
     remove: function remove(key) {
@@ -153,7 +170,7 @@ var ObjectStore = Class("ObjectStore", StoreBase, {
     set: function set(key, val) {
         var defined = key in this._object;
         var orig = this._object[key];
-        this._object[key] = val;
+        this._object[key] = this.makeOwn(val);
         if (!defined)
             this.fireEvent("add", key);
         else if (orig != val)
@@ -162,7 +179,7 @@ var ObjectStore = Class("ObjectStore", StoreBase, {
     }
 });
 
-var sessionGlobal = Cu.import("resource://gre/modules/Services.jsm", {})
+var sessionGlobal = Cu.import("resource://gre/modules/Services.jsm", {});
 
 var Storage = Module("Storage", {
     Local: function Local(dactyl, modules, window) ({
@@ -210,12 +227,18 @@ var Storage = Module("Storage", {
         }
     },
 
-    _saveData: function saveData(obj) {
+    _saveData: promises.task(function saveData(obj) {
         if (obj.privateData && storage.privateMode)
             return;
-        if (obj.store && storage.infoPath)
-            storage.infoPath.child(obj.name).write(obj.serial);
-    },
+        if (obj.store && storage.infoPath) {
+            var { path } = storage.infoPath.child(obj.name);
+            yield OS.File.makeDir(storage.infoPath.path,
+                                  { ignoreExisting: true });
+            yield OS.File.writeAtomic(
+                path, obj.serial,
+                { tmpPath: path + ".part" });
+        }
+    }),
 
     storeForSession: function storeForSession(key, val) {
         if (val)
@@ -224,7 +247,7 @@ var Storage = Module("Storage", {
             delete this.dactylSession[key];
     },
 
-    infoPath: Class.Memoize(function ()
+    infoPath: Class.Memoize(() =>
         File(IO.runtimePath.replace(/,.*/, ""))
             .child("info").child(config.profileName)),
 
@@ -236,12 +259,12 @@ var Storage = Module("Storage", {
                 this[key].timer.flush();
             delete this[key];
             delete this.keys[key];
-            this.infoPath.child(key).remove(false);
+            return OS.File.remove(
+                this.infoPath.child(key).path);
         }
     },
 
-    newObject: function newObject(key, constructor, params) {
-        let self = this;
+    newObject: function newObject(key, constructor, params={}) {
         if (params == null || !isObject(params))
             throw Error("Invalid argument type");
 
@@ -261,76 +284,50 @@ var Storage = Module("Storage", {
             if (key in this && !reload)
                 throw Error("Cannot add storage key with that name.");
 
-            let load = function () self._loadData(key, params.store, params.type || myObject);
+            let load = () => this._loadData(key, params.store, params.type || myObject);
 
             this.keys[key] = new constructor(key, params.store, load, params);
-            this.keys[key].timer = new Timer(1000, 10000, function () self.save(key));
+            this.keys[key].timer = new Timer(1000, 10000, () => this.save(key));
             this.__defineGetter__(key, function () this.keys[key]);
         }
         return this.keys[key];
     },
 
-    newMap: function newMap(key, options) {
+    newMap: function newMap(key, options={}) {
         return this.newObject(key, ObjectStore, options);
     },
 
-    newArray: function newArray(key, options) {
+    newArray: function newArray(key, options={}) {
         return this.newObject(key, ArrayStore, update({ type: Array }, options));
     },
 
-    addObserver: function addObserver(key, callback, ref) {
-        if (ref) {
-            let refs = overlay.getData(ref, "storage-refs");
-            refs.push(callback);
-            var callbackRef = util.weakReference(callback);
-        }
-        else {
-            callbackRef = { get: function () callback };
-        }
-
-        this.removeDeadObservers();
-
-        if (!(key in this.observers))
-            this.observers[key] = [];
-
-        if (!this.observers[key].some(function (o) o.callback.get() == callback))
-            this.observers[key].push({ ref: ref && Cu.getWeakReference(ref), callback: callbackRef });
+    get observerMaps() {
+        yield this.observers;
+        for (let window of overlay.windows)
+            yield overlay.getData(window, "storage-observers", Object);
     },
 
-    removeObserver: function (key, callback) {
-        this.removeDeadObservers();
+    addObserver: function addObserver(key, callback, window) {
+        var { observers } = this;
+        if (window instanceof Ci.nsIDOMWindow)
+            observers = overlay.getData(window, "storage-observers", Object);
 
-        if (!(key in this.observers))
-            return;
+        if (!hasOwnProperty(observers, key))
+            observers[key] = RealSet();
 
-        this.observers[key] = this.observers[key].filter(function (elem) elem.callback.get() != callback);
-        if (this.observers[key].length == 0)
-            delete obsevers[key];
+        observers[key].add(callback);
     },
 
-    removeDeadObservers: function () {
-        function filter(o) {
-            if (!o.callback.get())
-                return false;
-
-            let ref = o.ref && o.ref.get();
-            return ref && !ref.closed && overlay.getData(ref, "storage-refs", null);
-        }
-
-        for (let [key, ary] in Iterator(this.observers)) {
-            this.observers[key] = ary = ary.filter(filter);
-            if (!ary.length)
-                delete this.observers[key];
-        }
+    removeObserver: function (key, callback) {
+        for (let observers in this.observerMaps)
+            if (key in observers)
+                observers[key].remove(callback);
     },
 
     fireEvent: function fireEvent(key, event, arg) {
-        this.removeDeadObservers();
-
-        if (key in this.observers)
-            // Safe, since we have our own Array object here.
-            for each (let observer in this.observers[key])
-                observer.callback.get()(key, event, arg);
+        for (let observers in this.observerMaps)
+            for (let observer of observers[key] || [])
+                observer(key, event, arg);
 
         if (key in this.keys && this.keys[key].timer)
             this[key].timer.tell();
@@ -382,8 +379,7 @@ var Storage = Module("Storage", {
     }
 }, {
     cleanup: function (dactyl, modules, window) {
-        overlay.setData(window, "storage-refs", null);
-        this.removeDeadObservers();
+        overlay.setData(window, "storage-callbacks", undefined);
     }
 });
 
@@ -428,7 +424,7 @@ var File = Class("File", {
         return this;
     },
 
-    charset: Class.Memoize(function () File.defaultEncoding),
+    charset: Class.Memoize(() => File.defaultEncoding),
 
     /**
      * @property {nsIFileURL} Returns the nsIFileURL object for this file.
@@ -458,7 +454,7 @@ var File = Class("File", {
     child: function child() {
         let f = this.constructor(this);
         for (let [, name] in Iterator(arguments))
-            for each (let elem in name.split(File.pathSplit))
+            for (let elem of name.split(File.pathSplit))
                 f.append(elem);
         return f;
     },
@@ -496,7 +492,8 @@ var File = Class("File", {
 
         let array = [e for (e in this.iterDirectory())];
         if (sort)
-            array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
+            array.sort((a, b) => (b.isDirectory() - a.isDirectory() ||
+                                  String.localeCompare(a.path, b.path)));
         return array;
     },
 
@@ -694,10 +691,9 @@ var File = Class("File", {
         // Kris reckons we shouldn't replicate this 'bug'. --djk
         // TODO: should we be doing this for all paths?
         function expand(path) path.replace(
-            !win32 ? /\$(\w+)\b|\${(\w+)}/g
-                   : /\$(\w+)\b|\${(\w+)}|%(\w+)%/g,
-            function (m, n1, n2, n3) getenv(n1 || n2 || n3) || m
-        );
+            win32 ? /\$(\w+)\b|\${(\w+)}|%(\w+)%/g
+                  : /\$(\w+)\b|\${(\w+)}/g,
+            (m, n1, n2, n3) => (getenv(n1 || n2 || n3) || m));
         path = expand(path);
 
         // expand ~
@@ -762,7 +758,6 @@ var File = Class("File", {
         }
     },
 
-
     isAbsolutePath: function isAbsolutePath(path) {
         try {
             services.File().initWithPath(path);
@@ -793,7 +788,7 @@ let (file = services.directory.get("ProfD", Ci.nsIFile)) {
         if (!(prop in File.prototype)) {
             let isFunction;
             try {
-                isFunction = callable(file[prop])
+                isFunction = callable(file[prop]);
             }
             catch (e) {}
 
@@ -814,4 +809,4 @@ endModule();
 
 // catch(e){ dump(e + "\n" + (e.stack || Error().stack)); Components.utils.reportError(e) }
 
-// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: