From 97661539dce3f485a311fb9eb61626c684705d83 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 28 Feb 2018 21:43:18 -0500 Subject: [PATCH] Automatically retrieve metadata when saving PDFs Applies to dragging to the collections pane or the items pane, adding via New Item menu, or saving via the connector server If the renaming pref is enabled, the PDF is renamed after recognition. Can be disabled in the preferences Closes #917 --- .../preferences/preferences_general.xul | 2 + chrome/content/zotero/xpcom/attachments.js | 4 +- .../zotero/xpcom/collectionTreeView.js | 12 +++- chrome/content/zotero/xpcom/itemTreeView.js | 23 +++++-- chrome/content/zotero/xpcom/recognizePDF.js | 19 ++++++ .../content/zotero/xpcom/server_connector.js | 8 ++- chrome/content/zotero/zoteroPane.js | 41 ++++++++---- chrome/locale/en-US/zotero/preferences.dtd | 1 + defaults/preferences/zotero.js | 1 + test/content/support.js | 10 +++ test/tests/itemTreeViewTest.js | 65 ++++++++++++++++++- test/tests/server_connectorTest.js | 38 +++++++++-- 12 files changed, 192 insertions(+), 32 deletions(-) diff --git a/chrome/content/zotero/preferences/preferences_general.xul b/chrome/content/zotero/preferences/preferences_general.xul index fc0c087e8f..c908652c30 100644 --- a/chrome/content/zotero/preferences/preferences_general.xul +++ b/chrome/content/zotero/preferences/preferences_general.xul @@ -37,6 +37,7 @@ + @@ -123,6 +124,7 @@ label="&zotero.preferences.automaticSnapshots;" preference="pref-automaticSnapshots"/> + diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index c50cfdd0e3..10b74373d4 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -257,7 +257,7 @@ Zotero.Attachments = new function(){ * @param {String} [options.referrer] * @param {CookieSandbox} [options.cookieSandbox] * @param {Object} [options.saveOptions] - * @return {Promise} - A promise for the created attachment item + * @return {Promise} - A promise for the created attachment item */ this.importFromURL = Zotero.Promise.coroutine(function* (options) { var libraryID = options.libraryID; @@ -298,7 +298,7 @@ Zotero.Attachments = new function(){ if (channel.responseStatus < 200 || channel.responseStatus >= 400) { reject(new Error("Invalid response " + channel.responseStatus + " " + channel.responseStatusText + " for '" + url + "'")); - return; + return false; } } try { diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 7cb521b663..0c18c10c5e 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -2242,19 +2242,20 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r } else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') { var targetLibraryID = targetTreeRow.ref.libraryID; - if (targetTreeRow.isCollection()) { var parentCollectionID = targetTreeRow.ref.id; } else { var parentCollectionID = false; } + var addedItems = []; for (var i=0; i { + return item + && item.isFileAttachment() + && item.attachmentContentType == 'application/pdf'; + }); + if (!pdfs.length) { + return; + } + this.recognizeItems(pdfs); + let pane = Zotero.getActiveZoteroPane(); + if (pane) { + Zotero_RecognizePDF_Dialog.open(); + } + }; + /** * Returns all rows * @return {Array} diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js index 5fd0b2fd76..c78f77b43d 100644 --- a/chrome/content/zotero/xpcom/server_connector.js +++ b/chrome/content/zotero/xpcom/server_connector.js @@ -649,7 +649,13 @@ Zotero.Server.Connector.SaveSnapshot.prototype = { contentType: "application/pdf", cookieSandbox }); - yield session.addItem(item); + if (item) { + yield session.addItem(item); + + // Automatically recognize PDF + Zotero.RecognizePDF.autoRecognizeItems([item]); + } + return 201; } catch (e) { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 67f272a6d1..8ae802f58e 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -85,6 +85,7 @@ var ZoteroPane = new function() // Set key down handler document.getElementById('appcontent').addEventListener('keydown', ZoteroPane_Local.handleKeyDown, true); + // Hide or show the PDF recognizer button Zotero.RecognizePDF.addListener('empty', function (row) { document.getElementById('zotero-tb-recognize').hidden = true; }); @@ -3692,6 +3693,7 @@ var ZoteroPane = new function() files.push(file.path); } + var addedItems = []; var collection; var fileBaseName; if (parentItemID) { @@ -3713,6 +3715,8 @@ var ZoteroPane = new function() } for (let file of files) { + let item; + if (link) { // Rename linked file, with unique suffix if necessary try { @@ -3733,7 +3737,7 @@ var ZoteroPane = new function() Zotero.logError(e); } - let item = yield Zotero.Attachments.linkFromFile({ + item = yield Zotero.Attachments.linkFromFile({ file, parentItemID, collections: collection ? [collection] : undefined @@ -3746,7 +3750,7 @@ var ZoteroPane = new function() continue; } - yield Zotero.Attachments.importFromFile({ + item = yield Zotero.Attachments.importFromFile({ file, libraryID, fileBaseName, @@ -3754,6 +3758,13 @@ var ZoteroPane = new function() collections: collection ? [collection] : undefined }); } + + addedItems.push(item); + } + + // Automatically retrieve metadata for top-level PDFs + if (!parentItemID) { + Zotero.RecognizePDF.autoRecognizeItems(addedItems); } }); @@ -3917,6 +3928,9 @@ var ZoteroPane = new function() }); + /** + * @return {Zotero.Item|false} - The saved item, or false if item can't be saved + */ this.addItemFromURL = Zotero.Promise.coroutine(function* (url, itemType, saveSnapshot, row) { if (window.content && url == window.content.document.location.href) { return this.addItemFromPage(itemType, saveSnapshot, row); @@ -3932,8 +3946,8 @@ var ZoteroPane = new function() var processor = function (doc) { return ZoteroPane_Local.addItemFromDocument(doc, itemType, saveSnapshot, row) - .then(function () { - deferred.resolve() + .then(function (item) { + deferred.resolve(item) }); }; var done = function () {} @@ -3966,7 +3980,7 @@ var ZoteroPane = new function() if (!ZoteroPane_Local.canEdit(row)) { ZoteroPane_Local.displayCannotEditLibraryMessage(); - return; + return false; } if (row !== undefined) { @@ -3983,7 +3997,7 @@ var ZoteroPane = new function() if (!ZoteroPane_Local.canEditFiles(row)) { ZoteroPane_Local.displayCannotEditLibraryFilesMessage(); - return; + return false; } if (collectionTreeRow && collectionTreeRow.isCollection()) { @@ -4000,7 +4014,7 @@ var ZoteroPane = new function() contentType: mimeType }); this.selectItem(attachmentItem.id) - return; + return attachmentItem; } } @@ -4033,7 +4047,7 @@ var ZoteroPane = new function() } } - return item.id; + return item; } }); @@ -4521,6 +4535,12 @@ var ZoteroPane = new function() }; + this.recognizeSelected = function() { + Zotero.RecognizePDF.recognizeItems(ZoteroPane.getSelectedItems()); + Zotero_RecognizePDF_Dialog.open(); + }; + + this.createParentItemsFromSelected = Zotero.Promise.coroutine(function* () { if (!this.canEdit()) { this.displayCannotEditLibraryMessage(); @@ -4954,11 +4974,6 @@ var ZoteroPane = new function() if(_beforeReloadFunctions.indexOf(func) === -1) _beforeReloadFunctions.push(func); } - this.recognizeSelected = function() { - Zotero.RecognizePDF.recognizeItems(ZoteroPane.getSelectedItems()); - Zotero_RecognizePDF_Dialog.open(); - }; - /** * Implements nsIObserver for Zotero reload */ diff --git a/chrome/locale/en-US/zotero/preferences.dtd b/chrome/locale/en-US/zotero/preferences.dtd index 9bb38feeac..b8dd7afb3c 100644 --- a/chrome/locale/en-US/zotero/preferences.dtd +++ b/chrome/locale/en-US/zotero/preferences.dtd @@ -27,6 +27,7 @@ + diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index 38760ba7f4..9a6cc70c16 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -35,6 +35,7 @@ pref("extensions.zotero.automaticTags",true); pref("extensions.zotero.fontSize", "1.0"); pref("extensions.zotero.layout", "standard"); pref("extensions.zotero.recursiveCollections", false); +pref("extensions.zotero.autoRecognizeFiles", true); pref("extensions.zotero.renameAttachmentFiles.automatic", true); pref("extensions.zotero.renameAttachmentFiles.automatic.fileTypes", "application/pdf"); pref("extensions.zotero.attachmentRenameFormatString", '{%c - }{%y - }{%t{50}}'); diff --git a/test/content/support.js b/test/content/support.js index e1c9efadcd..cba1158c07 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -19,6 +19,16 @@ function waitForDOMEvent(target, event, capture) { return deferred.promise; } +async function waitForRecognizer() { + var win = await waitForWindow('chrome://zotero/content/recognizePDFDialog.xul') + // Wait for status to show as complete + var completeStr = Zotero.getString("recognizePDF.complete.label"); + while (win.document.getElementById("label").value != completeStr) { + await Zotero.Promise.delay(20); + } + return win; +} + /** * Open a chrome window and return a promise for the window * diff --git a/test/tests/itemTreeViewTest.js b/test/tests/itemTreeViewTest.js index ca485efb49..d7634dc2c4 100644 --- a/test/tests/itemTreeViewTest.js +++ b/test/tests/itemTreeViewTest.js @@ -695,11 +695,11 @@ describe("Zotero.ItemTreeView", function() { file.append(pdfFilename); pdfPath = file.path; httpd.registerFile("/" + pdfFilename, file); - - Zotero.Prefs.clear('renameAttachmentFiles.automatic'); }); - afterEach(() => { + beforeEach(() => { + // Don't run recognize on every file + Zotero.Prefs.set('autoRecognizeFiles', false); Zotero.Prefs.clear('renameAttachmentFiles.automatic'); }); @@ -707,6 +707,9 @@ describe("Zotero.ItemTreeView", function() { var defer = new Zotero.Promise.defer(); httpd.stop(() => defer.resolve()); yield defer.promise; + + Zotero.Prefs.clear('autoRecognizeFiles'); + Zotero.Prefs.clear('renameAttachmentFiles.automatic'); }); it("should move a child item from one item to another", function* () { @@ -879,6 +882,62 @@ describe("Zotero.ItemTreeView", function() { ); }); + it("should automatically retrieve metadata for top-level PDF if pref is enabled", async function () { + Zotero.Prefs.set('autoRecognizeFiles', true); + + var view = zp.itemsView; + + var promise = waitForItemEvent('add'); + var recognizerPromise = waitForRecognizer(); + + // Fake recognizer response + Zotero.HTTP.mock = sinon.FakeXMLHttpRequest; + var server = sinon.fakeServer.create(); + server.autoRespond = true; + setHTTPResponse( + server, + ZOTERO_CONFIG.RECOGNIZE_URL, + { + method: 'POST', + url: 'recognize', + status: 200, + headers: { + 'Content-Type': 'application/json' + }, + json: { + title: 'Test', + authors: [] + } + } + ); + + itemsView.drop(0, -1, { + dropEffect: 'copy', + effectAllowed: 'copy', + types: { + contains: function (type) { + return type == 'text/x-moz-url'; + } + }, + getData: function (type) { + if (type == 'text/x-moz-url') { + return pdfURL; + } + }, + mozItemCount: 1, + }) + + var itemIDs = await promise; + var item = Zotero.Items.get(itemIDs[0]); + + var progressWindow = await recognizerPromise; + progressWindow.close(); + Zotero.RecognizePDF.cancel(); + assert.isFalse(item.isTopLevelItem()); + + Zotero.HTTP.mock = null; + }); + it("should rename a stored child attachment using parent metadata if no existing file attachments and pref enabled", async function () { var view = zp.itemsView; var parentTitle = Zotero.Utilities.randomString(); diff --git a/test/tests/server_connectorTest.js b/test/tests/server_connectorTest.js index ab15218545..84975d7751 100644 --- a/test/tests/server_connectorTest.js +++ b/test/tests/server_connectorTest.js @@ -305,17 +305,36 @@ describe("Connector Server", function () { assert.equal(item.getField('title'), 'Title'); }); - it("should save a PDF to the current selected collection", function* () { - var collection = yield createDataObject('collection'); - yield waitForItemsLoad(win); + it("should save a PDF to the current selected collection and retrieve metadata", async function () { + var collection = await createDataObject('collection'); + await waitForItemsLoad(win); var file = getTestDataDirectory(); file.append('test.pdf'); httpd.registerFile("/test.pdf", file); - var ids; var promise = waitForItemEvent('add'); - yield Zotero.HTTP.request( + var recognizerPromise = waitForRecognizer(); + + var origRequest = Zotero.HTTP.request.bind(Zotero.HTTP); + var called = 0; + var stub = sinon.stub(Zotero.HTTP, 'request').callsFake(function (method, url, options) { + // Forward saveSnapshot request + if (url.endsWith('saveSnapshot')) { + return origRequest(...arguments); + } + + // Fake recognizer response + return Zotero.Promise.resolve({ + getResponseHeader: () => {}, + responseText: JSON.stringify({ + title: 'Test', + authors: [] + }) + }); + }); + + await Zotero.HTTP.request( 'POST', connectorServerPath + "/connector/saveSnapshot", { @@ -329,13 +348,20 @@ describe("Connector Server", function () { } ); - var ids = yield promise; + var ids = await promise; assert.lengthOf(ids, 1); var item = Zotero.Items.get(ids[0]); assert.isTrue(item.isImportedAttachment()); assert.equal(item.attachmentContentType, 'application/pdf'); assert.isTrue(collection.hasItem(item.id)); + + var progressWindow = await recognizerPromise; + progressWindow.close(); + Zotero.RecognizePDF.cancel(); + assert.isFalse(item.isTopLevelItem()); + + stub.restore(); }); it("should respond with 500 if a read-only library is selected", function* () {