1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-01-07 00:54:13 +01:00

Add cache module. Cleanup help and DTD generation. Fix :help version generation issues. Fix other assorted issues.

This commit is contained in:
Kris Maglione
2011-09-30 01:36:45 -04:00
parent 0cf1151e0a
commit 39e8bf7a06
15 changed files with 587 additions and 314 deletions

232
common/modules/cache.jsm Normal file
View File

@@ -0,0 +1,232 @@
// 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.localProviders = {};
if (JSMLoader.cacheFlush)
this.flush();
update(services["dactyl:"].providers, {
"cache": function (uri, path) {
// TODO: Return zip reader stream
let result = cache.force(path);
if (isArray(result))
return result;
try {
return [services.mime.getTypeFromURI(uri), result];
}
catch (e) {
return ["text/plain", result];
}
}
});
},
Local: function Local(dactyl, modules, window) ({
init: function init() {
delete this.instance;
this.providers = {};
},
isLocal: true
}),
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.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.eists()) {
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) {
if (this.cacheReader && this.cacheReader.hasEntry(name)) {
return JSON.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)) {
let [func, self] = this.providers[name];
this.cache[name] = func.call(self || this, 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),
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(JSON.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:

View File

@@ -14,6 +14,7 @@ defineModule("config", {
}, this);
this.lazyRequire("addons", ["AddonManager"]);
this.lazyRequire("cache", ["cache"]);
this.lazyRequire("highlight", ["highlight"]);
this.lazyRequire("messages", ["_"]);
@@ -54,9 +55,11 @@ var ConfigBase = Class("ConfigBase", {
"resource://dactyl-content/")));
this.timeout(function () {
services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)];
cache.register("config.dtd", function () util.makeDTD(config.dtd));
});
services["dactyl:"].pages["dtd"] = function () [null, cache.get("config.dtd")];
update(services["dactyl:"].providers, {
"locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
"locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
@@ -101,6 +104,7 @@ var ConfigBase = Class("ConfigBase", {
global: ["addons",
"base",
"io",
"cache",
"commands",
"completion",
"config",

View File

@@ -7,21 +7,79 @@
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("help", {
exports: ["help"],
require: ["dom", "javascript", "protocol", "services", "util"]
require: ["cache", "dom", "javascript", "protocol", "services", "util"]
}, this);
var HelpBuilder = Class("HelpBuilder", {
init: function init() {
try {
help._data = this;
this.files = {};
this.tags = {};
this.overlays = {};
// Scrape the list of help files from all.xml
this.tags["all"] = this.tags["all.xml"] = "all";
let files = this.findHelpFile("all").map(function (doc)
[f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
// Scrape the tags from the rest of the help files.
array.flatten(files).forEach(function (file) {
this.tags[file + ".xml"] = file;
this.findHelpFile(file).forEach(function (doc) {
this.addTags(file, doc);
}, this);
}, this);
}
finally {
delete help._data;
}
},
toJSON: function toJSON() ({
files: this.files,
overlays: this.overlays,
tags: this.tags
}),
// Find the tags in the document.
addTags: function addTags(file, doc) {
for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
this.tags[tag] = file;
},
bases: ["dactyl://locale-local/", "dactyl://locale/", "dactyl://cache/help/"],
// Find help and overlay files with the given name.
findHelpFile: function findHelpFile(file) {
let result = [];
for (let base in values(this.bases)) {
let url = [base, file, ".xml"].join("");
let res = util.httpGet(url);
if (res) {
if (res.responseXML.documentElement.localName == "document")
this.files[file] = url;
if (res.responseXML.documentElement.localName == "overlay")
this.overlays[file] = url;
result.push(res.responseXML);
}
}
return result;
}
});
var Help = Module("Help", {
init: function init() {
this.initialized = false;
this.files = {};
this.overlays = {};
this.tags = {};
function Loop(fn)
function (uri, path) {
if (!help.initialized)
return RedirectChannel(uri.spec, uri, 2,
"Initializing. Please wait...");
return fn.apply(this, arguments);
}
@@ -36,8 +94,136 @@ var Help = Module("Help", {
return RedirectChannel("dactyl://help/" + help.tags[tag] + "#" + tag.replace(/#/g, encodeURIComponent), uri);
})
});
cache.register("help", HelpBuilder);
cache.register("help/versions.xml", function () {
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
{ mimeType: "text/plain;charset=UTF-8" })
.responseText;
let re = util.regexp(UTF8(<![CDATA[
^ (?P<comment> \s* # .*\n)
| ^ (?P<space> \s*)
(?P<char> [-*+]) \ //
(?P<content> .*\n
(?: \2\ \ .*\n | \s*\n)* )
| (?P<par>
(?: ^ [^\S\n]*
(?:[^-*+\s] | [-*+]\S)
.*\n
)+
)
| (?: ^ [^\S\n]* \n) +
]]>), "gmxy");
let betas = util.regexp(/\[(b\d)\]/, "gx");
let beta = array(betas.iterate(NEWS))
.map(function (m) m[1]).uniq().slice(-1)[0];
default xml namespace = NS;
function rec(text, level, li) {
XML.ignoreWhitespace = XML.prettyPrinting = false;
let res = <></>;
let list, space, i = 0;
for (let match in re.iterate(text)) {
if (match.comment)
continue;
else if (match.char) {
if (!list)
res += list = <ul/>;
let li = <li/>;
li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
list.* += li;
}
else if (match.par) {
let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
let t = tags;
tags = array(betas.iterate(tags)).map(function (m) m[1]);
let group = !tags.length ? "" :
!tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
if (i === 0 && li) {
li.@highlight = group;
group = "";
}
list = null;
if (level == 0 && /^.*:\n$/.test(match.par)) {
let text = par.slice(0, -1);
res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
}
else {
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
res += <p highlight={group + " HelpNews"}>{
!tags.length ? "" :
<hl key="HelpNewsTag">{tags.join(" ")}</hl>
}{
a ? <hl key="HelpWarning">{a}</hl> : ""
}{
template.linkifyHelp(b, true)
}</p>;
}
}
i++;
}
for each (let attr in res..@highlight) {
attr.parent().@NS::highlight = attr;
delete attr.parent().@highlight;
}
return res;
}
XML.ignoreWhitespace = XML.prettyPrinting = false;
let body = rec(NEWS, 0);
for each (let li in body..li) {
let list = li..li.(@NS::highlight == "HelpNewsOld");
if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
for each (let li in list)
li.@NS::highlight = "";
li.@NS::highlight = "HelpNewsOld";
}
}
return '<?xml version="1.0"?>\n' +
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
<document xmlns={NS} xmlns:dactyl={NS}
name="versions" title={config.appName + " Versions"}>
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
<toc start="2"/>
{body}
</document>.toXMLString()
});
},
initialize: function initialize() {
help.initialized = true;
},
flush: function flush(entries, time) {
cache.flushEntry("help", time);
for (let entry in values(Array.concat(entries || [])))
cache.flushEntry(entry, time);
},
get data() this._data || cache.get("help"),
get files() this.data.files,
get overlays() this.data.overlays,
get tags() this.data.tags,
Local: function Local(dactyl, modules, window) ({
init: function init() {
dactyl.commands["dactyl.help"] = function (event) {
@@ -81,6 +267,7 @@ var Help = Module("Help", {
*/
help: function (topic, consolidated) {
dactyl.initHelp();
if (!topic) {
let helpFile = consolidated ? "all" : modules.options["helpfile"];
@@ -210,176 +397,7 @@ var Help = Module("Help", {
zip.close();
}, [function (context, args) completion.file(context)]),
}),
// Find the tags in the document.
addTags: function addTags(file, doc) {
for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
this.tags[tag] = file;
},
namespaces: ["locale-local", "locale"],
// Find help and overlay files with the given name.
findHelpFile: function findHelpFile(file) {
let result = [];
for (let namespace in values(this.namespaces)) {
let url = ["dactyl://", namespace, "/", file, ".xml"].join("");
let res = util.httpGet(url);
if (res) {
if (res.responseXML.documentElement.localName == "document")
this.files[file] = url;
if (res.responseXML.documentElement.localName == "overlay")
this.overlays[file] = url;
result.push(res.responseXML);
}
}
return result;
},
initialize: function initialize(force) {
// Waits for the add-on to become available, if necessary.
config.addon;
config.version;
if (force || !this.initialized) {
this.files["versions"] = function () {
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
{ mimeType: "text/plain;charset=UTF-8" })
.responseText;
let re = util.regexp(UTF8(<![CDATA[
^ (?P<comment> \s* # .*\n)
| ^ (?P<space> \s*)
(?P<char> [-•*+]) \ //
(?P<content> .*\n
(?: \2\ \ .*\n | \s*\n)* )
| (?P<par>
(?: ^ [^\S\n]*
(?:[^-•*+\s] | [-•*+]\S)
.*\n
)+
)
| (?: ^ [^\S\n]* \n) +
]]>), "gmxy");
let betas = util.regexp(/\[(b\d)\]/, "gx");
let beta = array(betas.iterate(NEWS))
.map(function (m) m[1]).uniq().slice(-1)[0];
default xml namespace = NS;
function rec(text, level, li) {
XML.ignoreWhitespace = XML.prettyPrinting = false;
let res = <></>;
let list, space, i = 0;
for (let match in re.iterate(text)) {
if (match.comment)
continue;
else if (match.char) {
if (!list)
res += list = <ul/>;
let li = <li/>;
li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
list.* += li;
}
else if (match.par) {
let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
let t = tags;
tags = array(betas.iterate(tags)).map(function (m) m[1]);
let group = !tags.length ? "" :
!tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
if (i === 0 && li) {
li.@highlight = group;
group = "";
}
list = null;
if (level == 0 && /^.*:\n$/.test(match.par)) {
let text = par.slice(0, -1);
res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
}
else {
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
res += <p highlight={group + " HelpNews"}>{
!tags.length ? "" :
<hl key="HelpNewsTag">{tags.join(" ")}</hl>
}{
a ? <hl key="HelpWarning">{a}</hl> : ""
}{
template.linkifyHelp(b, true)
}</p>;
}
}
i++;
}
for each (let attr in res..@highlight) {
attr.parent().@NS::highlight = attr;
delete attr.parent().@highlight;
}
return res;
}
XML.ignoreWhitespace = XML.prettyPrinting = false;
let body = rec(NEWS, 0);
for each (let li in body..li) {
let list = li..li.(@NS::highlight == "HelpNewsOld");
if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
for each (let li in list)
li.@NS::highlight = "";
li.@NS::highlight = "HelpNewsOld";
}
}
return ["application/xml",
'<?xml version="1.0"?>\n' +
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
<document xmlns={NS} xmlns:dactyl={NS}
name="versions" title={config.appName + " Versions"}>
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
<toc start="2"/>
{body}
</document>.toXMLString()
];
}
// Scrape the list of help files from all.xml
// Manually process main and overlay files, since XSLTProcessor and
// XMLHttpRequest don't allow access to chrome documents.
this.tags["all"] = this.tags["all.xml"] = "all";
let files = this.findHelpFile("all").map(function (doc)
[f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
// Scrape the tags from the rest of the help files.
array.flatten(files).forEach(function (file) {
this.tags[file + ".xml"] = file;
this.findHelpFile(file).forEach(function (doc) {
this.addTags(file, doc);
}, this);
}, this);
this.tags["versions"] = this.tags["versions.xml"] = "versions";
this.addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
help.initialized = true;
}
},
})
}, {
}, {
commands: function init_commands(dactyl, modules, window) {

View File

@@ -175,7 +175,7 @@ var IO = Module("io", {
util.flushCache();
dactyl.loadScript(uri.spec, context);
help.initialized = false;
dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
catch (e) {
if (e.fileName && !(e instanceof FailedAssertion))
@@ -200,6 +200,7 @@ var IO = Module("io", {
group: context.GROUP,
line: 1
});
dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
Set.add(this._scriptNames, file.path);

View File

@@ -794,7 +794,7 @@ var Options = Module("options", {
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
}, window);
services["dactyl:"].pages["options.dtd"] = function () [null,
modules.cache.register("options.dtd", function ()
util.makeDTD(
iter(([["option", o.name, "default"].join("."),
o.type === "string" ? o.defaultValue.replace(/'/g, "''") :
@@ -804,7 +804,13 @@ var Options = Module("options", {
([["option", o.name, "type"].join("."), o.type] for (o in self)),
config.dtd))];
config.dtd)));
},
signals: {
"io.source": function ioSource(context, file, modTime) {
cache.flushEntry("options.dtd", modTime);
}
},
dactyl: dactyl,

View File

@@ -371,6 +371,9 @@ var Overlay = Module("Overlay", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReferen
};
},
get activeModules() this.activeWindow && this.activeWindow.dactyl.modules,
get modules() this.windows.map(function (w) w.dactyl.modules),
/**
* The most recently active dactyl window.

View File

@@ -106,8 +106,8 @@ var Services = Module("Services", {
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest", "open");
this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", "nsIDOMXPathEvaluator");
this.addClass("XMLDocument", "@mozilla.org/xml/xml-document;1", ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]);
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open");
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open", false);
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open", false);
},
reinit: function () {},