From 04779d8d1c3b5191d54f6db43b28b87dd287aefe Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 2 Mar 2019 05:56:32 -0500 Subject: [PATCH] Add import option for storing or linking files This allows files in Mendeley imports to be stored and files in RIS/BibTeX/etc. to be linked. Closes #329 --- chrome/content/zotero/fileInterface.js | 7 +- chrome/content/zotero/import/importWizard.js | 39 +++++---- chrome/content/zotero/import/importWizard.xul | 10 +++ .../zotero/import/mendeley/mendeleyImport.js | 9 ++- chrome/content/zotero/xpcom/attachments.js | 6 +- .../zotero/xpcom/translation/translate.js | 3 + .../xpcom/translation/translate_item.js | 21 ++++- chrome/locale/en-US/zotero/zotero.dtd | 1 + chrome/locale/en-US/zotero/zotero.properties | 4 + chrome/skin/default/zotero/importWizard.css | 22 +++++ test/tests/translateTest.js | 81 +++++++++++++++++++ 11 files changed, 181 insertions(+), 22 deletions(-) diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js index f4d7ec0725..0509f5374e 100644 --- a/chrome/content/zotero/fileInterface.js +++ b/chrome/content/zotero/fileInterface.js @@ -296,6 +296,7 @@ var Zotero_File_Interface = new function() { * @param {nsIFile|string|null} [options.file=null] - File to import, or none to show a filepicker * @param {Boolean} [options.addToLibraryRoot=false] * @param {Boolean} [options.createNewCollection=true] - Put items in a new collection + * @param {Boolean} [options.linkFiles=false] - Link to files instead of storing them * @param {Function} [options.onBeforeImport] - Callback to receive translation object, useful * for displaying progress in a different way. This also causes an error to be throw * instead of shown in the main window. @@ -315,6 +316,7 @@ var Zotero_File_Interface = new function() { var file = options.file ? Zotero.File.pathToFile(options.file) : null; var createNewCollection = options.createNewCollection; var addToLibraryRoot = options.addToLibraryRoot; + var linkFiles = options.linkFiles; var onBeforeImport = options.onBeforeImport; if (createNewCollection === undefined && !addToLibraryRoot) { @@ -359,6 +361,7 @@ var Zotero_File_Interface = new function() { translation, createNewCollection, addToLibraryRoot, + linkFiles, defaultNewCollectionPrefix, onBeforeImport }); @@ -412,6 +415,7 @@ var Zotero_File_Interface = new function() { var translation = options.translation; var addToLibraryRoot = options.addToLibraryRoot; var createNewCollection = options.createNewCollection; + var linkFiles = options.linkFiles; var defaultNewCollectionPrefix = options.defaultNewCollectionPrefix; var onBeforeImport = options.onBeforeImport; @@ -518,7 +522,8 @@ var Zotero_File_Interface = new function() { try { yield translation.translate({ libraryID, - collections: importCollection ? [importCollection.id] : null + collections: importCollection ? [importCollection.id] : null, + linkFiles }); } catch(e) { if (!showProgressWindow) { diff --git a/chrome/content/zotero/import/importWizard.js b/chrome/content/zotero/import/importWizard.js index ccc86de34e..facb7d0d45 100644 --- a/chrome/content/zotero/import/importWizard.js +++ b/chrome/content/zotero/import/importWizard.js @@ -31,6 +31,17 @@ var Zotero_Import_Wizard = { } } + // Update labels + document.getElementById('file-handling-store').label = Zotero.getString( + 'import.fileHandling.store', + Zotero.appName + ); + document.getElementById('file-handling-link').label = Zotero.getString('import.fileHandling.link'); + document.getElementById('file-handling-description').textContent = Zotero.getString( + 'import.fileHandling.description', + Zotero.appName + ); + Zotero.Translators.init(); // async }, @@ -160,20 +171,6 @@ var Zotero_Import_Wizard = { }, - onImportStart: async function () { - if (!this._file) { - let index = document.getElementById('file-list').selectedIndex; - this._file = this._dbs[index].path; - } - this._disableCancel(); - this._wizard.canRewind = false; - this._wizard.canAdvance = false; - await this.doImport({ - createNewCollection: document.getElementById('create-collection-checkbox').hasAttribute('checked') - }); - }, - - onBeforeImport: async function (translation) { // Unrecognized translator if (!translation) { @@ -196,12 +193,22 @@ var Zotero_Import_Wizard = { }, - doImport: async function (options) { + onImportStart: async function () { + if (!this._file) { + let index = document.getElementById('file-list').selectedIndex; + this._file = this._dbs[index].path; + } + this._disableCancel(); + this._wizard.canRewind = false; + this._wizard.canAdvance = false; + try { let result = await Zotero_File_Interface.importFile({ file: this._file, onBeforeImport: this.onBeforeImport.bind(this), - addToLibraryRoot: !options.createNewCollection + addToLibraryRoot: !document.getElementById('create-collection-checkbox') + .hasAttribute('checked'), + linkFiles: document.getElementById('file-handling-radio').selectedIndex == 1 }); // Cancelled by user or due to error diff --git a/chrome/content/zotero/import/importWizard.xul b/chrome/content/zotero/import/importWizard.xul index c4e6240a28..8721aed14f 100644 --- a/chrome/content/zotero/import/importWizard.xul +++ b/chrome/content/zotero/import/importWizard.xul @@ -54,6 +54,16 @@ onpagerewound="return Zotero_Import_Wizard.goToStart()" onpageadvanced="Zotero_Import_Wizard.onImportStart()"> + + + + } */ @@ -161,6 +163,8 @@ Zotero.Attachments = new function(){ var file = Zotero.File.pathToFile(options.file); var parentItemID = options.parentItemID; var collections = options.collections; + var contentType = options.contentType || (yield Zotero.MIME.getMIMETypeFromFile(file)); + var charset = options.charset; var saveOptions = options.saveOptions; if (parentItemID && collections) { @@ -168,12 +172,12 @@ Zotero.Attachments = new function(){ } var title = file.leafName; - var contentType = yield Zotero.MIME.getMIMETypeFromFile(file); var item = yield _addToDB({ file, title, linkMode: this.LINK_MODE_LINKED_FILE, contentType, + charset, parentItemID, collections, saveOptions diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 5b0e2a7f99..e742df5484 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -1282,6 +1282,7 @@ Zotero.Translate.Base.prototype = { * or NULL for default library; * if FALSE, don't save items * @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import + * @param {Boolean} [linkFiles=false] - Save linked files instead of stored files * @returns {Promise} Promise resolved with saved items * when translation complete */ @@ -1321,6 +1322,7 @@ Zotero.Translate.Base.prototype = { } this._collections = options.collections; this._saveAttachments = options.saveAttachments === undefined || options.saveAttachments; + this._linkFiles = options.linkFiles; this._forceTagType = options.forceTagType; this._saveOptions = options.saveOptions; @@ -2431,6 +2433,7 @@ Zotero.Translate.Import.prototype._prepareTranslation = Zotero.Promise.method(fu collections: this._collections, forceTagType: this._forceTagType, attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")], + linkFiles: this._linkFiles, baseURI, saveOptions: Object.assign( { diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js index 1d7b786c9e..b78af7518f 100644 --- a/chrome/content/zotero/xpcom/translation/translate_item.js +++ b/chrome/content/zotero/xpcom/translation/translate_item.js @@ -31,6 +31,7 @@ *
  • libraryID - ID of library in which items should be saved
  • *
  • collections - New collections to create (used during Import translation
  • *
  • attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved
  • + *
  • linkFiles - Save attachments as linked files instead of stored files
  • *
  • forceTagType - Force tags to specified tag type
  • *
  • cookieSandbox - Cookie sandbox for attachment requests
  • *
  • proxy - A proxy to deproxify item URLs
  • @@ -53,6 +54,7 @@ Zotero.Translate.ItemSaver = function(options) { // If group filesEditable==false, don't save attachments this.attachmentMode = Zotero.Libraries.get(this._libraryID).filesEditable ? options.attachmentMode : Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE; + this._linkFiles = options.linkFiles; this._forceTagType = options.forceTagType; this._referrer = options.referrer; this._cookieSandbox = options.cookieSandbox; @@ -605,7 +607,24 @@ Zotero.Translate.ItemSaver.prototype = { title: attachment.title || undefined, collections: !parentItemID ? this._collections : undefined }); - } else { + } + else if (this._linkFiles + // Don't link if it's a path to the current storage directory + && !Zotero.File.directoryContains(Zotero.DataDirectory.getSubdirectory('storage'), file.path)) { + attachment.linkMode = "linked_file"; + newItem = yield Zotero.Attachments.linkFromFile({ + file, + parentItemID, + collections: !parentItemID ? this._collections : undefined + }); + if (attachment.title) { + newItem.setField("title", attachment.title); + } + if (attachment.url) { + newItem.setNote(attachment.url); + } + } + else { if (attachment.url) { attachment.linkMode = "imported_url"; newItem = yield Zotero.Attachments.importSnapshotFromFile({ diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index 2981687f2b..e215ad5c20 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -203,6 +203,7 @@ + diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index cf0474ca45..e01c5909d7 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -722,6 +722,10 @@ fileInterface.exportError = An error occurred while trying to export the selecte fileInterface.importOPML = Import Feeds from OPML fileInterface.OPMLFeedFilter = OPML Feed List +import.fileHandling.store = Copy files to the %S storage folder +import.fileHandling.link = Link to files in original location +import.fileHandling.description = Linked files cannot be synced by %S. + quickCopy.copyAs = Copy as %S quickSearch.mode.titleCreatorYear = Title, Creator, Year diff --git a/chrome/skin/default/zotero/importWizard.css b/chrome/skin/default/zotero/importWizard.css index ecc432fd7b..a690158aac 100644 --- a/chrome/skin/default/zotero/importWizard.css +++ b/chrome/skin/default/zotero/importWizard.css @@ -33,10 +33,32 @@ wizard[currentpageid="page-file-list"] .wizard-header { margin-bottom: 6px; } +#file-handling-options { + margin-top: 2em; +} + +#file-handling-options > label { + font-size: 14px; +} + +#file-handling-options radiogroup { + font-size: 13px; + margin-left: 1em; +} + +#file-handling-options description { + margin-top: .6em; + font-size: 11px; +} + listbox, #result-description, #result-description-html { font-size: 13px; } +#result-description-html { + line-height: 1.5; +} + #result-description-html a { text-decoration: underline; } diff --git a/test/tests/translateTest.js b/test/tests/translateTest.js index 0e19cbb312..30bd5a7ce7 100644 --- a/test/tests/translateTest.js +++ b/test/tests/translateTest.js @@ -505,6 +505,87 @@ describe("Zotero.Translate", function() { assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL); }); + it("import translators should save linked-file attachments with linkFiles: true", async function () { + var testDir = getTestDataDirectory().path; + var file1 = OS.Path.join(testDir, 'test.pdf'); + var file2 = OS.Path.join(testDir, 'test.html'); + var file2URL = "http://example.com"; + var json = [ + { + itemType: "journalArticle", + title: "Parent Item", + attachments: [ + { + title: "PDF", + mimeType: "application/pdf", + path: file1 + }, + { + title: "Snapshot", + mimeType: "text/html", + charset: "utf-8", + url: file2URL, + path: file2 + } + ] + } + ]; + + var newItems = itemsArrayToObject( + await saveItemsThroughTranslator( + "import", + json, + { + linkFiles: true + } + ) + ); + var attachmentIDs = newItems["Parent Item"].getAttachments(); + assert.lengthOf(attachmentIDs, 2); + var attachments = await Zotero.Items.getAsync(attachmentIDs); + assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE); + assert.equal(attachments[0].attachmentContentType, 'application/pdf'); + assert.equal(attachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_FILE); + assert.equal(attachments[1].attachmentContentType, 'text/html'); + assert.equal(attachments[1].attachmentCharset, 'utf-8'); + assert.equal(attachments[1].getNote(), file2URL); + }); + + it("import translators shouldn't save linked-file attachment with linkFiles: true if path is within current storage directory", async function () { + var attachment = await importFileAttachment('test.png'); + var path = attachment.getFilePath(); + var json = [ + { + itemType: "journalArticle", + title: "Parent Item", + attachments: [ + { + title: "PDF", + mimeType: "application/pdf", + path + } + ] + } + ]; + + var newItems = itemsArrayToObject( + await saveItemsThroughTranslator( + "import", + json, + { + linkFiles: true + } + ) + ); + var attachmentIDs = newItems["Parent Item"].getAttachments(); + assert.lengthOf(attachmentIDs, 1); + var attachments = await Zotero.Items.getAsync(attachmentIDs); + assert.equal(attachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE); + var newPath = attachments[0].getFilePath(); + assert.ok(newPath); + assert.notEqual(newPath, path); + }); + it('web translators should set accessDate to current date', function* () { let myItem = { "itemType":"webpage",