]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/storage.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / storage.jsm
index 896daf4461189867811231b5c916dba25887f93b..664124e883d6df3cb22d7ba826f49a09c0d19322 100644 (file)
@@ -2,7 +2,7 @@
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
 
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("storage", {
@@ -10,17 +10,25 @@ defineModule("storage", {
     require: ["services", "util"]
 }, this);
 
+this.lazyRequire("config", ["config"]);
+this.lazyRequire("io", ["IO"]);
+
 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;
+        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);
     }
-    catch (e) {}
 }
 
 function saveData(obj) {
@@ -71,10 +79,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 +93,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) {
@@ -164,16 +195,26 @@ 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(),
+    infoPath: Class.Memoize(function ()
+        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))
@@ -201,10 +242,9 @@ 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 };
@@ -227,7 +267,9 @@ var Storage = Module("Storage", {
 
     removeDeadObservers: function () {
         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(function (o) o.callback.get()
+                                                             && (!o.ref || o.ref.get()
+                                                                        && overlay.getData(o.ref.get(), "storage-refs", null)));
             if (!ary.length)
                 delete this.observers[key];
         }
@@ -273,14 +315,8 @@ var Storage = Module("Storage", {
         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,11 +328,18 @@ 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 (charset)
+            this.charset = charset;
+
+        if (path instanceof Ci.nsIFileURL)
+            path = path.file;
+
         if (path instanceof Ci.nsIFile)
             file = path.clone();
         else if (/file:\/\//.test(path))
@@ -320,10 +363,16 @@ var File = Class("File", {
         return self;
     },
 
+    charset: Class.Memoize(function () 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).QueryInterface(Ci.nsIFileURL);
+        uri.QueryInterface(Ci.nsIMutable).mutable = false;
+        return uri;
+    }),
 
     /**
      * Iterates over the objects in this directory.
@@ -347,19 +396,24 @@ var File = Class("File", {
         return f;
     },
 
+    /**
+     * Returns an iterator for all lines in a file.
+     */
+    get lines() File.readLines(services.FileInStream(this, -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);
+        let ifstream = services.FileInStream(this, -1, 0, 0);
 
-        return File.readStream(ifstream, encoding);
+        return File.readStream(ifstream, encoding || this.charset);
     },
 
     /**
@@ -408,20 +462,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);
         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 +484,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, mode, perms, 0);
         try {
             var ocstream = getStream(0);
             ocstream.writeString(buf);
@@ -510,15 +561,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 +592,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
@@ -577,11 +628,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 +652,24 @@ 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 +679,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
@@ -621,6 +696,6 @@ var File = Class("File", {
 
 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: