]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/storage.jsm
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / modules / storage.jsm
index 6cc704f0040c477242a61d3ee7705160621a9961..5ff94561bde8e2ba2e97c259cb2424bad9d7dbbc 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2008-2013 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,7 +27,7 @@ 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;
 
@@ -35,13 +39,21 @@ var StoreBase = Class("StoreBase", {
         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)
@@ -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)
@@ -236,11 +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) {
+    newObject: function newObject(key, constructor, params={}) {
         if (params == null || !isObject(params))
             throw Error("Invalid argument type");
 
@@ -269,68 +293,41 @@ var Storage = Module("Storage", {
         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(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(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);
     }
 });
 
@@ -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;
     },