diff --git a/chrome/content/zotero/RemoteTranslate.jsm b/chrome/content/zotero/RemoteTranslate.jsm index 854f88364c..ba5b75a191 100644 --- a/chrome/content/zotero/RemoteTranslate.jsm +++ b/chrome/content/zotero/RemoteTranslate.jsm @@ -53,8 +53,8 @@ class RemoteTranslate { _wasSuccess = false; constructor() { - TranslationManager.add(this._id); - TranslationManager.setHandler(this._id, 'done', success => this._wasSuccess = success); + TranslationManager.add(this._id, this); + TranslationManager.setHandler(this._id, 'done', (_, success) => this._wasSuccess = success); } /** @@ -86,7 +86,23 @@ class RemoteTranslate { this._doneHandlers.push(handler); } else { - TranslationManager.setHandler(this._id, name, (...args) => handler(this, ...args)); + TranslationManager.setHandler(this._id, name, handler); + } + } + + /** + * Remove a handler added by #setHandler(). + * + * @param {String} name + * @param {Function} handler + */ + removeHandler(name, handler) { + // 'done' is triggered from translate() + if (name == 'done') { + this._doneHandlers = this._doneHandlers.filter(h => h !== handler); + } + else { + TranslationManager.removeHandler(this._id, name, handler); } } @@ -141,10 +157,13 @@ class RemoteTranslate { */ async translate(options = {}) { let actor = this._browser.browsingContext.currentWindowGlobal.getActor("Translation"); - let items = null; + let items = []; try { - items = await actor.sendQuery("translate", { translator: this._translator, id: this._id }); + let jsonItems = await actor.sendQuery("translate", { translator: this._translator, id: this._id }); if (options.libraryID !== false) { + let itemsLeftToSave = jsonItems.length; + let attachmentsInProgress = new Set(); + let doneHandlersInvoked = false; let itemSaver = new Zotero.Translate.ItemSaver({ libraryID: options.libraryID, collections: options.collections, @@ -154,22 +173,43 @@ class RemoteTranslate { // proxy: unimplemented in the client }); - // Call itemDone on each completed item + let invokeDoneHandlersIfDone = () => { + if (itemsLeftToSave || attachmentsInProgress.size || doneHandlersInvoked) { + return; + } + // Call done (saved in #setHandler() above) at the end + // The Zotero.Translate instance running in the content process has already tried to call done by now, + // but we prevented it from reaching the caller. Now that we've run ItemSaver#saveItems() on this side, + // we can pass it through. + this._callDoneHandlers(this._wasSuccess); + doneHandlersInvoked = true; + }; let itemsDoneCallback = (jsonItems, dbItems) => { - for (let i = 0; i < jsonItems.length; i++) { + // Call itemDone on each completed item + for (let i = 0; i < dbItems.length; i++) { let jsonItem = jsonItems[i]; let dbItem = dbItems[i]; TranslationManager.runHandler(this._id, 'itemDone', dbItem, jsonItem); } + items.push(...dbItems); + itemsLeftToSave -= dbItems.length; + invokeDoneHandlersIfDone(); }; - await itemSaver.saveItems(items, () => {}, itemsDoneCallback); + let attachmentCallback = (attachment, progress) => { + if (progress === 100 || progress === false) { + attachmentsInProgress.delete(attachment); + } + else { + attachmentsInProgress.add(attachment); + } + invokeDoneHandlersIfDone(); + }; + + await itemSaver.saveItems(jsonItems, attachmentCallback, itemsDoneCallback); + } + else { + items.push(...jsonItems); } - - // And call done (saved in #setHandler() above) at the end - // The Zotero.Translate instance running in the content process has already tried to call done by now, - // but we prevented it from reaching the caller. Now that we've run ItemSaver#saveItems() on this side, - // we can pass it through. - this._callDoneHandlers(this._wasSuccess); } catch (e) { this._callDoneHandlers(false); diff --git a/chrome/content/zotero/actors/TranslationParent.jsm b/chrome/content/zotero/actors/TranslationParent.jsm index 8bf693dc83..af0f6527e4 100644 --- a/chrome/content/zotero/actors/TranslationParent.jsm +++ b/chrome/content/zotero/actors/TranslationParent.jsm @@ -7,8 +7,9 @@ const Zotero = Components.classes['@zotero.org/Zotero;1'] const TranslationManager = new class { _registeredRemoteTranslates = new Map(); - add(id) { + add(id, remoteTranslate) { this._registeredRemoteTranslates.set(id, { + remoteTranslate, translatorProvider: null, handlers: {}, }); @@ -37,18 +38,24 @@ const TranslationManager = new class { this._registeredRemoteTranslates.get(id).handlers[name] = [handler]; } } + + removeHandler(id, name, handler) { + this._registeredRemoteTranslates.get(id).handlers[name] + = this._registeredRemoteTranslates.get(id).handlers[name].filter(h => h !== handler); + } clearHandlers(id, name) { this._registeredRemoteTranslates.get(id).handlers[name] = null; } async runHandler(id, name, ...args) { + let remoteTranslate = this._registeredRemoteTranslates.get(id).remoteTranslate; let handlers = this._registeredRemoteTranslates.get(id).handlers[name]; let returnValue = null; if (handlers) { for (let handler of handlers) { try { - returnValue = await handler(...args); + returnValue = await handler(remoteTranslate, ...args); } catch (e) { Zotero.logError(e); diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 260b33741a..70c6c29a9e 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -849,7 +849,7 @@ Zotero.Attachments = new function(){ * @return {Promise} - A promise for the created attachment item */ this.importFromDocument = Zotero.Promise.coroutine(function* (options) { - Zotero.debug('Importing attachment from document'); + Zotero.debug('Importing attachment from ' + (options.document ? 'document' : 'browser')); var libraryID = options.libraryID; var document = options.document;