-// 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.
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.__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() {
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)
delete this.dactylSession[key];
},
- infoPath: Class.Memoize(function ()
+ infoPath: Class.Memoize(() =>
File(IO.runtimePath.replace(/,.*/, ""))
.child("info").child(config.profileName)),
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(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();
}
}, {
cleanup: function (dactyl, modules, window) {
- overlay.setData(window, "storage-refs", null);
- this.removeDeadObservers();
+ overlay.setData(window, "storage-callbacks", undefined);
}
});
return this;
},
- charset: Class.Memoize(function () File.defaultEncoding),
+ charset: Class.Memoize(() => File.defaultEncoding),
/**
* @property {nsIFileURL} Returns the nsIFileURL object for this 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;
},
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;
},
// 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 ~