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
*/
this.init = function() {
this.init = async function() {
_ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
_jsonFile = _getLocateFile();
if(_jsonFile.exists()) {
_locateEngines = JSON.parse(Zotero.File.getContents(_jsonFile))
.map(engine => new LocateEngine(engine));
} else {
this.restoreDefaultEngines();
try {
if (await OS.File.exists(_jsonFile)) {
_locateEngines = JSON.parse(await Zotero.File.getContentsAsync(_jsonFile))
.map(engine => new LocateEngine(engine));
}
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
*/
this.restoreDefaultEngines = function() {
this.restoreDefaultEngines = async function () {
// get locate dir
var locateDir = _getLocateDirectory();
// remove old locate dir
if(locateDir.exists()) locateDir.remove(true);
await OS.File.removeDir(locateDir, { ignoreAbsent: true, ignorePermissions: true });
// 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
Zotero.File.putContents(_jsonFile,
Zotero.File.getContentsFromURL(_getDefaultFile()));
await Zotero.File.putContentsAsync(
_jsonFile,
await Zotero.File.getContentsFromURLAsync(_getDefaultFile())
);
// reread locate engines
this.init();
await this.init();
// reload icons for default locate engines
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
*/
this.notify = function() {
Zotero.File.putContents(_jsonFile, JSON.stringify(_locateEngines, null, "\t"));
this.notify = async function () {
await Zotero.File.putContentsAsync(_jsonFile, JSON.stringify(_locateEngines, null, "\t"));
_timer = undefined;
}
@ -157,18 +165,14 @@ Zotero.LocateManager = new function() {
* Gets the JSON file containing engine info
*/
function _getLocateFile() {
var locateDir = _getLocateDirectory();
locateDir.append(LOCATE_FILE_NAME);
return locateDir;
return OS.Path.join(_getLocateDirectory(), LOCATE_FILE_NAME);
}
/**
* Gets the dir containing the JSON file and engine icons
*/
function _getLocateDirectory() {
var locateDir = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
locateDir.append(LOCATE_DIR_NAME);
return locateDir;
return OS.Path.join(Zotero.DataDirectory.dir, LOCATE_DIR_NAME);
}
/**
@ -197,53 +201,6 @@ Zotero.LocateManager = new function() {
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
*
@ -495,108 +452,44 @@ Zotero.LocateManager = new function() {
if(file.exists()) file.remove(null);
},
"_updateIcon":function() {
// create new channel
var uri = _ios.newURI(this._iconSourceURI, null, null);
if(uri.scheme !== "http" && uri.scheme !== "https" && uri.scheme !== "ftp") return;
var chan = _ios.newChannelFromURI(uri);
var listener = new loadListener(chan, this, _engineIconLoaded);
chan.notificationCallbacks = listener;
chan.asyncOpen(listener, null);
_updateIcon: async function () {
const iconExtensions = {
"image/png": "png",
"image/jpeg": "jpg",
"image/gif": "gif",
"image/vnd.microsoft.icon": "ico"
};
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) {}
}
}