]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/cache.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / cache.jsm
diff --git a/common/modules/cache.jsm b/common/modules/cache.jsm
new file mode 100644 (file)
index 0000000..c240846
--- /dev/null
@@ -0,0 +1,262 @@
+// Copyright (c) 2011 by Kris Maglione <maglione.k@gmail.com>
+//
+// 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("cache", {
+    exports: ["Cache", "cache"],
+    require: ["config", "services", "util"]
+}, this);
+
+var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
+    init: function init() {
+        this.queue = [];
+        this.cache = {};
+        this.providers = {};
+        this.globalProviders = this.providers;
+        this.providing = {};
+        this.localProviders = {};
+
+        if (JSMLoader.cacheFlush)
+            this.flush();
+
+        update(services["dactyl:"].providers, {
+            "cache": function (uri, path) {
+                let contentType = "text/plain";
+                try {
+                    contentType = services.mime.getTypeFromURI(uri)
+                }
+                catch (e) {}
+
+                if (!cache.cacheReader || !cache.cacheReader.hasEntry(path))
+                    return [contentType, cache.force(path)];
+
+                let channel = services.StreamChannel(uri);
+                channel.contentStream = cache.cacheReader.getInputStream(path);
+                channel.contentType = contentType;
+                channel.contentCharset = "UTF-8";
+                return channel;
+            }
+        });
+    },
+
+    Local: function Local(dactyl, modules, window) ({
+        init: function init() {
+            delete this.instance;
+            this.providers = {};
+        },
+
+        isLocal: true
+    }),
+
+    parse: function parse(str) {
+        if (~'{['.indexOf(str[0]))
+            return JSON.parse(str);
+        return str;
+    },
+
+    stringify: function stringify(obj) {
+        if (isString(obj))
+            return obj;
+        return JSON.stringify(obj);
+    },
+
+    compression: 9,
+
+    cacheFile: Class.Memoize(function () {
+        let dir = File(services.directory.get("ProfD", Ci.nsIFile))
+                    .child("dactyl");
+        if (!dir.exists())
+            dir.create(dir.DIRECTORY_TYPE, octal(777));
+        return dir.child("cache.zip");
+    }),
+
+    get cacheReader() {
+        if (!this._cacheReader && this.cacheFile.exists()
+                && !this.inQueue)
+            try {
+                this._cacheReader = services.ZipReader(this.cacheFile);
+            }
+            catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+                util.reportError(e);
+                this.closeWriter();
+                this.cacheFile.remove(false);
+            }
+
+        return this._cacheReader;
+    },
+
+    get inQueue() this._cacheWriter && this._cacheWriter.inQueue,
+
+    getCacheWriter: function () {
+        if (!this._cacheWriter)
+            try {
+                let mode = File.MODE_RDWR;
+                if (!this.cacheFile.exists())
+                    mode |= File.MODE_CREATE;
+
+                cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
+            }
+            catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+                util.reportError(e);
+                this.cacheFile.remove(false);
+
+                mode |= File.MODE_CREATE;
+                cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
+            }
+        return this._cacheWriter;
+    },
+
+    closeReader: function closeReader() {
+        if (cache._cacheReader) {
+            this.cacheReader.close();
+            delete cache._cacheReader;
+        }
+    },
+
+    closeWriter: function closeWriter() {
+        this.closeReader();
+
+        if (this._cacheWriter) {
+            this._cacheWriter.close();
+            delete cache._cacheWriter;
+
+            // ZipWriter bug.
+            if (this.cacheFile.fileSize <= 22)
+                this.cacheFile.remove(false);
+        }
+    },
+
+    flush: function flush() {
+        cache.cache = {};
+        if (this.cacheFile.exists()) {
+            this.closeReader();
+
+            this.flushJAR(this.cacheFile);
+            this.cacheFile.remove(false);
+        }
+    },
+
+    flushAll: function flushAll(file) {
+        this.flushStartup();
+        this.flush();
+    },
+
+    flushEntry: function flushEntry(name, time) {
+        if (this.cacheReader && this.cacheReader.hasEntry(name)) {
+            if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
+                return;
+
+            this.queue.push([null, name]);
+            cache.processQueue();
+        }
+
+        delete this.cache[name];
+    },
+
+    flushJAR: function flushJAR(file) {
+        services.observer.notifyObservers(file, "flush-cache-entry", "");
+    },
+
+    flushStartup: function flushStartup() {
+        services.observer.notifyObservers(null, "startupcache-invalidate", "");
+    },
+
+    force: function force(name, localOnly) {
+        util.waitFor(function () !this.inQueue, this);
+
+        if (this.cacheReader && this.cacheReader.hasEntry(name)) {
+            return this.parse(File.readStream(
+                this.cacheReader.getInputStream(name)));
+        }
+
+        if (Set.has(this.localProviders, name) && !this.isLocal) {
+            for each (let { cache } in overlay.modules)
+                if (cache._has(name))
+                    return cache.force(name, true);
+        }
+
+        if (Set.has(this.providers, name)) {
+            util.assert(!Set.add(this.providing, name),
+                        "Already generating cache for " + name,
+                        false);
+            try {
+                let [func, self] = this.providers[name];
+                this.cache[name] = func.call(self || this, name);
+            }
+            finally {
+                delete this.providing[name];
+            }
+
+            cache.queue.push([Date.now(), name]);
+            cache.processQueue();
+
+            return this.cache[name];
+        }
+
+        if (this.isLocal && !localOnly)
+            return cache.force(name);
+    },
+
+    get: function get(name) {
+        if (!Set.has(this.cache, name)) {
+            this.cache[name] = this.force(name);
+            util.assert(this.cache[name] !== undefined,
+                        "No such cache key", false);
+        }
+
+        return this.cache[name];
+    },
+
+    _has: function _has(name) Set.has(this.providers, name) || set.has(this.cache, name),
+
+    has: function has(name) [this.globalProviders, this.cache, this.localProviders]
+            .some(function (obj) Set.has(obj, name)),
+
+    register: function register(name, callback, self) {
+        if (this.isLocal)
+            Set.add(this.localProviders, name);
+
+        this.providers[name] = [callback, self];
+    },
+
+    processQueue: function processQueue() {
+        this.closeReader();
+        this.closeWriter();
+
+        if (this.queue.length && !this.inQueue) {
+            // removeEntry does not work properly with queues.
+            for each (let [, entry] in this.queue)
+                if (this.getCacheWriter().hasEntry(entry)) {
+                    this.getCacheWriter().removeEntry(entry, false);
+                    this.closeWriter();
+                }
+
+            this.queue.splice(0).forEach(function ([time, entry]) {
+                if (time && Set.has(this.cache, entry)) {
+                    let stream = services.CharsetConv("UTF-8")
+                                         .convertToInputStream(this.stringify(this.cache[entry]));
+
+                    this.getCacheWriter().addEntryStream(entry, time * 1000,
+                                                         this.compression, stream,
+                                                         true);
+                }
+            }, this);
+
+            if (this._cacheWriter)
+                this.getCacheWriter().processQueue(this, null);
+        }
+    },
+
+    onStopRequest: function onStopRequest() {
+        this.processQueue();
+    }
+});
+
+endModule();
+
+// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+
+// vim: set fdm=marker sw=4 sts=4 et ft=javascript: