1 // Copyright (c) 2011 by Kris Maglione <maglione.k@gmail.com>
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
7 Components.utils.import("resource://dactyl/bootstrap.jsm");
8 defineModule("cache", {
9 exports: ["Cache", "cache"],
10 require: ["config", "services", "util"]
13 var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
14 init: function init() {
18 this.globalProviders = this.providers;
20 this.localProviders = {};
22 if (JSMLoader.cacheFlush)
25 update(services["dactyl:"].providers, {
26 "cache": function (uri, path) {
27 let contentType = "text/plain";
29 contentType = services.mime.getTypeFromURI(uri)
33 if (!cache.cacheReader || !cache.cacheReader.hasEntry(path))
34 return [contentType, cache.force(path)];
36 let channel = services.StreamChannel(uri);
37 channel.contentStream = cache.cacheReader.getInputStream(path);
38 channel.contentType = contentType;
39 channel.contentCharset = "UTF-8";
45 Local: function Local(dactyl, modules, window) ({
46 init: function init() {
54 parse: function parse(str) {
55 if (~'{['.indexOf(str[0]))
56 return JSON.parse(str);
60 stringify: function stringify(obj) {
63 return JSON.stringify(obj);
68 cacheFile: Class.Memoize(function () {
69 let dir = File(services.directory.get("ProfD", Ci.nsIFile))
72 dir.create(dir.DIRECTORY_TYPE, octal(777));
73 return dir.child("cache.zip");
77 if (!this._cacheReader && this.cacheFile.exists()
80 this._cacheReader = services.ZipReader(this.cacheFile);
82 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
85 this.cacheFile.remove(false);
88 return this._cacheReader;
91 get inQueue() this._cacheWriter && this._cacheWriter.inQueue,
93 getCacheWriter: function () {
94 if (!this._cacheWriter)
96 let mode = File.MODE_RDWR;
97 if (!this.cacheFile.exists())
98 mode |= File.MODE_CREATE;
100 cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
102 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
104 this.cacheFile.remove(false);
106 mode |= File.MODE_CREATE;
107 cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
109 return this._cacheWriter;
112 closeReader: function closeReader() {
113 if (cache._cacheReader) {
114 this.cacheReader.close();
115 delete cache._cacheReader;
119 closeWriter: function closeWriter() {
122 if (this._cacheWriter) {
123 this._cacheWriter.close();
124 delete cache._cacheWriter;
127 if (this.cacheFile.fileSize <= 22)
128 this.cacheFile.remove(false);
132 flush: function flush() {
134 if (this.cacheFile.exists()) {
137 this.flushJAR(this.cacheFile);
138 this.cacheFile.remove(false);
142 flushAll: function flushAll(file) {
147 flushEntry: function flushEntry(name, time) {
148 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
149 if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
152 this.queue.push([null, name]);
153 cache.processQueue();
156 delete this.cache[name];
159 flushJAR: function flushJAR(file) {
160 services.observer.notifyObservers(file, "flush-cache-entry", "");
163 flushStartup: function flushStartup() {
164 services.observer.notifyObservers(null, "startupcache-invalidate", "");
167 force: function force(name, localOnly) {
168 util.waitFor(function () !this.inQueue, this);
170 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
171 return this.parse(File.readStream(
172 this.cacheReader.getInputStream(name)));
175 if (Set.has(this.localProviders, name) && !this.isLocal) {
176 for each (let { cache } in overlay.modules)
177 if (cache._has(name))
178 return cache.force(name, true);
181 if (Set.has(this.providers, name)) {
182 util.assert(!Set.add(this.providing, name),
183 "Already generating cache for " + name,
186 let [func, self] = this.providers[name];
187 this.cache[name] = func.call(self || this, name);
190 delete this.providing[name];
193 cache.queue.push([Date.now(), name]);
194 cache.processQueue();
196 return this.cache[name];
199 if (this.isLocal && !localOnly)
200 return cache.force(name);
203 get: function get(name) {
204 if (!Set.has(this.cache, name)) {
205 this.cache[name] = this.force(name);
206 util.assert(this.cache[name] !== undefined,
207 "No such cache key", false);
210 return this.cache[name];
213 _has: function _has(name) Set.has(this.providers, name) || set.has(this.cache, name),
215 has: function has(name) [this.globalProviders, this.cache, this.localProviders]
216 .some(function (obj) Set.has(obj, name)),
218 register: function register(name, callback, self) {
220 Set.add(this.localProviders, name);
222 this.providers[name] = [callback, self];
225 processQueue: function processQueue() {
229 if (this.queue.length && !this.inQueue) {
230 // removeEntry does not work properly with queues.
231 for each (let [, entry] in this.queue)
232 if (this.getCacheWriter().hasEntry(entry)) {
233 this.getCacheWriter().removeEntry(entry, false);
237 this.queue.splice(0).forEach(function ([time, entry]) {
238 if (time && Set.has(this.cache, entry)) {
239 let stream = services.CharsetConv("UTF-8")
240 .convertToInputStream(this.stringify(this.cache[entry]));
242 this.getCacheWriter().addEntryStream(entry, time * 1000,
243 this.compression, stream,
248 if (this._cacheWriter)
249 this.getCacheWriter().processQueue(this, null);
253 onStopRequest: function onStopRequest() {
260 // catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
262 // vim: set fdm=marker sw=4 sts=4 et ft=javascript: