www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 3fc09add3a89d5db70547d942214b3ff6e35b755
parent 6e2d1f683ab64fac0275b74cfc6395d3ea219fb9
Author: Dan Stillman <dstillman@zotero.org>
Date:   Sat, 23 May 2015 04:25:47 -0400

Attachment fixes

Change all attachment functions to take parameter objects, including a
'collections' property to assign collections. (Previously, calling code
assigned collections separately, which required a nested transaction,
which is no longer possible.)

Fixes #723, Can't attach files by dragging

Diffstat:
Mchrome/content/zotero/xpcom/attachments.js | 136+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mchrome/content/zotero/xpcom/collectionTreeView.js | 39+++++++++++++++++++--------------------
Mchrome/content/zotero/xpcom/data/item.js | 9++++-----
Mchrome/content/zotero/xpcom/file.js | 3+++
Mchrome/content/zotero/xpcom/itemTreeView.js | 43++++++++++++++++++++++---------------------
Mchrome/content/zotero/zoteroPane.js | 38++++++++++++++++++++++----------------
Mtest/tests/attachmentsTest.js | 47+++++++++++++++++++++++++++++++++++++++++++++--
Mtest/tests/itemTreeViewTest.js | 34++++++++++++++++++++++++++++++++++
Mtest/tests/recognizePDFTest.js | 12++++++++----
9 files changed, 224 insertions(+), 137 deletions(-)

diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js @@ -34,15 +34,29 @@ Zotero.Attachments = new function(){ /** + * @param {Object} options + * @param {nsIFile} [options.file] - File to add + * @param {Integer} [options.libraryID] + * @param {Integer[]|String[]} [options.parentItemID] - Parent item to add item to + * @param {Integer[]} [options.collections] - Collection keys or ids to add new item to * @return {Promise} */ - this.importFromFile = Zotero.Promise.coroutine(function* (file, parentItemID, libraryID) { + this.importFromFile = Zotero.Promise.coroutine(function* (options) { Zotero.debug('Importing attachment from file'); + var libraryID = options.libraryID; + var file = options.file; + var parentItemID = options.parentItemID; + var collections = options.collections; + var newName = Zotero.File.getValidFileName(file.leafName); if (!file.isFile()) { - throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()"); + throw new Error("'" + file.leafName + "' must be a file"); + } + + if (parentItemID && collections) { + throw new Error("parentItemID and collections cannot both be provided"); } var itemID, newFile, contentType; @@ -60,11 +74,14 @@ Zotero.Attachments = new function(){ attachmentItem.setField('title', newName); attachmentItem.parentID = parentItemID; attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_FILE; + if (collections) { + attachmentItem.setCollections(collections); + } itemID = yield attachmentItem.save(); attachmentItem = yield Zotero.Items.getAsync(itemID); // Create directory for attachment files within storage directory - var destDir = yield this.createDirectoryForItem(attachmentItem); + destDir = yield this.createDirectoryForItem(attachmentItem); // Point to copied file newFile = destDir.clone(); @@ -105,28 +122,34 @@ Zotero.Attachments = new function(){ /** + * @param {nsIFile} [options.file] + * @param {Integer[]|String[]} [options.parentItemID] - Parent item to add item to + * @param {Integer[]} [options.collections] - Collection keys or ids to add new item to * @return {Promise} */ - this.linkFromFile = Zotero.Promise.coroutine(function* (file, parentItemID) { + this.linkFromFile = Zotero.Promise.coroutine(function* (options) { Zotero.debug('Linking attachment from file'); + var file = options.file; + var parentItemID = options.parentItemID; + var collections = options.collections; + + if (parentItemID && collections) { + throw new Error("parentItemID and collections cannot both be provided"); + } + var title = file.leafName; var contentType = yield Zotero.MIME.getMIMETypeFromFile(file); - var itemID; - - return Zotero.DB.executeTransaction(function* () { - itemID = yield _addToDB({ - file: file, - title: title, - linkMode: this.LINK_MODE_LINKED_FILE, - contentType: contentType, - parentItemID: parentItemID - }); - }.bind(this)) - .then(function () { - return _postProcessFile(itemID, file, contentType); - }) - .then(() => itemID); + var itemID = yield _addToDB({ + file: file, + title: title, + linkMode: this.LINK_MODE_LINKED_FILE, + contentType: contentType, + parentItemID: parentItemID, + collections: collections + }); + yield _postProcessFile(itemID, file, contentType); + return itemID; }); @@ -202,7 +225,7 @@ Zotero.Attachments = new function(){ /** - * @param {Object} options - 'url', 'parentItemID', 'parentCollectionIDs', 'title', + * @param {Object} options - 'url', 'parentItemID', 'collections', 'title', * 'fileBaseName', 'contentType', 'cookieSandbox' * @return {Promise<Zotero.Item>} - A promise for the created attachment item */ @@ -212,17 +235,14 @@ Zotero.Attachments = new function(){ var libraryID = options.libraryID; var url = options.url; var parentItemID = options.parentItemID; - var parentCollectionIDs = options.parentCollectionIDs; + var collections = options.collections; var title = options.title; var fileBaseName = options.forceFileBaseName; var contentType = options.contentType; var cookieSandbox = options.cookieSandbox; - if (parentItemID && parentCollectionIDs) { - let msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.importFromURL()"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - parentCollectionIDs = undefined; + if (parentItemID && collections) { + throw new Error("parentItemID and collections cannot both be provided"); } // Throw error on invalid URLs @@ -246,7 +266,7 @@ Zotero.Attachments = new function(){ document: browser.contentDocument, parentItemID: parentItemID, title: title, - parentCollectionIDs: parentCollectionIDs + collections: collections }) .then(function (attachmentItem) { Zotero.Browser.deleteHiddenBrowser(browser); @@ -329,6 +349,9 @@ Zotero.Attachments = new function(){ attachmentItem.parentID = parentItemID; attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL; attachmentItem.attachmentContentType = contentType; + if (collections) { + attachmentItem.setCollections(collections); + } var itemID = yield attachmentItem.save(); // Create a new folder for this item in the storage directory @@ -343,15 +366,6 @@ Zotero.Attachments = new function(){ destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL ); yield attachmentItem.save(); - - // Add to collections - if (parentCollectionIDs) { - var ids = Zotero.flattenArguments(parentCollectionIDs); - for (let i=0; i<ids.length; i++) { - let col = yield Zotero.Collections.getAsync(ids[i]); - yield col.addItem(itemID); - } - } }.bind(this)) .catch(function (e) { Zotero.debug(e, 1); @@ -482,7 +496,7 @@ Zotero.Attachments = new function(){ /** * TODO: what if called on file:// document? * - * @param {Object} options - 'document', 'parentItemID', 'parentCollectionIDs' + * @param {Object} options - 'document', 'parentItemID', 'collections' * @return {Promise} */ this.linkFromDocument = Zotero.Promise.coroutine(function* (options) { @@ -490,13 +504,10 @@ Zotero.Attachments = new function(){ var document = options.document; var parentItemID = options.parentItemID; - var parentCollectionIDs = options.parentCollectionIDs; + var collections = options.collections; - if (parentItemID && parentCollectionIDs) { - let msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.linkFromDocument()"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - parentCollectionIDs = undefined; + if (parentItemID && collections) { + throw new Error("parentItemID and collections cannot both be provided"); } var url = document.location.href; @@ -511,17 +522,9 @@ Zotero.Attachments = new function(){ linkMode: this.LINK_MODE_LINKED_URL, contentType: contentType, charset: document.characterSet, - parentItemID: parentItemID + parentItemID: parentItemID, + collections: collections }); - - // Add to collections - if (parentCollectionIDs) { - var ids = Zotero.flattenArguments(parentCollectionIDs); - for (let i=0; i<ids.length; i++) { - let col = yield Zotero.Collections.getAsync(id); - yield col.addItem(itemID); - } - } }.bind(this)); // Run the indexer asynchronously @@ -542,7 +545,7 @@ Zotero.Attachments = new function(){ /** * Save a snapshot -- uses synchronous WebPageDump or asynchronous saveURI() * - * @param {Object} options - 'libraryID', 'document', 'parentItemID', 'forceTitle', 'parentCollectionIDs' + * @param {Object} options - 'libraryID', 'document', 'parentItemID', 'forceTitle', 'collections' * @return {Promise<Zotero.Item>} - A promise for the created attachment item */ this.importFromDocument = Zotero.Promise.coroutine(function* (options) { @@ -552,13 +555,10 @@ Zotero.Attachments = new function(){ var document = options.document; var parentItemID = options.parentItemID; var title = options.title; - var parentCollectionIDs = options.parentCollectionIDs; + var collections = options.collections; - if (parentItemID && parentCollectionIDs) { - var msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.importFromDocument()"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - parentCollectionIDs = undefined; + if (parentItemID && collections) { + throw new Error("parentItemID and parentCollectionIDs cannot both be provided"); } var url = document.location.href; @@ -644,6 +644,9 @@ Zotero.Attachments = new function(){ attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL; attachmentItem.attachmentCharset = document.characterSet; attachmentItem.attachmentContentType = contentType; + if (collections) { + attachmentItem.setCollections(collections); + } var itemID = yield attachmentItem.save(); // Create a new folder for this item in the storage directory @@ -657,15 +660,6 @@ Zotero.Attachments = new function(){ destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL ); yield attachmentItem.save(); - - // Add to collections - if (parentCollectionIDs) { - let ids = Zotero.flattenArguments(parentCollectionIDs); - for (let i=0; i<ids.length; i++) { - let col = yield Zotero.Collections.getAsync(ids[i]); - yield col.addItem(itemID); - } - } }.bind(this)) .catch(function (e) { Zotero.debug(e, 1); @@ -1286,6 +1280,7 @@ Zotero.Attachments = new function(){ var contentType = options.contentType; var charset = options.charset; var parentItemID = options.parentItemID; + var collections = options.collections; return Zotero.DB.executeTransaction(function* () { var attachmentItem = new Zotero.Item('attachment'); @@ -1312,6 +1307,9 @@ Zotero.Attachments = new function(){ attachmentItem.attachmentLinkMode = linkMode; attachmentItem.attachmentContentType = contentType; attachmentItem.attachmentCharset = charset; + if (collections) { + attachmentItem.setCollections(collections); + } yield attachmentItem.save(); return attachmentItem.id; diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -2007,29 +2007,28 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r // Otherwise file, so fall through } - yield Zotero.DB.executeTransaction(function* () { - if (dropEffect == 'link') { - var itemID = yield Zotero.Attachments.linkFromFile(file); - } - else { - var itemID = yield Zotero.Attachments.importFromFile(file, false, targetLibraryID); - // If moving, delete original file - if (dragData.dropEffect == 'move') { - try { - file.remove(false); - } - catch (e) { - Components.utils.reportError("Error deleting original file " + file.path + " after drag"); - } + if (dropEffect == 'link') { + var itemID = yield Zotero.Attachments.linkFromFile({ + file: file, + collections: [parentCollectionID] + }); + } + else { + var itemID = yield Zotero.Attachments.importFromFile({ + file: file, + libraryID: targetLibraryID, + collections: [parentCollectionID] + }); + // If moving, delete original file + if (dragData.dropEffect == 'move') { + try { + file.remove(false); } - } - if (parentCollectionID) { - var col = yield Zotero.Collections.getAsync(parentCollectionID); - if (col) { - col.addItem(itemID); + catch (e) { + Components.utils.reportError("Error deleting original file " + file.path + " after drag"); } } - }); + } } } finally { diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js @@ -2135,7 +2135,7 @@ Zotero.Item.prototype.getFile = function () { try { var file = Components.classes["@mozilla.org/file/local;1"]. createInstance(Components.interfaces.nsILocalFile); - file.persistentDescriptor = row.path; + file.persistentDescriptor = path; } catch (e) { Zotero.debug('Invalid persistent descriptor', 2); @@ -2153,7 +2153,7 @@ Zotero.Item.prototype.getFile = function () { createInstance(Components.interfaces.nsILocalFile); try { - file.persistentDescriptor = row.path; + file.persistentDescriptor = path; } catch (e) { // See if this is an old relative path (deprecated) @@ -2161,7 +2161,7 @@ Zotero.Item.prototype.getFile = function () { try { var refDir = (row.linkMode == this.LINK_MODE_LINKED_FILE) ? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory(); - file.setRelativeDescriptor(refDir, row.path); + file.setRelativeDescriptor(refDir, path); } catch (e) { Zotero.debug('Invalid relative descriptor', 2); @@ -2177,8 +2177,7 @@ Zotero.Item.prototype.getFile = function () { /** * Get the absolute file path for the attachment * - * @return {Promise<string|false>} - A promise for either the absolute file path of the attachment - * or false for invalid paths + * @return {string|false} - The absolute file path of the attachment, or false for invalid paths */ Zotero.Item.prototype.getFilePath = function () { if (!this.isAttachment()) { diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js @@ -255,6 +255,9 @@ Zotero.File = new function(){ * @return {Promise} A promise that is resolved with the contents of the source */ this.getBinaryContentsAsync = function (source, maxLength) { + if (typeof source == 'string') { + source = this.pathToFile(source); + } var deferred = Zotero.Promise.defer(); NetUtil.asyncFetch(source, function(inputStream, status) { if (!Components.isSuccessCode(status)) { diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js @@ -3014,8 +3014,8 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or var parentItemID = false; var parentCollectionID = false; - var treerow = this.getRow(row); if (orient == 0) { + let treerow = this.getRow(row); parentItemID = treerow.ref.id } else if (collectionTreeRow.isCollection()) { @@ -3073,29 +3073,30 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or // Otherwise file, so fall through } - yield Zotero.DB.executeTransaction(function* () { - if (dropEffect == 'link') { - var itemID = yield Zotero.Attachments.linkFromFile(file, parentItemID); - } - else { - var itemID = yield Zotero.Attachments.importFromFile(file, parentItemID, targetLibraryID); - // If moving, delete original file - if (dragData.dropEffect == 'move') { - try { - file.remove(false); - } - catch (e) { - Components.utils.reportError("Error deleting original file " + file.path + " after drag"); - } + if (dropEffect == 'link') { + var itemID = yield Zotero.Attachments.linkFromFile({ + file: file, + parentItemID: parentItemID, + collections: parentCollectionID ? [parentCollectionID] : undefined + }); + } + else { + var itemID = yield Zotero.Attachments.importFromFile({ + file: file, + libraryID: targetLibraryID, + parentItemID: parentItemID, + collections: parentCollectionID ? [parentCollectionID] : undefined + }); + // If moving, delete original file + if (dragData.dropEffect == 'move') { + try { + file.remove(false); } - } - if (parentCollectionID) { - var col = yield Zotero.Collections.getAsync(parentCollectionID); - if (col) { - yield col.addItem(itemID); + catch (e) { + Components.utils.reportError("Error deleting original file " + file.path + " after drag"); } } - }); + } } } finally { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js @@ -58,7 +58,6 @@ var ZoteroPane = new function() this.clearItemsPaneMessage = clearItemsPaneMessage; this.contextPopupShowing = contextPopupShowing; this.openNoteWindow = openNoteWindow; - this.addAttachmentFromDialog = addAttachmentFromDialog; this.viewSelectedAttachment = viewSelectedAttachment; this.showAttachmentNotFoundDialog = showAttachmentNotFoundDialog; this.reportErrors = reportErrors; @@ -3227,8 +3226,7 @@ var ZoteroPane = new function() }); - function addAttachmentFromDialog(link, id) - { + this.addAttachmentFromDialog = Zotero.Promise.coroutine(function* (link, parentItemID) { if (!this.canEdit()) { this.displayCannotEditLibraryMessage(); return; @@ -3265,25 +3263,33 @@ var ZoteroPane = new function() if(fp.show() == nsIFilePicker.returnOK) { + if (!parentItemID) { + var collection = this.getSelectedCollection(); + } + var files = fp.files; while (files.hasMoreElements()){ var file = files.getNext(); file.QueryInterface(Components.interfaces.nsILocalFile); - var attachmentID; - if(link) - attachmentID = Zotero.Attachments.linkFromFile(file, id); - else - attachmentID = Zotero.Attachments.importFromFile(file, id, libraryID); - - if(attachmentID && !id) - { - var c = this.getSelectedCollection(); - if(c) - c.addItem(attachmentID); + let attachmentID; + if (link) { + attachmentID = yield Zotero.Attachments.linkFromFile({ + file: file, + parentItemID: parentItemID, + collections: collection ? [collection] : undefined + }); + } + else { + attachmentID = yield Zotero.Attachments.importFromFile({ + file: file, + libraryID: libraryID, + parentItemID: parentItemID, + collections: collection ? [collection] : undefined + }); } } } - } + }); this.addItemFromPage = function (itemType, saveSnapshot, row) { @@ -3398,7 +3404,7 @@ var ZoteroPane = new function() let item = yield Zotero.Attachments.importFromDocument({ libraryID: libraryID, document: doc, - parentCollectionIDs: [collectionID] + collections: [collectionID] }); yield this.selectItem(item.id); diff --git a/test/tests/attachmentsTest.js b/test/tests/attachmentsTest.js @@ -27,7 +27,10 @@ describe("Zotero.Attachments", function() { var parentItemID = yield item.saveTx(); // Create attachment and compare content - var itemID = yield Zotero.Attachments.importFromFile(tmpFile, parentItemID); + var itemID = yield Zotero.Attachments.importFromFile({ + file: tmpFile, + parentItemID: parentItemID + }); var item = yield Zotero.Items.getAsync(itemID); var storedFile = item.getFile(); assert.equal((yield Zotero.File.getContentsAsync(storedFile)), contents); @@ -36,6 +39,43 @@ describe("Zotero.Attachments", function() { yield Zotero.Items.erase(itemID); }); + it("should create a top-level attachment from a PNG file", function* () { + var file = getTestDataDirectory(); + file.append('test.png'); + var contents = yield Zotero.File.getBinaryContentsAsync(file); + + // Create attachment and compare content + var itemID = yield Zotero.Attachments.importFromFile({ + file: file + }); + var item = yield Zotero.Items.getAsync(itemID); + var storedFile = item.getFile(); + assert.equal((yield Zotero.File.getBinaryContentsAsync(storedFile)), contents); + + // Clean up + yield Zotero.Items.erase(itemID); + }); + + it("should create a top-level attachment from a PNG file in a collection", function* () { + var file = getTestDataDirectory(); + file.append('test.png'); + var contents = yield Zotero.File.getBinaryContentsAsync(file); + + var collection = yield createDataObject('collection'); + + // Create attachment and compare content + var itemID = yield Zotero.Attachments.importFromFile({ + file: file, + collections: [collection.id] + }); + var item = yield Zotero.Items.getAsync(itemID); + var storedFile = item.getFile(); + assert.equal((yield Zotero.File.getBinaryContentsAsync(storedFile)), contents); + + // Clean up + yield Zotero.Items.erase(itemID); + }); + it("should create a child attachment from a PNG file", function* () { var file = getTestDataDirectory(); file.append('test.png'); @@ -46,7 +86,10 @@ describe("Zotero.Attachments", function() { var parentItemID = yield item.saveTx(); // Create attachment and compare content - var itemID = yield Zotero.Attachments.importFromFile(file, parentItemID); + var itemID = yield Zotero.Attachments.importFromFile({ + file: file, + parentItemID: parentItemID + }); var item = yield Zotero.Items.getAsync(itemID); var storedFile = item.getFile(); assert.equal((yield Zotero.File.getBinaryContentsAsync(storedFile)), contents); diff --git a/test/tests/itemTreeViewTest.js b/test/tests/itemTreeViewTest.js @@ -162,4 +162,38 @@ describe("Zotero.ItemTreeView", function() { assert.equal(selected[0], id); }); }) + + describe("#drop()", function () { + it("should create a top-level attachment when a file is dragged", function* () { + var file = getTestDataDirectory(); + file.append('test.png'); + + var deferred = Zotero.Promise.defer(); + itemsView.addEventListener('select', () => deferred.resolve()); + + itemsView.drop(0, -1, { + dropEffect: 'copy', + effectAllowed: 'copy', + types: { + contains: function (type) { + return type == 'application/x-moz-file'; + } + }, + mozItemCount: 1, + mozGetDataAt: function (type, i) { + if (type == 'application/x-moz-file' && i == 0) { + return file; + } + } + }) + + yield deferred.promise; + var items = itemsView.getSelectedItems(); + var path = yield items[0].getFilePathAsync(); + assert.equal( + (yield Zotero.File.getBinaryContentsAsync(path)), + (yield Zotero.File.getBinaryContentsAsync(file)) + ); + }) + }); }) diff --git a/test/tests/recognizePDFTest.js b/test/tests/recognizePDFTest.js @@ -19,12 +19,14 @@ describe.skip("PDF Recognition", function() { win.close(); }); - it("should recognize a PDF with a DOI", function() { + it("should recognize a PDF with a DOI", function* () { this.timeout(30000); // Import the PDF var testdir = getTestDataDirectory(); testdir.append("recognizePDF_test_DOI.pdf"); - var id = Zotero.Attachments.importFromFile(testdir); + var id = yield Zotero.Attachments.importFromFile({ + file: testdir + }); // Recognize the PDF win.ZoteroPane.selectItem(id); @@ -37,14 +39,16 @@ describe.skip("PDF Recognition", function() { }); }); - it("should recognize a PDF without a DOI", function() { + it("should recognize a PDF without a DOI", function* () { if (Zotero.noUserInput) this.skip(); // CAPTCHAs make this fail this.timeout(30000); // Import the PDF var testdir = getTestDataDirectory(); testdir.append("recognizePDF_test_GS.pdf"); - var id = Zotero.Attachments.importFromFile(testdir); + var id = yield Zotero.Attachments.importFromFile({ + file: testdir + }); // Recognize the PDF win.ZoteroPane.selectItem(id);