commit 34c90fd156eb83486459ec0370626a7c7006ff96
parent cbed716424acca3552acf9fc36aa3a616a57fac4
Author: Dan Stillman <dstillman@zotero.org>
Date: Thu, 16 Feb 2017 18:01:53 -0500
If parent item is missing remotely, mark as unsynced and add to queue
This shouldn't happen, but there've been some reports of it.
Diffstat:
2 files changed, 95 insertions(+), 0 deletions(-)
diff --git a/chrome/content/zotero/xpcom/sync/syncEngine.js b/chrome/content/zotero/xpcom/sync/syncEngine.js
@@ -1113,6 +1113,32 @@ Zotero.Sync.Data.Engine.prototype._uploadObjects = Zotero.Promise.coroutine(func
Zotero.logError("Error for " + objectType + " " + jsonBatch[index].key + " in "
+ this.library.name + ":\n\n" + e);
+ // If parent item is missing remotely and it isn't in the queue (which shouldn't happen),
+ // mark it as unsynced and add to queue
+ if (e.code == 400 && objectType == 'item' && data && data.parentItem) {
+ let parentItem = Zotero.Items.getByLibraryAndKey(this.libraryID, data.parentItem);
+ if (!parentItem) {
+ throw new Error(`Item ${this.libraryID}/${jsonBatch[index].key} references parent `
+ + `item ${data.parentItem}, which doesn't exist`);
+ }
+
+ let id = parentItem.id;
+ // If parent item isn't already in queue, mark it as unsynced and add it
+ if (!queue.find(o => o.id == id) && !batch.find(o => o.id == id)) {
+ yield Zotero.Sync.Data.Local.markObjectAsUnsynced(parentItem);
+ Zotero.logError(`Adding parent item ${data.parentItem} to upload queue`);
+ queue.push({
+ id,
+ json: null,
+ tries: 0,
+ failed: false
+ });
+ // Pretend that we were successful so syncing continues
+ numSuccessful++;
+ continue;
+ }
+ }
+
// This shouldn't happen, because the upload request includes a library version and should
// prevent an outdated upload before the object version is checked. If it does, we need to
// do a full sync. This error is checked in handleUploadError().
diff --git a/test/tests/syncEngineTest.js b/test/tests/syncEngineTest.js
@@ -2175,6 +2175,75 @@ describe("Zotero.Sync.Data.Engine", function () {
var result = yield engine._startUpload();
assert.equal(result, engine.UPLOAD_RESULT_OBJECT_CONFLICT);
});
+
+
+ it("should mark local parent item as unsynced if it doesn't exist when uploading child", function* () {
+ ({ engine, client, caller } = yield setup());
+
+ var library = Zotero.Libraries.userLibrary;
+ var libraryID = library.id;
+ var lastLibraryVersion = 5;
+ library.libraryVersion = lastLibraryVersion;
+ yield library.saveTx();
+
+ var item = createUnsavedDataObject('item');
+ // Set the parent item as synced (though this shouldn't happen)
+ item.synced = true;
+ yield item.saveTx();
+ var note = yield createDataObject('item', { itemType: 'note', parentID: item.id });
+
+ var called = 0;
+ server.respond(function (req) {
+ let requestJSON = JSON.parse(req.requestBody);
+
+ if (called == 0) {
+ assert.lengthOf(requestJSON, 1);
+ assert.equal(requestJSON[0].key, note.key);
+ req.respond(
+ 200,
+ {
+ "Last-Modified-Version": lastLibraryVersion
+ },
+ JSON.stringify({
+ successful: {},
+ unchanged: {},
+ failed: {
+ 0: {
+ code: 400,
+ message: `Parent item '${item.key}' doesn't exist`,
+ data: {
+ parentItem: item.key
+ }
+ }
+ }
+ })
+ );
+ }
+ else if (called == 1) {
+ assert.lengthOf(requestJSON, 2);
+ assert.sameMembers(requestJSON.map(o => o.key), [item.key, note.key]);
+ req.respond(
+ 200,
+ {
+ "Last-Modified-Version": ++lastLibraryVersion
+ },
+ JSON.stringify({
+ successful: {
+ 0: item.toResponseJSON(),
+ 1: note.toResponseJSON()
+ },
+ unchanged: {},
+ failed: {}
+ })
+ );
+ }
+ called++;
+ });
+
+ var result = yield engine._startUpload();
+ assert.equal(result, engine.UPLOAD_RESULT_SUCCESS);
+ assert.equal(called, 2);
+ });
});