Update locate engine download code

Asyncify, make JSON load failures not fatal, and simplify icon download
code (which also fixes download of CrossRef icon, which was failing for
some reason).
This commit is contained in:
Dan Stillman 2017-09-18 02:26:44 -04:00
parent 6d53839fbc
commit d83d70eb5c

View file

@ -35,17 +35,23 @@ Zotero.LocateManager = new function() {
/** /**
* Read locateEngines JSON file to initialize locate manager * Read locateEngines JSON file to initialize locate manager
*/ */
this.init = function() { this.init = async function() {
_ios = Components.classes["@mozilla.org/network/io-service;1"]. _ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService); getService(Components.interfaces.nsIIOService);
_jsonFile = _getLocateFile(); _jsonFile = _getLocateFile();
if(_jsonFile.exists()) { try {
_locateEngines = JSON.parse(Zotero.File.getContents(_jsonFile)) if (await OS.File.exists(_jsonFile)) {
.map(engine => new LocateEngine(engine)); _locateEngines = JSON.parse(await Zotero.File.getContentsAsync(_jsonFile))
} else { .map(engine => new LocateEngine(engine));
this.restoreDefaultEngines(); }
else {
await this.restoreDefaultEngines();
}
}
catch (e) {
Zotero.logError(e);
} }
} }
@ -124,22 +130,24 @@ Zotero.LocateManager = new function() {
/** /**
* Restore default engines by copying file from extension dir * Restore default engines by copying file from extension dir
*/ */
this.restoreDefaultEngines = function() { this.restoreDefaultEngines = async function () {
// get locate dir // get locate dir
var locateDir = _getLocateDirectory(); var locateDir = _getLocateDirectory();
// remove old locate dir // remove old locate dir
if(locateDir.exists()) locateDir.remove(true); await OS.File.removeDir(locateDir, { ignoreAbsent: true, ignorePermissions: true });
// create new locate dir // create new locate dir
locateDir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700); await OS.File.makeDir(locateDir, { unixMode: 0o755 });
// copy default file to new locate dir // copy default file to new locate dir
Zotero.File.putContents(_jsonFile, await Zotero.File.putContentsAsync(
Zotero.File.getContentsFromURL(_getDefaultFile())); _jsonFile,
await Zotero.File.getContentsFromURLAsync(_getDefaultFile())
);
// reread locate engines // reread locate engines
this.init(); await this.init();
// reload icons for default locate engines // reload icons for default locate engines
for (let engine of this.getEngines()) engine._updateIcon(); for (let engine of this.getEngines()) engine._updateIcon();
@ -148,8 +156,8 @@ Zotero.LocateManager = new function() {
/** /**
* Writes the engines to disk; called from the nsITimer spawned by _serializeLocateEngines * Writes the engines to disk; called from the nsITimer spawned by _serializeLocateEngines
*/ */
this.notify = function() { this.notify = async function () {
Zotero.File.putContents(_jsonFile, JSON.stringify(_locateEngines, null, "\t")); await Zotero.File.putContentsAsync(_jsonFile, JSON.stringify(_locateEngines, null, "\t"));
_timer = undefined; _timer = undefined;
} }
@ -157,18 +165,14 @@ Zotero.LocateManager = new function() {
* Gets the JSON file containing engine info * Gets the JSON file containing engine info
*/ */
function _getLocateFile() { function _getLocateFile() {
var locateDir = _getLocateDirectory(); return OS.Path.join(_getLocateDirectory(), LOCATE_FILE_NAME);
locateDir.append(LOCATE_FILE_NAME);
return locateDir;
} }
/** /**
* Gets the dir containing the JSON file and engine icons * Gets the dir containing the JSON file and engine icons
*/ */
function _getLocateDirectory() { function _getLocateDirectory() {
var locateDir = Zotero.File.pathToFile(Zotero.DataDirectory.dir); return OS.Path.join(Zotero.DataDirectory.dir, LOCATE_DIR_NAME);
locateDir.append(LOCATE_DIR_NAME);
return locateDir;
} }
/** /**
@ -197,53 +201,6 @@ Zotero.LocateManager = new function() {
return newval; return newval;
} }
/**
* Called when an engine icon is downloaded to write it to disk
*/
function _engineIconLoaded(iconBytes, engine, contentType) {
const iconExtensions = {
"image/png":"png",
"image/jpeg":"jpg",
"image/gif":"gif",
"image/x-icon":"ico",
"image/vnd.microsoft.icon":"ico"
};
// ensure there is an icon
if(!iconBytes) throw "Icon could not be retrieved for "+engine.name;
// ensure there is an extension
var extension = iconExtensions[contentType.toLowerCase()];
if(!extension) throw "Invalid MIME type "+contentType+" for icon for engine "+engine.name;
// remove old icon
engine._removeIcon();
// find a good place to put the icon file
var sanitizedAlias = engine.name.replace(/[^\w _]/g, "");
var iconFile = _getLocateDirectory();
iconFile.append(sanitizedAlias + "." + extension);
if(iconFile.exists()) {
for(var i=0; iconFile.exists(); i++) {
iconFile = iconFile.parent;
iconFile.append(sanitizedAlias + "_" + i + "." + extension);
}
}
// write the icon to the file
var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
fos.init(iconFile, 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate
var bos = Components.classes["@mozilla.org/binaryoutputstream;1"].
createInstance(Components.interfaces.nsIBinaryOutputStream);
bos.setOutputStream(fos);
bos.writeByteArray(iconBytes, iconBytes.length);
bos.close();
// get the URI of the icon
engine.icon = _ios.newFileURI(iconFile).spec;
}
/** /**
* Looks up a parameter in our list * Looks up a parameter in our list
* *
@ -495,108 +452,44 @@ Zotero.LocateManager = new function() {
if(file.exists()) file.remove(null); if(file.exists()) file.remove(null);
}, },
"_updateIcon":function() { _updateIcon: async function () {
// create new channel const iconExtensions = {
var uri = _ios.newURI(this._iconSourceURI, null, null); "image/png": "png",
if(uri.scheme !== "http" && uri.scheme !== "https" && uri.scheme !== "ftp") return; "image/jpeg": "jpg",
var chan = _ios.newChannelFromURI(uri); "image/gif": "gif",
var listener = new loadListener(chan, this, _engineIconLoaded); "image/vnd.microsoft.icon": "ico"
chan.notificationCallbacks = listener; };
chan.asyncOpen(listener, null);
if (!this._iconSourceURI.startsWith('http') && !this._iconSourceURI.startsWith('https')) {
return;
}
var tmpPath = OS.Path.join(Zotero.getTempDirectory().path, Zotero.Utilities.randomString());
await Zotero.File.download(this._iconSourceURI, tmpPath);
var sample = await Zotero.File.getSample(tmpPath);
var contentType = Zotero.MIME.getMIMETypeFromData(sample);
// ensure there is an extension
var extension = iconExtensions[contentType.toLowerCase()];
if (!extension) {
throw new Error(`Invalid content type ${contentType} for icon for engine ${this.name}`);
}
// Find a good place to put the icon file
var sanitizedAlias = this.name.replace(/[^\w _]/g, "");
var iconFile = OS.Path.join(_getLocateDirectory(), sanitizedAlias + "." + extension);
if (await OS.File.exists(iconFile)) {
for (let i = 0; await OS.File.exists(iconFile); i++) {
iconFile = OS.Path.join(
OS.Path.dirname(iconFile),
sanitizedAlias + "_" + i + "." + extension
);
}
}
await OS.File.move(tmpPath, iconFile);
this.icon = OS.Path.toFileURI(iconFile);
} }
} }
/**
* Ripped from nsSearchService.js
*/
function loadListener(aChannel, aEngine, aCallback) {
this._channel = aChannel;
this._bytes = [];
this._engine = aEngine;
this._callback = aCallback;
}
loadListener.prototype = {
_callback: null,
_channel: null,
_countRead: 0,
_engine: null,
_stream: null,
QueryInterface: function SRCH_loadQI(aIID) {
if (aIID.equals(Ci.nsISupports) ||
aIID.equals(Ci.nsIRequestObserver) ||
aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsIChannelEventSink) ||
aIID.equals(Ci.nsIInterfaceRequestor) ||
aIID.equals(Ci.nsIBadCertListener2) ||
aIID.equals(Ci.nsISSLErrorListener) ||
// See FIXME comment below
aIID.equals(Ci.nsIHttpEventSink) ||
aIID.equals(Ci.nsIProgressEventSink) ||
false)
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
// nsIRequestObserver
onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
this._stream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
},
onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
var requestFailed = !Components.isSuccessCode(aStatusCode);
if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
requestFailed = !aRequest.requestSucceeded;
if (requestFailed || this._countRead == 0) {
// send null so the callback can deal with the failure
this._callback(null, this._engine, this._channel.contentType);
} else
this._callback(this._bytes, this._engine, this._channel.contentType);
this._channel = null;
this._engine = null;
},
// nsIStreamListener
onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
aInputStream, aOffset,
aCount) {
this._stream.setInputStream(aInputStream);
// Get a byte array of the data
this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
this._countRead += aCount;
},
// nsIChannelEventSink
onChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
aFlags) {
this._channel = aNewChannel;
},
// nsIInterfaceRequestor
getInterface: function SRCH_load_GI(aIID) {
return this.QueryInterface(aIID);
},
// nsIBadCertListener2
notifyCertProblem: function SRCH_certProblem(socketInfo, status, targetSite) {
return true;
},
// nsISSLErrorListener
notifySSLError: function SRCH_SSLError(socketInfo, error, targetSite) {
return true;
},
// FIXME: bug 253127
// nsIHttpEventSink
onRedirect: function (aChannel, aNewChannel) {},
// nsIProgressEventSink
onProgress: function (aRequest, aContext, aProgress, aProgressMax) {},
onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
}
} }