commit e8aec31715c08663a216ad3338694d9fa6cc918e
parent f844d9e46d85c5c502df1e22e7a6caea8caa76cc
Author: Dan Stillman <dstillman@zotero.org>
Date: Tue, 3 May 2016 02:55:26 -0400
Fix various cases of sync errors with read-only libraries
Addresses #983
Diffstat:
3 files changed, 189 insertions(+), 14 deletions(-)
diff --git a/chrome/content/zotero/xpcom/sync/syncEngine.js b/chrome/content/zotero/xpcom/sync/syncEngine.js
@@ -346,7 +346,9 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
+ " didn't exist after conflict resolution");
continue;
}
- yield obj.erase();
+ yield obj.erase({
+ skipEditCheck: true
+ });
}
}.bind(this));
}.bind(this)
@@ -358,6 +360,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
yield Zotero.DB.executeTransaction(function* () {
for (let obj of toDelete) {
yield obj.erase({
+ skipEditCheck: true,
skipDeleteLog: true
});
}
@@ -1348,7 +1351,13 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
// Delete local objects that were deleted remotely
if (toDelete.length) {
Zotero.debug("Deleting remotely deleted synced " + objectTypePlural);
- yield objectsClass.erase(toDelete, { skipDeleteLog: true });
+ yield objectsClass.erase(
+ toDelete,
+ {
+ skipEditCheck: true,
+ skipDeleteLog: true
+ }
+ );
}
// For remotely missing objects that exist locally, reset version, since old
// version will no longer match remote, and mark for upload
diff --git a/chrome/content/zotero/xpcom/sync/syncLocal.js b/chrome/content/zotero/xpcom/sync/syncLocal.js
@@ -1320,23 +1320,13 @@ Zotero.Sync.Data.Local = {
markObjectAsSynced: Zotero.Promise.method(function (obj) {
obj.synced = true;
- return obj.saveTx({
- skipSyncedUpdate: true,
- skipDateModifiedUpdate: true,
- skipClientDateModifiedUpdate: true,
- skipNotifier: true
- });
+ return obj.saveTx({ skipAll: true });
}),
markObjectAsUnsynced: Zotero.Promise.method(function (obj) {
obj.synced = false;
- return obj.saveTx({
- skipSyncedUpdate: true,
- skipDateModifiedUpdate: true,
- skipClientDateModifiedUpdate: true,
- skipNotifier: true
- });
+ return obj.saveTx({ skipAll: true });
}),
diff --git a/test/tests/syncEngineTest.js b/test/tests/syncEngineTest.js
@@ -286,6 +286,182 @@ describe("Zotero.Sync.Data.Engine", function () {
yield assertInCache(obj);
})
+ it("should download items into a new read-only group", function* () {
+ var group = yield createGroup({
+ editable: false,
+ filesEditable: false
+ });
+ var libraryID = group.libraryID;
+ var itemToDelete = yield createDataObject(
+ 'item', { libraryID, synced: true }, { skipEditCheck: true }
+ )
+ var itemToDeleteID = itemToDelete.id;
+
+ ({ engine, client, caller } = yield setup({ libraryID }));
+
+ var headers = {
+ "Last-Modified-Version": 3
+ };
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/settings`,
+ status: 200,
+ headers: headers,
+ json: {
+ tagColors: {
+ value: [
+ {
+ name: "A",
+ color: "#CC66CC"
+ }
+ ],
+ version: 2
+ }
+ }
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/collections?format=versions`,
+ status: 200,
+ headers: headers,
+ json: {
+ "AAAAAAAA": 1
+ }
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/searches?format=versions`,
+ status: 200,
+ headers: headers,
+ json: {
+ "AAAAAAAA": 2
+ }
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/items/top?format=versions&includeTrashed=1`,
+ status: 200,
+ headers: headers,
+ json: {
+ "AAAAAAAA": 3
+ }
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/items?format=versions&includeTrashed=1`,
+ status: 200,
+ headers: headers,
+ json: {
+ "AAAAAAAA": 3,
+ "BBBBBBBB": 3
+ }
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/collections?format=json&collectionKey=AAAAAAAA`,
+ status: 200,
+ headers: headers,
+ json: [
+ makeCollectionJSON({
+ key: "AAAAAAAA",
+ version: 1,
+ name: "A"
+ })
+ ]
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/searches?format=json&searchKey=AAAAAAAA`,
+ status: 200,
+ headers: headers,
+ json: [
+ makeSearchJSON({
+ key: "AAAAAAAA",
+ version: 2,
+ name: "A"
+ })
+ ]
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/items?format=json&itemKey=AAAAAAAA&includeTrashed=1`,
+ status: 200,
+ headers: headers,
+ json: [
+ makeItemJSON({
+ key: "AAAAAAAA",
+ version: 3,
+ itemType: "book",
+ title: "A"
+ })
+ ]
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/items?format=json&itemKey=BBBBBBBB&includeTrashed=1`,
+ status: 200,
+ headers: headers,
+ json: [
+ makeItemJSON({
+ key: "BBBBBBBB",
+ version: 3,
+ itemType: "note",
+ parentItem: "AAAAAAAA",
+ note: "This is a note."
+ })
+ ]
+ });
+ setResponse({
+ method: "GET",
+ url: `groups/${group.id}/deleted?since=0`,
+ status: 200,
+ headers: headers,
+ json: {
+ "items": [itemToDelete.key]
+ }
+ });
+ yield engine.start();
+
+ // Check local library version
+ assert.equal(group.libraryVersion, 3);
+
+ // Make sure local objects exist
+ var setting = Zotero.SyncedSettings.get(libraryID, "tagColors");
+ assert.lengthOf(setting, 1);
+ assert.equal(setting[0].name, 'A');
+ var settingMetadata = Zotero.SyncedSettings.getMetadata(libraryID, "tagColors");
+ assert.equal(settingMetadata.version, 2);
+ assert.isTrue(settingMetadata.synced);
+
+ var obj = Zotero.Collections.getByLibraryAndKey(libraryID, "AAAAAAAA");
+ assert.equal(obj.name, 'A');
+ assert.equal(obj.version, 1);
+ assert.isTrue(obj.synced);
+ yield assertInCache(obj);
+
+ obj = Zotero.Searches.getByLibraryAndKey(libraryID, "AAAAAAAA");
+ assert.equal(obj.name, 'A');
+ assert.equal(obj.version, 2);
+ assert.isTrue(obj.synced);
+ yield assertInCache(obj);
+
+ obj = Zotero.Items.getByLibraryAndKey(libraryID, "AAAAAAAA");
+ assert.equal(obj.getField('title'), 'A');
+ assert.equal(obj.version, 3);
+ assert.isTrue(obj.synced);
+ var parentItemID = obj.id;
+ yield assertInCache(obj);
+
+ obj = Zotero.Items.getByLibraryAndKey(libraryID, "BBBBBBBB");
+ assert.equal(obj.getNote(), 'This is a note.');
+ assert.equal(obj.parentItemID, parentItemID);
+ assert.equal(obj.version, 3);
+ assert.isTrue(obj.synced);
+ yield assertInCache(obj);
+
+ assert.isFalse(Zotero.Items.exists(itemToDeleteID));
+ });
+
it("should upload new full items and subsequent patches", function* () {
({ engine, client, caller } = yield setup());