X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fstorage.jsm;h=6cc704f0040c477242a61d3ee7705160621a9961;hb=354a049cce8415487552ce405cce167b7071fe1f;hp=896daf4461189867811231b5c916dba25887f93b;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index 896daf4..6cc704f 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -1,35 +1,21 @@ -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2013 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; -Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("storage", { exports: ["File", "Storage", "storage"], require: ["services", "util"] -}, this); +}); + +lazyRequire("config", ["config"]); +lazyRequire("io", ["IO"]); +lazyRequire("overlay", ["overlay"]); var win32 = /^win(32|nt)$/i.test(services.runtime.OS); var myObject = JSON.parse("{}").constructor; -function loadData(name, store, type) { - try { - let data = storage.infoPath.child(name).read(); - let result = JSON.parse(data); - if (result instanceof type) - return result; - } - catch (e) {} -} - -function saveData(obj) { - if (obj.privateData && storage.privateMode) - return; - if (obj.store && storage.infoPath) - storage.infoPath.child(obj.name).write(obj.serial); -} - var StoreBase = Class("StoreBase", { OPTIONS: ["privateData", "replacer"], @@ -39,16 +25,24 @@ var StoreBase = Class("StoreBase", { init: function (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(); }, - changed: function () { this.timer.tell(); }, + clone: function (storage) { + let store = storage.privateMode ? false : this.store; + let res = this.constructor(this.name, store, this._load, this._options); + res.storage = storage; + return res; + }, + + changed: function () { this.timer && this.timer.tell(); }, reload: function reload() { this._object = this._load() || this._constructor(); @@ -61,7 +55,7 @@ var StoreBase = Class("StoreBase", { storage.infoPath.child(this.name).remove(false); }, - save: function () { saveData(this); }, + save: function () { (self.storage || storage)._saveData(this); }, __iterator__: function () Iterator(this._object) }); @@ -71,10 +65,13 @@ var ArrayStore = Class("ArrayStore", StoreBase, { get length() this._object.length, - set: function set(index, value) { + set: function set(index, value, quiet) { var orig = this._object[index]; this._object[index] = value; - this.fireEvent("change", index); + if (!quiet) + this.fireEvent("change", index); + + return orig; }, push: function push(value) { @@ -82,12 +79,32 @@ var ArrayStore = Class("ArrayStore", StoreBase, { this.fireEvent("push", this._object.length); }, - pop: function pop(value) { - var res = this._object.pop(); - this.fireEvent("pop", this._object.length); + pop: function pop(value, ord) { + if (ord == null) + var res = this._object.pop(); + else + res = this._object.splice(ord, 1)[0]; + + this.fireEvent("pop", this._object.length, ord); + return res; + }, + + shift: function shift(value) { + var res = this._object.shift(); + this.fireEvent("shift", this._object.length); return res; }, + insert: function insert(value, ord) { + if (ord == 0) + this._object.unshift(value); + else + this._object = this._object.slice(0, ord) + .concat([value]) + .concat(this._object.slice(ord)); + this.fireEvent("insert", this._object.length, ord); + }, + truncate: function truncate(length, fromEnd) { var res = this._object.length; if (this._object.length > length) { @@ -145,15 +162,24 @@ var ObjectStore = Class("ObjectStore", StoreBase, { } }); +var sessionGlobal = Cu.import("resource://gre/modules/Services.jsm", {}); + var Storage = Module("Storage", { + Local: function Local(dactyl, modules, window) ({ + init: function init() { + this.privateMode = PrivateBrowsingUtils.isWindowPrivate(window); + } + }), + alwaysReload: {}, - init: function () { + init: function init() { this.cleanup(); - if (services.bootstrap && !services.bootstrap.session) - services.bootstrap.session = {}; - this.session = services.bootstrap ? services.bootstrap.session : {}; + let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + if (!Services.dactylSession) + Services.dactylSession = Cu.createObjectIn(sessionGlobal); + this.session = Services.dactylSession; }, cleanup: function () { @@ -164,28 +190,80 @@ var Storage = Module("Storage", { this[key].timer.flush(); delete this[key]; } - for (let ary in values(this.observers)) - for (let obj in values(ary)) - if (obj.ref && obj.ref.get()) - delete obj.ref.get().dactylStorageRefs; this.keys = {}; this.observers = {}; }, - exists: function exists(name) this.infoPath.child(name).exists(), + _loadData: function loadData(name, store, type) { + try { + let file = storage.infoPath.child(name); + if (file.exists()) { + let data = file.read(); + let result = JSON.parse(data); + if (result instanceof type) + return result; + } + } + catch (e) { + util.reportError(e); + } + }, + + _saveData: function saveData(obj) { + if (obj.privateData && storage.privateMode) + return; + if (obj.store && storage.infoPath) + storage.infoPath.child(obj.name).write(obj.serial); + }, + + storeForSession: function storeForSession(key, val) { + if (val) + this.session[key] = sessionGlobal.JSON.parse(JSON.stringify(val)); + else + delete this.dactylSession[key]; + }, + + infoPath: Class.Memoize(() => + File(IO.runtimePath.replace(/,.*/, "")) + .child("info").child(config.profileName)), + + exists: function exists(key) this.infoPath.child(key).exists(), + + remove: function remove(key) { + if (this.exists(key)) { + if (this[key] && this[key].timer) + this[key].timer.flush(); + delete this[key]; + delete this.keys[key]; + this.infoPath.child(key).remove(false); + } + }, newObject: function newObject(key, constructor, params) { if (params == null || !isObject(params)) throw Error("Invalid argument type"); - if (!(key in this.keys) || params.reload || this.alwaysReload[key]) { - if (key in this && !(params.reload || this.alwaysReload[key])) - throw Error(); - let load = function () loadData(key, params.store, params.type || myObject); + if (this.isLocalModule) { + this.globalInstance.newObject.apply(this.globalInstance, arguments); + + if (!(key in this.keys) && this.privateMode && key in this.globalInstance.keys) { + let obj = this.globalInstance.keys[key]; + this.keys[key] = this._privatize(obj); + } + + return this.keys[key]; + } + + let reload = params.reload || this.alwaysReload[key]; + if (!(key in this.keys) || reload) { + if (key in this && !reload) + throw Error("Cannot add storage key with that name."); + + 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 () storage.save(key)); + this.keys[key].timer = new Timer(1000, 10000, () => this.save(key)); this.__defineGetter__(key, function () this.keys[key]); } return this.keys[key]; @@ -201,33 +279,46 @@ var Storage = Module("Storage", { addObserver: function addObserver(key, callback, ref) { if (ref) { - if (!ref.dactylStorageRefs) - ref.dactylStorageRefs = []; - ref.dactylStorageRefs.push(callback); - var callbackRef = Cu.getWeakReference(callback); + 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 }); + + if (!this.observers[key].some(o => o.callback.get() == callback)) + this.observers[key].push({ ref: ref && Cu.getWeakReference(ref), + callback: callbackRef }); }, removeObserver: function (key, callback) { this.removeDeadObservers(); + if (!(key in this.observers)) return; - this.observers[key] = this.observers[key].filter(function (elem) elem.callback.get() != callback); + + this.observers[key] = this.observers[key].filter(elem => elem.callback.get() != callback); if (this.observers[key].length == 0) delete obsevers[key]; }, 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(function (o) o.callback.get() && (!o.ref || o.ref.get() && o.ref.get().dactylStorageRefs)); + this.observers[key] = ary = ary.filter(filter); if (!ary.length) delete this.observers[key]; } @@ -235,11 +326,13 @@ var Storage = Module("Storage", { 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); - if (key in this.keys) + + if (key in this.keys && this.keys[key].timer) this[key].timer.tell(); }, @@ -250,37 +343,46 @@ var Storage = Module("Storage", { save: function save(key) { if (this[key]) - saveData(this.keys[key]); + this._saveData(this.keys[key]); }, saveAll: function storeAll() { for each (let obj in this.keys) - saveData(obj); + this._saveData(obj); }, _privateMode: false, get privateMode() this._privateMode, - set privateMode(val) { - if (val && !this._privateMode) + set privateMode(enabled) { + this._privateMode = Boolean(enabled); + + if (this.isLocalModule) { this.saveAll(); - if (!val && this._privateMode) - for (let key in this.keys) - this.load(key); - return this._privateMode = Boolean(val); - } + + if (!enabled) + delete this.keys; + else { + let { keys } = this; + this.keys = {}; + for (let [k, v] in Iterator(keys)) + this.keys[k] = this._privatize(v); + } + } + return this._privateMode; + }, + + _privatize: function privatize(obj) { + if (obj.privateData && obj.clone) + return obj.clone(this); + return obj; + }, }, { Replacer: { skipXpcom: function skipXpcom(key, val) val instanceof Ci.nsISupports ? null : val } }, { - init: function init(dactyl, modules) { - init.superapply(this, arguments); - storage.infoPath = File(modules.IO.runtimePath.replace(/,.*/, "")) - .child("info").child(dactyl.profileName); - }, - cleanup: function (dactyl, modules, window) { - delete window.dactylStorageRefs; + overlay.setData(window, "storage-refs", null); this.removeDeadObservers(); } }); @@ -292,12 +394,19 @@ var Storage = Module("Storage", { * @param {nsIFile|string} path Expanded according to {@link IO#expandPath} * @param {boolean} checkPWD Whether to allow expansion relative to the * current directory. @default true + * @param {string} charset The charset of the file. @default File.defaultEncoding */ var File = Class("File", { - init: function (path, checkPWD) { + init: function (path, checkPWD, charset) { let file = services.File(); - if (path instanceof Ci.nsIFile) + if (charset) + this.charset = charset; + + if (path instanceof Ci.nsIFileURL) + path = path.file; + + if (path instanceof Ci.nsIFile || path instanceof File) file = path.clone(); else if (/file:\/\//.test(path)) file = services["file:"].getFileFromURLSpec(path); @@ -315,20 +424,26 @@ var File = Class("File", { return File.DoesNotExist(path, e); } } - let self = XPCSafeJSObjectWrapper(file.QueryInterface(Ci.nsILocalFile)); - self.__proto__ = this; - return self; + this.file = file.QueryInterface(Ci.nsILocalFile); + return this; }, + charset: Class.Memoize(() => File.defaultEncoding), + /** * @property {nsIFileURL} Returns the nsIFileURL object for this file. */ - get URI() services.io.newFileURI(this), + URI: Class.Memoize(function () { + let uri = services.io.newFileURI(this.file) + .QueryInterface(Ci.nsIFileURL); + uri.QueryInterface(Ci.nsIMutable).mutable = false; + return uri; + }), /** * Iterates over the objects in this directory. */ - iterDirectory: function () { + iterDirectory: function iterDirectory() { if (!this.exists()) throw Error(_("io.noSuchFile")); if (!this.isDirectory()) @@ -340,26 +455,32 @@ var File = Class("File", { /** * Returns a new file for the given child of this directory entry. */ - child: function (name) { + child: function child() { let f = this.constructor(this); - for each (let elem in name.split(File.pathSplit)) - f.append(elem); + for (let [, name] in Iterator(arguments)) + for each (let elem in name.split(File.pathSplit)) + f.append(elem); return f; }, + /** + * Returns an iterator for all lines in a file. + */ + get lines() File.readLines(services.FileInStream(this.file, -1, 0, 0), + this.charset), + /** * Reads this file's entire contents in "text" mode and returns the * content as a string. * * @param {string} encoding The encoding from which to decode the file. - * @default options["fileencoding"] + * @default #charset * @returns {string} */ - read: function (encoding) { - let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); - ifstream.init(this, -1, 0, 0); + read: function read(encoding) { + let ifstream = services.FileInStream(this.file, -1, 0, 0); - return File.readStream(ifstream, encoding); + return File.readStream(ifstream, encoding || this.charset); }, /** @@ -369,13 +490,14 @@ var File = Class("File", { * entries. * @returns {[nsIFile]} */ - readDirectory: function (sort) { + readDirectory: function readDirectory(sort) { if (!this.isDirectory()) throw Error(_("io.eNotDir")); 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; }, @@ -384,7 +506,7 @@ var File = Class("File", { * * @returns {nsIFileURL} */ - toURI: function toURI() services.io.newFileURI(this), + toURI: function toURI() services.io.newFileURI(this.file), /** * Writes the string *buf* to this file. @@ -408,20 +530,17 @@ var File = Class("File", { * permissions if the file exists. * @default 0644 * @param {string} encoding The encoding to used to write the file. - * @default options["fileencoding"] + * @default #charset */ - write: function (buf, mode, perms, encoding) { - let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); + write: function write(buf, mode, perms, encoding) { function getStream(defaultChar) { - let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream); - stream.init(ofstream, encoding, 0, defaultChar); - return stream; + return services.ConvOutStream(ofstream, encoding, 0, defaultChar); } if (buf instanceof File) buf = buf.read(); if (!encoding) - encoding = File.defaultEncoding; + encoding = this.charset; if (mode == ">>") mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_APPEND; @@ -433,7 +552,7 @@ var File = Class("File", { if (!this.exists()) // OCREAT won't create the directory this.create(this.NORMAL_FILE_TYPE, perms); - ofstream.init(this, mode, perms, 0); + let ofstream = services.FileOutStream(this.file, mode, perms, 0); try { var ocstream = getStream(0); ocstream.writeString(buf); @@ -452,7 +571,34 @@ var File = Class("File", { ofstream.close(); } return true; - } + }, + + // Wrapped native methods: + copyTo: function copyTo(dir, name) + this.file.copyTo(this.constructor(dir).file, + name), + + copyToFollowingLinks: function copyToFollowingLinks(dir, name) + this.file.copyToFollowingLinks(this.constructor(dir).file, + name), + + moveTo: function moveTo(dir, name) + this.file.moveTo(this.constructor(dir).file, + name), + + equals: function equals(file) + this.file.equals(this.constructor(file).file), + + contains: function contains(dir, recur) + this.file.contains(this.constructor(dir).file, + recur), + + getRelativeDescriptor: function getRelativeDescriptor(file) + this.file.getRelativeDescriptor(this.constructor(file).file), + + setRelativeDescriptor: function setRelativeDescriptor(file, path) + this.file.setRelativeDescriptor(this.constructor(file).file, + path) }, { /** * @property {number} Open for reading only. @@ -510,15 +656,15 @@ var File = Class("File", { /** * @property {string} The current platform's path separator. */ - PATH_SEP: Class.memoize(function () { + PATH_SEP: Class.Memoize(function () { let f = services.directory.get("CurProcD", Ci.nsIFile); f.append("foo"); return f.path.substr(f.parent.path.length, 1); }), - pathSplit: Class.memoize(function () util.regexp("(?:/|" + util.regexp.escape(this.PATH_SEP) + ")", "g")), + pathSplit: Class.Memoize(function () util.regexp("(?:/|" + util.regexp.escape(this.PATH_SEP) + ")", "g")), - DoesNotExist: function (path, error) ({ + DoesNotExist: function DoesNotExist(path, error) ({ path: path, exists: function () false, __noSuchMethod__: function () { throw error || Error("Does not exist"); } @@ -541,7 +687,7 @@ var File = Class("File", { * @param {boolean} relative Whether the path is relative or absolute. * @returns {string} */ - expandPath: function (path, relative) { + expandPath: function expandPath(path, relative) { function getenv(name) services.environment.get(name); // expand any $ENV vars - this is naive but so is Vim and we like to be compatible @@ -549,10 +695,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 ~ @@ -577,11 +722,18 @@ var File = Class("File", { expandPathList: function (list) list.map(this.expandPath), - readStream: function (ifstream, encoding) { + readURL: function readURL(url, encoding) { + let channel = services.io.newChannel(url, null, null); + channel.contentType = "text/plain"; + return this.readStream(channel.open(), encoding); + }, + + readStream: function readStream(ifstream, encoding) { try { - var icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream); - icstream.init(ifstream, encoding || File.defaultEncoding, 4096, // 4096 bytes buffering - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + var icstream = services.CharsetStream( + ifstream, encoding || File.defaultEncoding, 4096, // buffer size + services.CharsetStream.DEFAULT_REPLACEMENT_CHARACTER); + let buffer = []; let str = {}; while (icstream.readString(4096, str) != 0) @@ -594,7 +746,23 @@ var File = Class("File", { } }, - isAbsolutePath: function (path) { + readLines: function readLines(ifstream, encoding) { + try { + var icstream = services.CharsetStream( + ifstream, encoding || File.defaultEncoding, 4096, // buffer size + services.CharsetStream.DEFAULT_REPLACEMENT_CHARACTER); + + var value = {}; + while (icstream.readLine(value)) + yield value.value; + } + finally { + icstream.close(); + ifstream.close(); + } + }, + + isAbsolutePath: function isAbsolutePath(path) { try { services.File().initWithPath(path); return true; @@ -604,7 +772,7 @@ var File = Class("File", { } }, - joinPaths: function (head, tail, cwd) { + joinPaths: function joinPaths(head, tail, cwd) { let path = this(head, cwd); try { // FIXME: should only expand environment vars and normalize path separators @@ -619,8 +787,30 @@ var File = Class("File", { replacePathSep: function (path) path.replace("/", File.PATH_SEP, "g") }); +let (file = services.directory.get("ProfD", Ci.nsIFile)) { + Object.keys(file).forEach(function (prop) { + if (!(prop in File.prototype)) { + let isFunction; + try { + isFunction = callable(file[prop]); + } + catch (e) {} + + if (isFunction) + File.prototype[prop] = util.wrapCallback(function wrapper() this.file[prop].apply(this.file, arguments)); + else + Object.defineProperty(File.prototype, prop, { + configurable: true, + get: function wrap_get() this.file[prop], + set: function wrap_set(val) { this.file[prop] = val; } + }); + } + }); + file = null; +} + endModule(); -// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);} +// 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: