Fix various cases of sync errors with read-only libraries
Addresses #983
This commit is contained in:
parent
f844d9e46d
commit
e8aec31715
|
@ -346,7 +346,9 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
|
||||||
+ " didn't exist after conflict resolution");
|
+ " didn't exist after conflict resolution");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
yield obj.erase();
|
yield obj.erase({
|
||||||
|
skipEditCheck: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
|
@ -358,6 +360,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
|
||||||
yield Zotero.DB.executeTransaction(function* () {
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
for (let obj of toDelete) {
|
for (let obj of toDelete) {
|
||||||
yield obj.erase({
|
yield obj.erase({
|
||||||
|
skipEditCheck: true,
|
||||||
skipDeleteLog: true
|
skipDeleteLog: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1348,7 +1351,13 @@ Zotero.Sync.Data.Engine.prototype._fullSync = Zotero.Promise.coroutine(function*
|
||||||
// Delete local objects that were deleted remotely
|
// Delete local objects that were deleted remotely
|
||||||
if (toDelete.length) {
|
if (toDelete.length) {
|
||||||
Zotero.debug("Deleting remotely deleted synced " + objectTypePlural);
|
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
|
// For remotely missing objects that exist locally, reset version, since old
|
||||||
// version will no longer match remote, and mark for upload
|
// version will no longer match remote, and mark for upload
|
||||||
|
|
|
@ -1320,23 +1320,13 @@ Zotero.Sync.Data.Local = {
|
||||||
|
|
||||||
markObjectAsSynced: Zotero.Promise.method(function (obj) {
|
markObjectAsSynced: Zotero.Promise.method(function (obj) {
|
||||||
obj.synced = true;
|
obj.synced = true;
|
||||||
return obj.saveTx({
|
return obj.saveTx({ skipAll: true });
|
||||||
skipSyncedUpdate: true,
|
|
||||||
skipDateModifiedUpdate: true,
|
|
||||||
skipClientDateModifiedUpdate: true,
|
|
||||||
skipNotifier: true
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
||||||
markObjectAsUnsynced: Zotero.Promise.method(function (obj) {
|
markObjectAsUnsynced: Zotero.Promise.method(function (obj) {
|
||||||
obj.synced = false;
|
obj.synced = false;
|
||||||
return obj.saveTx({
|
return obj.saveTx({ skipAll: true });
|
||||||
skipSyncedUpdate: true,
|
|
||||||
skipDateModifiedUpdate: true,
|
|
||||||
skipClientDateModifiedUpdate: true,
|
|
||||||
skipNotifier: true
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -286,6 +286,182 @@ describe("Zotero.Sync.Data.Engine", function () {
|
||||||
yield assertInCache(obj);
|
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* () {
|
it("should upload new full items and subsequent patches", function* () {
|
||||||
({ engine, client, caller } = yield setup());
|
({ engine, client, caller } = yield setup());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user