From e3a9c6779bc0ffea8093cbd19d67f1730325baa2 Mon Sep 17 00:00:00 2001 From: Dan Stillman <dstillman@zotero.org> Date: Mon, 5 Sep 2016 20:41:35 -0400 Subject: [PATCH] Restore connector mode functionality in Firefox Non-Zotero for Firefox connector code will probably need to be updated to handle these changes. --- chrome/content/zotero/browser.js | 22 ++-- chrome/content/zotero/icon.js | 5 + chrome/content/zotero/overlay.js | 4 + chrome/content/zotero/xpcom/connector/repo.js | 48 +++++--- .../zotero/xpcom/connector/translator.js | 107 +++++++----------- .../zotero/xpcom/translation/translate.js | 103 ++++++++++------- .../zotero/xpcom/translation/translator.js | 28 ++--- .../zotero/xpcom/translation/translators.js | 2 +- chrome/content/zotero/xpcom/zotero.js | 9 ++ chrome/content/zotero/zoteroPane.js | 6 +- components/zotero-service.js | 10 +- 11 files changed, 188 insertions(+), 156 deletions(-) diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js index f06c4178c3..d449e5bd99 100644 --- a/chrome/content/zotero/browser.js +++ b/chrome/content/zotero/browser.js @@ -613,7 +613,9 @@ var Zotero_Browser = new function() { return; } - yield Zotero.DB.waitForTransaction(); + if (!Zotero.isConnector) { + yield Zotero.DB.waitForTransaction(); + } Zotero_Browser.progress.show(); Zotero_Browser.isScraping = true; @@ -635,14 +637,16 @@ var Zotero_Browser = new function() { collection = ZoteroPane.getSelectedCollection(); } - if (libraryID === Zotero.Libraries.publicationsLibraryID) { - Zotero_Browser.progress.Translation.cannotAddToPublications(); - return; - } - - if (Zotero.Feeds.get(libraryID)) { - Zotero_Browser.progress.Translation.cannotAddToFeed(); - return; + if (!Zotero.isConnector) { + if (libraryID === Zotero.Libraries.publicationsLibraryID) { + Zotero_Browser.progress.Translation.cannotAddToPublications(); + return; + } + + if (Zotero.Feeds.get(libraryID)) { + Zotero_Browser.progress.Translation.cannotAddToFeed(); + return; + } } Zotero_Browser.progress.Translation.scrapingTo(libraryID, collection); diff --git a/chrome/content/zotero/icon.js b/chrome/content/zotero/icon.js index 1f3ce8ce9e..34f427ca44 100644 --- a/chrome/content/zotero/icon.js +++ b/chrome/content/zotero/icon.js @@ -28,6 +28,11 @@ Components.utils.import("resource://zotero/config.js"); Components.utils.import("resource:///modules/CustomizableUI.jsm"); +// Necessary for connector mode, for some reason +var Zotero = Components.classes["@zotero.org/Zotero;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + var comboButtonsID = 'zotero-toolbar-buttons'; CustomizableUI.addListener({ diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js index c12a3a0107..6ee68a6660 100644 --- a/chrome/content/zotero/overlay.js +++ b/chrome/content/zotero/overlay.js @@ -42,6 +42,10 @@ var ZoteroOverlay = new function() var self = this; var iconLoaded = false; + if (Zotero.isConnector) { + return; + } + Zotero.Promise.try(function () { if (!Zotero) { throw new Error("No Zotero object"); diff --git a/chrome/content/zotero/xpcom/connector/repo.js b/chrome/content/zotero/xpcom/connector/repo.js index 7b6774956a..b747f6d1e0 100644 --- a/chrome/content/zotero/xpcom/connector/repo.js +++ b/chrome/content/zotero/xpcom/connector/repo.js @@ -55,34 +55,54 @@ Zotero.Repo = new function() { /** * Get translator code from repository * @param {String} translatorID ID of the translator to retrieve code for - * @param {Function} callback Callback to pass code when retreived */ - this.getTranslatorCode = function(translatorID, callback) { + this.getTranslatorCode = Zotero.Promise.method(function (translatorID) { + var deferred = Zotero.Promise.defer(); + // try standalone Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) { if(result) { - _haveCode(result, translatorID, Zotero.Repo.SOURCE_ZOTERO_STANDALONE, callback); + deferred.resolve( + Zotero.Promise.all( + [ + _haveCode(result, translatorID), + Zotero.Repo.SOURCE_ZOTERO_STANDALONE + ] + ) + ); return; } + // then try repo - Zotero.HTTP.doGet(ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version, + Zotero.HTTP.doGet( + ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version, function(xmlhttp) { - _haveCode(xmlhttp.status === 200 ? xmlhttp.responseText : false, translatorID, - Zotero.Repo.SOURCE_REPO, callback); + deferred.resolve( + Zotero.Promise.all( + [ + _haveCode( + xmlhttp.status === 200 ? xmlhttp.responseText : false, + translatorID + ), + Zotero.Repo.SOURCE_REPO + ] + ) + ); } ); }); - }; + + return deferred.promise; + }); /** * Called when code has been retrieved from standalone or repo */ - function _haveCode(code, translatorID, source, callback) { + function _haveCode(code, translatorID) { if(!code) { Zotero.logError(new Error("Code could not be retrieved for " + translatorID)); - callback(false); - return; + return false; } if(!Zotero.isFx) { @@ -91,16 +111,14 @@ Zotero.Repo = new function() { var m = infoRe.exec(code); if (!m) { Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID)); - callback(false); - return; + return false; } try { var metadata = JSON.parse(m[0]); } catch(e) { Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID)); - callback(false); - return; + return false; } var translator = Zotero.Translators.getWithoutCode(translatorID); @@ -114,7 +132,7 @@ Zotero.Repo = new function() { } } } - callback(code, source); + return code; } /** diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js index d919313853..121ffbbc49 100644 --- a/chrome/content/zotero/xpcom/connector/translator.js +++ b/chrome/content/zotero/xpcom/connector/translator.js @@ -24,7 +24,7 @@ */ // Enumeration of types of translators -const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; +var TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; /** * Singleton to handle loading and caching of translators @@ -92,9 +92,6 @@ Zotero.Translators = new function() { /** * Gets the translator that corresponds to a given ID, without attempting to retrieve code * @param {String} id The ID of the translator - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. */ this.getWithoutCode = function(id) { if(!_initialized) Zotero.Translators.init(); @@ -103,54 +100,46 @@ Zotero.Translators = new function() { /** * Gets the translator that corresponds to a given ID + * * @param {String} id The ID of the translator - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. */ - this.get = function(id, callback) { + this.get = Zotero.Promise.method(function (id) { if(!_initialized) Zotero.Translators.init(); var translator = _translators[id]; if(!translator) { - callback(false); return false; } // only need to get code if it is of some use if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER && !translator.hasOwnProperty("code")) { - translator.getCode(function() { callback(translator) }); + return translator.getCode().then(() => translator); } else { - callback(translator); + return translator; } - } + }); /** * Gets all translators for a specific type of translation * @param {String} type The type of translators to get (import, export, web, or search) - * @param {Function} callback A required callback to be executed when translators have been - * retrieved. * @param {Boolean} [debugMode] Whether to assume debugging mode. If true, code is included for * unsupported translators, and code originally retrieved from the * repo is re-retrieved from Zotero Standalone. */ - this.getAllForType = function(type, callback, debugMode) { + this.getAllForType = Zotero.Promise.method(function (type, debugMode) { if(!_initialized) Zotero.Translators.init() var translators = _cache[type].slice(0); - new Zotero.Translators.CodeGetter(translators, callback, translators, debugMode); - return true; - } + var codeGetter = new Zotero.Translators.CodeGetter(translators, debugMode); + return codeGetter.getAll(); + }); /** * Gets web translators for a specific location * @param {String} uri The URI for which to look for translators - * @param {Function} [callback] An optional callback to be executed when translators have been - * retrieved. If no callback is specified, translators are - * returned. The callback is passed a set of functions for - * converting URLs from proper to proxied forms as the second - * argument. + * @return {Promise<Array[]>} - A promise for a 2-item array containing an array of translators and + * an array of functions for converting URLs from proper to proxied forms */ - this.getWebTranslatorsForLocation = function(uri, callback) { + this.getWebTranslatorsForLocation = Zotero.Promise.method(function (uri) { if(!_initialized) Zotero.Translators.init(); var allTranslators = _cache["web"]; var potentialTranslators = []; @@ -215,10 +204,11 @@ Zotero.Translators = new function() { } } - new Zotero.Translators.CodeGetter(potentialTranslators, callback, - [potentialTranslators, converterFunctions]); - return true; - } + var codeGetter = new Zotero.Translators.CodeGetter(potentialTranslators); + return codeGetter.getAll().then(function () { + return [potentialTranslators, converterFunctions]; + }); + }); /** * Converts translators to JSON-serializable objects @@ -331,29 +321,16 @@ Zotero.Translators = new function() { * A class to get the code for a set of translators at once * * @param {Zotero.Translator[]} translators Translators for which to retrieve code - * @param {Function} callback Callback to call once code has been retrieved - * @param {Function} callbackArgs All arguments to be passed to callback (including translators) * @param {Boolean} [debugMode] If true, include code for unsupported translators */ -Zotero.Translators.CodeGetter = function(translators, callback, callbackArgs, debugMode) { +Zotero.Translators.CodeGetter = function(translators, debugMode) { this._translators = translators; - this._callbackArgs = callbackArgs; - this._callback = callback; this._debugMode = debugMode; - this.getCodeFor(0); } -Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) { - var me = this; - while(true) { - if(i === this._translators.length) { - // all done; run callback - this._callback(this._callbackArgs); - return; - } - - var translator = this._translators[i]; - +Zotero.Translators.CodeGetter.prototype.getAll = Zotero.Promise.method(function () { + var translators = []; + for (let translator of this._translators) { // retrieve code if no code and translator is supported locally if((translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER && !translator.hasOwnProperty("code")) // or if debug mode is enabled (even if unsupported locally) @@ -362,17 +339,13 @@ Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) { // include test cases) || (Zotero.Repo && translator.codeSource === Zotero.Repo.SOURCE_REPO)))) { // get next translator - translator.getCode(function() { me.getCodeFor(i+1) }); - return; + translators.push(translator.getCode()); } - - // if we are not at end of list and there is no reason to retrieve the code, keep going - // through the list of potential translators - i++; } -} + return Zotero.Promise.all(translators); +}); -const TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", +var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", "priority", "lastUpdated"]; var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES .concat(["browserSupport", "code", "runMode", "itemType"]); @@ -453,21 +426,23 @@ Zotero.Translator.prototype.init = function(info) { /** * Retrieves code for this translator + * + * @return {Promise<String|false>} - Promise for translator code or false if none */ -Zotero.Translator.prototype.getCode = function(callback) { - var me = this; - Zotero.Repo.getTranslatorCode(this.translatorID, - function(code, source) { - if(!code) { - callback(false); - } else { - // cache code for session only (we have standalone anyway) - me.code = code; - me.codeSource = source; - callback(true); - } +Zotero.Translator.prototype.getCode = function () { + return Zotero.Repo.getTranslatorCode(this.translatorID) + .then(function (args) { + var code = args[0]; + var source = args[1]; + if (!code) { + return false; } - ); + + // cache code for session only (we have standalone anyway) + this.code = code; + this.codeSource = source; + return code; + }.bind(this)); } /** diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index e93d0dd22b..9f1d966c35 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -1082,7 +1082,7 @@ Zotero.Translate.Base.prototype = { * getAllTranslators parameter is meaningless in this context. * @return {Promise} Promise for an array of {@link Zotero.Translator} objects */ - "getTranslators":function(getAllTranslators, checkSetTranslator) { + getTranslators: Zotero.Promise.method(function (getAllTranslators, checkSetTranslator) { var potentialTranslators; // do not allow simultaneous instances of getTranslators @@ -1115,68 +1115,74 @@ Zotero.Translate.Base.prototype = { } // if detection returns immediately, return found translators - var me = this; return potentialTranslators.then(function(result) { var allPotentialTranslators = result[0]; var properToProxyFunctions = result[1]; - me._potentialTranslators = []; - me._foundTranslators = []; + this._potentialTranslators = []; + this._foundTranslators = []; // this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is // specific for each translator, but we want to avoid making a copy of a translator whenever // possible. - me._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null; - me._waitingForRPC = false; + this._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null; + this._waitingForRPC = false; for(var i=0, n=allPotentialTranslators.length; i<n; i++) { var translator = allPotentialTranslators[i]; if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) { - me._potentialTranslators.push(translator); - } else if(me instanceof Zotero.Translate.Web && Zotero.Connector) { - me._waitingForRPC = true; + this._potentialTranslators.push(translator); + } else if (this instanceof Zotero.Translate.Web && Zotero.Connector) { + this._waitingForRPC = true; } } // Attach handler for translators, so that we can return a // promise that provides them. - // TODO make me._detect() return a promise - var deferred = Zotero.Promise.defer(), - translatorsHandler = function(obj, translators) { - me.removeHandler("translators", translatorsHandler); - deferred.resolve(translators); - } - me.setHandler("translators", translatorsHandler); - me._detect(); + // TODO make this._detect() return a promise + var deferred = Zotero.Promise.defer(); + var translatorsHandler = function(obj, translators) { + this.removeHandler("translators", translatorsHandler); + deferred.resolve(translators); + }.bind(this); + this.setHandler("translators", translatorsHandler); + this._detect(); - if(me._waitingForRPC) { + if(this._waitingForRPC) { // Try detect in Zotero Standalone. If this fails, it fails; we shouldn't // get hung up about it. - Zotero.Connector.callMethod("detect", {"uri":me.location.toString(), - "cookie":me.document.cookie, - "html":me.document.documentElement.innerHTML}).then(function(rpcTranslators) { - me._waitingForRPC = false; + Zotero.Connector.callMethod( + "detect", + { + uri: this.location.toString(), + cookie: this.document.cookie, + html: this.document.documentElement.innerHTML + }, + function (rpcTranslators) { + this._waitingForRPC = false; // if there are translators, add them to the list of found translators if(rpcTranslators) { for(var i=0, n=rpcTranslators.length; i<n; i++) { rpcTranslators[i].runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE; } - me._foundTranslators = me._foundTranslators.concat(rpcTranslators); + this._foundTranslators = this._foundTranslators.concat(rpcTranslators); } // call _detectTranslatorsCollected to return detected translators - if(me._currentState === null) { - me._detectTranslatorsCollected(); + if (this._currentState === null) { + this._detectTranslatorsCollected(); } - }); + }.bind(this) + ); } return deferred.promise; - }).catch(function(e) { + }.bind(this)) + .catch(function(e) { Zotero.logError(e); - me.complete(false, e); - }); - }, + this.complete(false, e); + }.bind(this)); + }), /** * Get all potential translators (without running detect) @@ -1199,7 +1205,7 @@ Zotero.Translate.Base.prototype = { * @returns {Promise} Promise resolved with saved items * when translation complete */ - "translate": function (options = {}, ...args) { // initialize properties specific to each translation + translate: Zotero.Promise.method(function (options = {}, ...args) { // initialize properties specific to each translation if (typeof options == 'number') { Zotero.debug("Translate: translate() now takes an object -- update your code", 2); options = { @@ -1256,19 +1262,28 @@ Zotero.Translate.Base.prototype = { this.translator[0] = Zotero.Translators.get(this.translator[0]); } - var loadPromise = this._loadTranslator(this.translator[0]); - if (this.noWait) { - if (!loadPromise.isResolved()) { - return Zotero.Promise.reject(new Error("Load promise is not resolved in noWait mode")); + // Zotero.Translators.get() returns a promise in the connectors + if (this.noWait && this.translator[0].then && !this.translator[0].isResolved()) { + throw new Error("Translator promise is not resolved in noWait mode"); + } + + Zotero.Promise.resolve(this.translator[0]) + .then(function (translator) { + this.translator[0] = translator; + var loadPromise = this._loadTranslator(translator); + if (this.noWait) { + if (!loadPromise.isResolved()) { + return Zotero.Promise.reject(new Error("Load promise is not resolved in noWait mode")); + } + this._translateTranslatorLoaded(); } - this._translateTranslatorLoaded(); - } - else { - loadPromise.then(() => this._translateTranslatorLoaded()); - } - + else { + loadPromise.then(() => this._translateTranslatorLoaded()); + } + }.bind(this)); + return deferred.promise; - }, + }), /** * Called when translator has been retrieved and loaded @@ -1586,7 +1601,7 @@ Zotero.Translate.Base.prototype = { */ "_checkIfDone":function() { if(!this._savingItems && !this._savingAttachments.length && (!this._currentState || this._waitingForSave)) { - if(this.newCollections) { + if(this.newCollections && this._itemSaver.saveCollections) { var me = this; this._itemSaver.saveCollections(this.newCollections).then(function (newCollections) { me.newCollections = newCollections; @@ -1659,7 +1674,7 @@ Zotero.Translate.Base.prototype = { /** * Loads the translator into its sandbox * @param {Zotero.Translator} translator - * @return {Boolean} Whether the translator could be successfully loaded + * @return {Promise<Boolean>} Whether the translator could be successfully loaded */ "_loadTranslator": Zotero.Promise.method(function (translator) { var sandboxLocation = this._getSandboxLocation(); diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js index 64b76fd951..3cf69a682c 100644 --- a/chrome/content/zotero/xpcom/translation/translator.js +++ b/chrome/content/zotero/xpcom/translation/translator.js @@ -140,36 +140,36 @@ Zotero.Translator.prototype.init = function(info) { /** * Load code for a translator */ -Zotero.Translator.prototype.getCode = function() { - if(this.code) return Zotero.Promise.resolve(this.code); +Zotero.Translator.prototype.getCode = Zotero.Promise.method(function () { + if (this.code) return this.code; - var me = this; if(Zotero.isConnector) { - // TODO make this a promise - return Zotero.Repo.getTranslatorCode(this.translatorID). - spread(function(code, source) { + return Zotero.Repo.getTranslatorCode(this.translatorID) + .then(function (args) { + var code = args[0]; + var source = args[1]; if(!code) { - throw "Code for "+me.label+" could not be retrieved"; + throw new Error("Code for " + this.label + " could not be retrieved"); } // Cache any translators for session, since retrieving via // HTTP may be expensive - me.code = code; - me.codeSource = source; + this.code = code; + this.codeSource = source; return code; - }); + }.bind(this)); } else { var promise = Zotero.File.getContentsAsync(this.path); if(this.cacheCode) { // Cache target-less web translators for session, since we // will use them a lot - promise.then(function(code) { - me.code = code; + return promise.then(function(code) { + this.code = code; return code; - }); + }.bind(this)); } return promise; } -} +}); /** * Get metadata block for a translator diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js index 174fd6d65d..9257f35b64 100644 --- a/chrome/content/zotero/xpcom/translation/translators.js +++ b/chrome/content/zotero/xpcom/translation/translators.js @@ -260,7 +260,7 @@ Zotero.Translators = new function() { */ this.getAll = function() { return this.init().then(function () { - return Object.keys(_translators); + return Object.keys(_translators).map(id => _translators[id]); }); } diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 414c424555..7a346ce879 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -460,7 +460,16 @@ Components.utils.import("resource://gre/modules/osfile.jsm"); this.initializationDeferred.resolve(); if(Zotero.isConnector) { + // Add toolbar icon + try { + Services.scriptloader.loadSubScript("chrome://zotero/content/icon.js", {}, "UTF-8"); + } + catch (e) { + Zotero.logError(e); + } + Zotero.Repo.init(); + Zotero.locked = false; } if(!Zotero.isFirstLoadThisSession) { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 67eeaed191..4476bc9575 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -140,7 +140,7 @@ var ZoteroPane = new function() } /** - * Called on window load or when has been reloaded after switching into or out of connector + * Called on window load or when pane has been reloaded after switching into or out of connector * mode */ function _loadPane() { @@ -160,6 +160,10 @@ var ZoteroPane = new function() collectionsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true); collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true); + // Clear items view, so that the load registers as a new selected collection when switching + // between modes + ZoteroPane_Local.itemsView = null; + var itemsTree = document.getElementById('zotero-items-tree'); itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree)); itemsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true); diff --git a/components/zotero-service.js b/components/zotero-service.js index 0672dcf2ac..ca66420860 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -336,9 +336,6 @@ function ZoteroService() { makeZoteroContext(false); zContext.Zotero.init(zInitOptions) .catch(function (e) { - dump(e + "\n\n"); - Components.utils.reportError(e); - if (e === "ZOTERO_SHOULD_START_AS_CONNECTOR") { // if Zotero should start as a connector, reload it return zContext.Zotero.shutdown() @@ -347,9 +344,10 @@ function ZoteroService() { return zContext.Zotero.init(zInitOptions); }) } - else { - throw e; - } + + dump(e + "\n\n"); + Components.utils.reportError(e); + throw e; }) .then(function () { zContext.Zotero.debug("Initialized in "+(Date.now() - start)+" ms");