From e683b2be07b3220a610089e765674c0429515c89 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 11 Jul 2017 01:22:07 -0400 Subject: [PATCH] Fix a potential sync error with child attachments If a standalone attachment existed in a collection and then was added to a parent (e.g., via Create Parent Item), and attachment metadata was also changed at the same time (e.g., due to file syncing), the 'collection item must be top level' trigger could throw on another syncing computer. To work around this, remove collections first, then make changes to the parentItemID columns, and then add new collections. --- chrome/content/zotero/xpcom/data/item.js | 192 ++++++++++++----------- test/tests/itemTest.js | 16 ++ 2 files changed, 115 insertions(+), 93 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 226292171e..4b0d4e5c80 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -1382,7 +1382,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { } } - // Parent item + // Parent item (DB update is done below after collection removals) var parentItemKey = this.parentKey; var parentItemID = this.ObjectsClass.getIDFromLibraryAndKey(this.libraryID, parentItemKey) || null; if (this._changed.parentKey) { @@ -1411,9 +1411,6 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { } } else { - let type = Zotero.ItemTypes.getName(itemTypeID); - let Type = type[0].toUpperCase() + type.substr(1); - if (parentItemKey) { if (!parentItemID) { // TODO: clear caches @@ -1494,15 +1491,6 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { } } - // Update DB, if not a note or attachment we're changing below - if (!this._changed.attachmentData && - (!this._changed.note || !this.isNote())) { - let sql = "UPDATE item" + Type + "s SET parentItemID=? " - + "WHERE itemID=?"; - let bindParams = [parentItemID, this.id]; - yield Zotero.DB.queryAsync(sql, bindParams); - } - // Update the counts of the previous and new sources if (oldParentItemID) { reloadParentChildItems[oldParentItemID] = true; @@ -1579,6 +1567,67 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { yield Zotero.DB.queryAsync(sql, itemID); } + // Collections + // + // Only diffing and removal are done here. Additions have to be done below after parentItemID has + // been updated in itemAttachments/itemNotes, since a child item that was made a standalone item and + // added to a collection can't be added to the collection while it still has a parent, and vice + // versa, due to the trigger checks on collectionItems/itemAttachments/itemNotes. + if (this._changed.collections) { + if (libraryType == 'publications') { + throw new Error("Items in My Publications cannot be added to collections"); + } + + let oldCollections = this._previousData.collections || []; + let newCollections = this._collections; + + let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections); + let toRemove = Zotero.Utilities.arrayDiff(oldCollections, newCollections); + + env.collectionsAdded = toAdd; + env.collectionsRemoved = toRemove; + + if (toRemove.length) { + let sql = "DELETE FROM collectionItems WHERE itemID=? AND collectionID IN (" + + toRemove.join(',') + + ")"; + yield Zotero.DB.queryAsync(sql, this.id); + + for (let i=0; i