1 // Copyright (c) 2011-2012 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 defineModule("cache", {
8 exports: ["Cache", "cache"],
9 require: ["config", "services", "util"]
12 lazyRequire("overlay", ["overlay"]);
13 lazyRequire("storage", ["File"]);
15 var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
16 init: function init() {
20 this.globalProviders = this.providers;
22 this.localProviders = {};
24 if (JSMLoader.cacheFlush)
27 update(services["dactyl:"].providers, {
28 "cache": function (uri, path) {
29 let contentType = "text/plain";
31 contentType = services.mime.getTypeFromURI(uri)
35 if (!cache.cacheReader || !cache.cacheReader.hasEntry(path))
36 return [contentType, cache.force(path)];
38 let channel = services.StreamChannel(uri);
39 channel.contentStream = cache.cacheReader.getInputStream(path);
40 channel.contentType = contentType;
41 channel.contentCharset = "UTF-8";
47 Local: function Local(dactyl, modules, window) ({
48 init: function init() {
56 parse: function parse(str) {
57 if (~'{['.indexOf(str[0]))
58 return JSON.parse(str);
62 stringify: function stringify(obj) {
65 return JSON.stringify(obj);
70 cacheFile: Class.Memoize(function () {
71 let dir = File(services.directory.get("ProfD", Ci.nsIFile))
74 dir.create(dir.DIRECTORY_TYPE, octal(777));
75 return dir.child("cache.zip");
79 if (!this._cacheReader && this.cacheFile.exists()
82 this._cacheReader = services.ZipReader(this.cacheFile.file);
84 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
87 this.cacheFile.remove(false);
90 return this._cacheReader;
93 get inQueue() this._cacheWriter && this._cacheWriter.inQueue,
95 getCacheWriter: function () {
96 if (!this._cacheWriter)
98 let mode = File.MODE_RDWR;
99 if (!this.cacheFile.exists())
100 mode |= File.MODE_CREATE;
102 cache._cacheWriter = services.ZipWriter(this.cacheFile.file, mode);
104 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
106 this.cacheFile.remove(false);
108 mode |= File.MODE_CREATE;
109 cache._cacheWriter = services.ZipWriter(this.cacheFile.file, mode);
111 return this._cacheWriter;
114 closeReader: function closeReader() {
115 if (cache._cacheReader) {
116 this.cacheReader.close();
117 delete cache._cacheReader;
121 closeWriter: util.wrapCallback(function closeWriter() {
124 if (this._cacheWriter) {
125 this._cacheWriter.close();
126 delete cache._cacheWriter;
129 if (this.cacheFile.fileSize <= 22)
130 this.cacheFile.remove(false);
134 flush: function flush() {
136 if (this.cacheFile.exists()) {
139 this.flushJAR(this.cacheFile);
140 this.cacheFile.remove(false);
144 flushAll: function flushAll(file) {
149 flushEntry: function flushEntry(name, time) {
150 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
151 if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
154 this.queue.push([null, name]);
155 cache.processQueue();
158 delete this.cache[name];
161 flushJAR: function flushJAR(file) {
162 services.observer.notifyObservers(File(file).file, "flush-cache-entry", "");
165 flushStartup: function flushStartup() {
166 services.observer.notifyObservers(null, "startupcache-invalidate", "");
169 force: function force(name, localOnly) {
170 util.waitFor(function () !this.inQueue, this);
172 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
173 return this.parse(File.readStream(
174 this.cacheReader.getInputStream(name)));
177 if (Set.has(this.localProviders, name) && !this.isLocal) {
178 for each (let { cache } in overlay.modules)
179 if (cache._has(name))
180 return cache.force(name, true);
183 if (Set.has(this.providers, name)) {
184 util.assert(!Set.add(this.providing, name),
185 "Already generating cache for " + name,
188 let [func, self] = this.providers[name];
189 this.cache[name] = func.call(self || this, name);
192 delete this.providing[name];
195 cache.queue.push([Date.now(), name]);
196 cache.processQueue();
198 return this.cache[name];
201 if (this.isLocal && !localOnly)
202 return cache.force(name);
205 get: function get(name, callback, self) {
206 if (!Set.has(this.cache, name)) {
207 if (callback && !(Set.has(this.providers, name) ||
208 Set.has(this.localProviders, name)))
209 this.register(name, callback, self);
211 this.cache[name] = this.force(name);
212 util.assert(this.cache[name] !== undefined,
213 "No such cache key", false);
216 return this.cache[name];
219 _has: function _has(name) Set.has(this.providers, name) || set.has(this.cache, name),
221 has: function has(name) [this.globalProviders, this.cache, this.localProviders]
222 .some(function (obj) Set.has(obj, name)),
224 register: function register(name, callback, self) {
226 Set.add(this.localProviders, name);
228 this.providers[name] = [callback, self];
231 processQueue: function processQueue() {
235 if (this.queue.length && !this.inQueue) {
236 // removeEntry does not work properly with queues.
238 for each (let [, entry] in this.queue)
239 if (this.getCacheWriter().hasEntry(entry)) {
240 this.getCacheWriter().removeEntry(entry, false);
246 this.queue.splice(0).forEach(function ([time, entry]) {
247 if (time && Set.has(this.cache, entry)) {
248 let stream = services.CharsetConv("UTF-8")
249 .convertToInputStream(this.stringify(this.cache[entry]));
251 this.getCacheWriter().addEntryStream(entry, time * 1000,
252 this.compression, stream,
257 if (this._cacheWriter)
258 this.getCacheWriter().processQueue(this, null);
262 onStopRequest: function onStopRequest() {
269 // catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
271 // vim: set fdm=marker sw=4 sts=4 et ft=javascript: