X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fcache.jsm;fp=common%2Fmodules%2Fcache.jsm;h=c24084671c6b1f9dc71af75a93708fec03ad4872;hb=9044153cb63835e39b9de8ec4ade237c03e3888a;hp=0000000000000000000000000000000000000000;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/cache.jsm b/common/modules/cache.jsm new file mode 100644 index 0000000..c240846 --- /dev/null +++ b/common/modules/cache.jsm @@ -0,0 +1,262 @@ +// Copyright (c) 2011 by 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("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: