diff --git a/chrome/content/zotero/xpcom/storage/storageLocal.js b/chrome/content/zotero/xpcom/storage/storageLocal.js index 2b8e9f7748..149d5c9a05 100644 --- a/chrome/content/zotero/xpcom/storage/storageLocal.js +++ b/chrome/content/zotero/xpcom/storage/storageLocal.js @@ -622,11 +622,11 @@ Zotero.Sync.Storage.Local = { } if (!path) { - // This can happen if an HTML snapshot filename was changed and synced - // elsewhere but the renamed file wasn't synced, so the ZIP doesn't - // contain a file with the known name - Components.utils.reportError("File '" + item.attachmentFilename - + "' not found after processing download " + item.libraryKey); + // This generally shouldn't happen, since if the ZIP doesn't contain the primary file, + // and there's only one HTML file within it, we rename it to the current filename, but + // it could occur if there are multiple HTML files or there's an error renaming the file. + Zotero.logError("File '" + item.attachmentFilename + "' not found after processing " + + "download " + item.libraryKey); return new Zotero.Sync.Storage.Result({ localChanges: false }); @@ -795,6 +795,7 @@ Zotero.Sync.Storage.Local = { var itemFileName = item.attachmentFilename; + var createdFiles = new Set(); var entries = zipReader.findEntries(null); while (entries.hasMore()) { var entryName = entries.getNext(); @@ -835,7 +836,7 @@ Zotero.Sync.Storage.Local = { filtered = true; } - var destPath = OS.Path.join(parentDir, ...filePath.split('/')); + let destPath = OS.Path.join(parentDir, ...filePath.split('/')); // If only one file in zip and it doesn't match the known filename, // take our chances and use that name @@ -910,6 +911,7 @@ Zotero.Sync.Storage.Local = { try { zipReader.extract(entryName, Zotero.File.pathToFile(destPath)); + createdFiles.add(PathUtils.filename(destPath)); } catch (e) { try { @@ -968,6 +970,23 @@ Zotero.Sync.Storage.Local = { zipFile.remove(false); } + // If multiple files and none match known filename, but there's only one HTML file, rename it + if (!createdFiles.has(itemFileName)) { + Zotero.debug(`${itemFileName} not found among extracted files`); + let htmlFiles = [...createdFiles].filter(x => /\.html?$/.test(x)); + if (htmlFiles.length == 1) { + let destPath = PathUtils.join(parentDir, itemFileName); + try { + Zotero.debug(`Renaming ${htmlFiles[0]} to ${itemFileName}`); + await IOUtils.move(PathUtils.join(parentDir, htmlFiles[0]), destPath); + returnFile = destPath; + } + catch (e) { + Zotero.logError(e); + } + } + } + return returnFile; }, diff --git a/test/tests/storageLocalTest.js b/test/tests/storageLocalTest.js index e60ad5bf4b..f825bdd856 100644 --- a/test/tests/storageLocalTest.js +++ b/test/tests/storageLocalTest.js @@ -549,6 +549,71 @@ describe("Zotero.Sync.Storage.Local", function () { ); assert.equal(await item.attachmentModificationTime, mtime); }); + + + it("should rename a single HTML file in an old multi-file ZIP to match the current primary filename", async function () { + var libraryID = Zotero.Libraries.userLibraryID; + var parentItem = await createDataObject('item'); + var key = Zotero.DataObjectUtilities.generateKey(); + + var oldFilename = "a.html"; + var auxFilename = "a.gif"; + var newFilename = "b.html"; + var fileContents = Zotero.Utilities.randomString(); + var tmpDir = Zotero.getTempDirectory().path; + var zipFile = OS.Path.join(tmpDir, key + '.tmp'); + + // Create ZIP file + var tmpDir = Zotero.getTempDirectory().path; + var zipDir = await getTempDirectory(); + await Zotero.File.putContentsAsync(PathUtils.join(zipDir, oldFilename), fileContents); + await Zotero.File.putContentsAsync(PathUtils.join(zipDir, auxFilename), ''); + await Zotero.File.zipDirectory(zipDir, zipFile); + await removeDir(zipDir); + + var md5 = Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(zipFile)); + var mtime = 1445667239000; + + var json = { + key, + version: 10, + itemType: 'attachment', + linkMode: 'imported_url', + url: 'https://example.com/foo.html', + filename: 'b.html', + contentType: 'text/plain', + charset: 'utf-8', + md5, + mtime + }; + await Zotero.Sync.Data.Local.processObjectsFromJSON('item', libraryID, [json]); + + var item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, key); + + await Zotero.Sync.Storage.Local.processDownload({ + item, + md5, + mtime, + compressed: true + }); + await OS.File.remove(zipFile); + + var storageDir = Zotero.Attachments.getStorageDirectory(item).path; + + // Make sure path is set correctly + assert.equal(item.getFilePath(), PathUtils.join(storageDir, newFilename)); + // Make sure previous file doesn't exist + assert.isFalse(await IOUtils.exists(PathUtils.join(storageDir, oldFilename))); + // And new ones do + assert.isTrue(await IOUtils.exists(PathUtils.join(storageDir, newFilename))); + assert.isTrue(await IOUtils.exists(PathUtils.join(storageDir, auxFilename))); + + // Make sure main file matches attachment hash and mtime + assert.equal( + await item.attachmentHash, Zotero.Utilities.Internal.md5(fileContents) + ); + assert.equal(await item.attachmentModificationTime, mtime); + }); }); })