diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index 403bf525eb..83832baf31 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -1195,7 +1195,19 @@ Zotero.Items = function() { * @return {Promise} */ this._hashAttachmentText = async function (attachment) { - if ((await OS.File.stat(await attachment.getFilePathAsync())).size > 5e8) { + var fileInfo; + try { + fileInfo = await OS.File.stat(attachment.getFilePath()); + } + catch (e) { + if (e instanceof OS.File.Error && e.becauseNoSuchFile) { + Zotero.debug('_hashAttachmentText: Attachment not found'); + return null; + } + Zotero.logError(e); + return null; + } + if (fileInfo.size > 5e8) { Zotero.debug('_hashAttachmentText: Attachment too large'); return null; } diff --git a/test/tests/itemsTest.js b/test/tests/itemsTest.js index 3813075485..c2f9f4a04e 100644 --- a/test/tests/itemsTest.js +++ b/test/tests/itemsTest.js @@ -516,7 +516,26 @@ describe("Zotero.Items", function () { assert.isTrue(item2.deleted); assert.isFalse(attachment2.deleted); }); - + + it("should ignore attachment with missing file", async function () { + let item1 = await createDataObject('item'); + let attachment1 = await importPDFAttachment(item1); + + let item2 = item1.clone(); + await item2.saveTx(); + let attachment2 = await importPDFAttachment(item2); + // Delete the attachment file + await OS.File.remove(await attachment2.getFilePathAsync()); + + await Zotero.Items.merge(item1, [item2]); + + assert.isFalse(item1.deleted); + assert.isFalse(attachment1.deleted); + assert.equal(item1.numAttachments(true), 2); + assert.isTrue(item2.deleted); + assert.isFalse(attachment2.deleted); + }); + it("should allow small differences when hashing content", async function () { let item1 = await createDataObject('item', { setTitle: true }); let attachment1 = await importFileAttachment('duplicatesMerge_JSTOR_1.pdf', { parentItemID: item1.id });