-// 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.
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"],
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.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() {
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); },
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);
},
push: function push(value) {
- this._object.push(value);
+ this._object.push(this.makeOwn(value));
this.fireEvent("push", this._object.length);
},
},
insert: function insert(value, ord) {
+ value = this.makeOwn(value);
if (ord == 0)
this._object.unshift(value);
else
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);
},
},
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) {
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)
}
},
- _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)
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");
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();
}
}, {
cleanup: function (dactyl, modules, window) {
- overlay.setData(window, "storage-refs", null);
- this.removeDeadObservers();
+ overlay.setData(window, "storage-callbacks", undefined);
}
});
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;
},