1 // Copyright (c) 2011-2014 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", "storage"]);
15 var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
16 init: function init() {
18 this.storage = storage.newMap("cache", { store: true });
20 this.globalProviders = this.providers;
21 this.providing = RealSet();
22 this.localProviders = RealSet();
24 if (JSMLoader.cacheFlush)
27 update(services["dactyl:"].providers, {
28 "cache": (uri, path) => {
29 let contentType = "text/plain";
31 contentType = services.mime.getTypeFromURI(uri);
35 if (this.storage.has(path) ||
37 !this.cacheReader.hasEntry(path))
38 return [contentType, this.force(path)];
40 let channel = services.StreamChannel(uri);
42 channel.contentStream = this.cacheReader.getInputStream(path);
44 catch (e if e.result = Cr.NS_ERROR_FILE_CORRUPTED) {
45 this.flushDiskCache();
48 channel.contentType = contentType;
49 channel.contentCharset = "UTF-8";
55 Local: function Local(dactyl, modules, window) ({
56 init: function init() {
64 parse: function parse(str) {
65 if ('{['.contains(str[0]))
66 return JSON.parse(str);
70 stringify: function stringify(obj) {
73 return JSON.stringify(obj);
78 cacheFile: Class.Memoize(function () {
79 let dir = File(services.directory.get("ProfD", Ci.nsIFile))
82 dir.create(dir.DIRECTORY_TYPE, octal(777));
83 return dir.child("cache.zip");
87 if (!this._cacheReader && this.cacheFile.exists()
90 this._cacheReader = services.ZipReader(this.cacheFile.file);
92 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
94 this.flushDiskCache();
97 return this._cacheReader;
100 get inQueue() this._cacheWriter && this._cacheWriter.inQueue,
102 getCacheWriter: function () {
103 if (!this._cacheWriter)
105 let mode = File.MODE_RDWR;
106 if (!this.cacheFile.exists())
107 mode |= File.MODE_CREATE;
109 cache._cacheWriter = services.ZipWriter(this.cacheFile.file, mode);
111 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
113 this.cacheFile.remove(false);
115 mode |= File.MODE_CREATE;
116 cache._cacheWriter = services.ZipWriter(this.cacheFile.file, mode);
118 return this._cacheWriter;
121 closeReader: function closeReader() {
122 if (cache._cacheReader) {
123 this.cacheReader.close();
124 cache._cacheReader = null;
128 closeWriter: util.wrapCallback(function closeWriter() {
131 if (this._cacheWriter) {
132 this._cacheWriter.close();
133 cache._cacheWriter = null;
136 if (this.cacheFile.fileSize <= 22)
137 this.cacheFile.remove(false);
141 flush: function flush(filter) {
143 this.storage.keys().filter(filter)
144 .forEach(bind("remove", this.storage));
147 this.storage.clear();
148 this.flushDiskCache();
152 flushDiskCache: function flushDiskCache() {
153 if (this.cacheFile.exists()) {
156 this.flushJAR(this.cacheFile);
157 this.cacheFile.remove(false);
161 flushAll: function flushAll(file) {
166 flushEntry: function flushEntry(name, time) {
167 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
168 if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
171 this.queue.push([null, name]);
172 cache.processQueue();
175 this.storage.remove(name);
178 flushJAR: function flushJAR(file) {
179 services.observer.notifyObservers(File(file).file, "flush-cache-entry", "");
182 flushStartup: function flushStartup() {
183 services.observer.notifyObservers(null, "startupcache-invalidate", "");
186 force: function force(name, localOnly) {
187 if (this.storage.has(name))
188 return this.storage.get(name);
190 util.waitFor(() => !this.inQueue);
192 if (this.cacheReader && this.cacheReader.hasEntry(name)) {
194 return this.parse(File.readStream(
195 this.cacheReader.getInputStream(name)));
197 catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
198 this.flushDiskCache();
202 if (this.localProviders.has(name) && !this.isLocal) {
203 for (let { cache } of overlay.modules)
204 if (cache._has(name))
205 return cache.force(name, true);
208 if (hasOwnProperty(this.providers, name)) {
209 util.assert(!this.providing.add(name),
210 "Already generating cache for " + name,
213 let [func, long] = this.providers[name];
215 var value = func.call(this, name);
218 this.providing.delete(name);
222 this.storage.set(name, value);
224 cache.queue.push([Date.now(), name, value]);
225 cache.processQueue();
231 if (this.isLocal && !localOnly)
232 return cache.force(name);
235 get: function get(name, callback, long) {
236 if (this.storage.has(name))
237 return this.storage.get(name);
239 if (callback && !(hasOwnProperty(this.providers, name) ||
240 this.localProviders.has(name)))
241 this.register(name, callback, long);
243 var result = this.force(name);
244 util.assert(result !== undefined, "No such cache key", false);
249 _has: function _has(name) hasOwnProperty(this.providers, name)
250 || this.storage.has(name),
252 has: function has(name) [this.globalProviders, this.localProviders]
253 .some(obj => isinstance(obj, ["Set"]) ? obj.has(name)
254 : hasOwnProperty(obj, name)),
256 register: function register(name, callback, long) {
258 this.localProviders.add(name);
260 this.providers[name] = [callback, long];
263 processQueue: function processQueue() {
267 if (this.queue.length && !this.inQueue) {
268 // removeEntry does not work properly with queues.
270 for (let [, entry] of this.queue)
271 if (this.getCacheWriter().hasEntry(entry)) {
272 this.getCacheWriter().removeEntry(entry, false);
277 util.flushCache(this.cacheFile);
280 this.queue.splice(0).forEach(function ([time, entry, value]) {
281 if (time && value != null) {
282 let stream = services.CharsetConv("UTF-8")
283 .convertToInputStream(this.stringify(value));
285 this.getCacheWriter().addEntryStream(entry, time * 1000,
286 this.compression, stream,
291 if (this._cacheWriter)
292 this.getCacheWriter().processQueue(this, null);
296 onStopRequest: function onStopRequest() {
303 // catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
305 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: