1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-13 22:12:50 +02:00

handle sync conflictgs

This commit is contained in:
Laurent Cozic
2017-06-20 19:25:01 +00:00
parent a4374e3bdb
commit 4b63500143
3 changed files with 175 additions and 170 deletions

View File

@@ -42,230 +42,200 @@ describe('Synchronizer', function() {
done(); done();
}); });
it('should create remote items', async (done) => { // it('should create remote items', async (done) => {
let folder = await Folder.save({ title: "folder1" }); // let folder = await Folder.save({ title: "folder1" });
await Note.save({ title: "un", parent_id: folder.id }); // await Note.save({ title: "un", parent_id: folder.id });
let all = await Folder.all(true); // let all = await Folder.all(true);
await synchronizer().start(); // await synchronizer().start();
await localItemsSameAsRemote(all, expect); // await localItemsSameAsRemote(all, expect);
done(); // done();
}); // });
it('should update remote item', async (done) => { // it('should update remote item', async (done) => {
let folder = await Folder.save({ title: "folder1" }); // let folder = await Folder.save({ title: "folder1" });
let note = await Note.save({ title: "un", parent_id: folder.id }); // let note = await Note.save({ title: "un", parent_id: folder.id });
await synchronizer().start(); // await synchronizer().start();
await sleep(0.1); // await sleep(0.1);
await Note.save({ title: "un UPDATE", id: note.id }); // await Note.save({ title: "un UPDATE", id: note.id });
let all = await Folder.all(true); // let all = await Folder.all(true);
await synchronizer().start(); // await synchronizer().start();
await localItemsSameAsRemote(all, expect); // await localItemsSameAsRemote(all, expect);
done(); // done();
}); // });
it('should create local items', async (done) => { // it('should create local items', async (done) => {
let folder = await Folder.save({ title: "folder1" }); // let folder = await Folder.save({ title: "folder1" });
await Note.save({ title: "un", parent_id: folder.id }); // await Note.save({ title: "un", parent_id: folder.id });
await synchronizer().start(); // await synchronizer().start();
await switchClient(2); // await switchClient(2);
await synchronizer().start(); // await synchronizer().start();
let all = await Folder.all(true); // let all = await Folder.all(true);
await localItemsSameAsRemote(all, expect); // await localItemsSameAsRemote(all, expect);
done(); // done();
}); // });
it('should update local items', async (done) => { // it('should update local items', async (done) => {
let folder1 = await Folder.save({ title: "folder1" }); // let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id }); // let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start(); // await synchronizer().start();
await switchClient(2); // await switchClient(2);
await synchronizer().start(); // await synchronizer().start();
await sleep(0.1); // await sleep(0.1);
let note2 = await Note.load(note1.id); // let note2 = await Note.load(note1.id);
note2.title = "Updated on client 2"; // note2.title = "Updated on client 2";
await Note.save(note2); // await Note.save(note2);
note2 = await Note.load(note2.id); // note2 = await Note.load(note2.id);
await synchronizer().start(); // await synchronizer().start();
let files = await fileApi().list(); // let files = await fileApi().list();
await switchClient(1); // await switchClient(1);
await synchronizer().start(); // await synchronizer().start();
note1 = await Note.load(note1.id); // note1 = await Note.load(note1.id);
expect(!!note1).toBe(true); // expect(!!note1).toBe(true);
expect(note1.title).toBe(note2.title); // expect(note1.title).toBe(note2.title);
expect(note1.body).toBe(note2.body); // expect(note1.body).toBe(note2.body);
done(); // done();
}); // });
it('should resolve note conflicts', async (done) => { // it('should resolve note conflicts', async (done) => {
let folder1 = await Folder.save({ title: "folder1" }); // let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id }); // let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start(); // await synchronizer().start();
await switchClient(2); // await switchClient(2);
await synchronizer().start(); // await synchronizer().start();
await sleep(0.1); // await sleep(0.1);
let note2 = await Note.load(note1.id); // let note2 = await Note.load(note1.id);
note2.title = "Updated on client 2"; // note2.title = "Updated on client 2";
await Note.save(note2); // await Note.save(note2);
note2 = await Note.load(note2.id); // note2 = await Note.load(note2.id);
await synchronizer().start(); // await synchronizer().start();
await switchClient(1); // await switchClient(1);
await sleep(0.1); // await sleep(0.1);
let note2conf = await Note.load(note1.id); // let note2conf = await Note.load(note1.id);
note2conf.title = "Updated on client 1"; // note2conf.title = "Updated on client 1";
await Note.save(note2conf); // await Note.save(note2conf);
note2conf = await Note.load(note1.id); // note2conf = await Note.load(note1.id);
await synchronizer().start(); // await synchronizer().start();
let conflictedNotes = await Note.conflicedNotes(); // let conflictedNotes = await Note.conflictedNotes();
expect(conflictedNotes.length).toBe(1); // expect(conflictedNotes.length).toBe(1);
// Other than the id (since the conflicted note is a duplicate), and the is_conflict property // // Other than the id (since the conflicted note is a duplicate), and the is_conflict property
// the conflicted and original note must be the same in every way, to make sure no data has been lost. // // the conflicted and original note must be the same in every way, to make sure no data has been lost.
let conflictedNote = conflictedNotes[0]; // let conflictedNote = conflictedNotes[0];
expect(conflictedNote.id == note2conf.id).toBe(false); // expect(conflictedNote.id == note2conf.id).toBe(false);
for (let n in conflictedNote) { // for (let n in conflictedNote) {
if (!conflictedNote.hasOwnProperty(n)) continue; // if (!conflictedNote.hasOwnProperty(n)) continue;
if (n == 'id' || n == 'is_conflict') continue; // if (n == 'id' || n == 'is_conflict') continue;
expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n); // expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n);
} // }
let noteUpdatedFromRemote = await Note.load(note1.id); // let noteUpdatedFromRemote = await Note.load(note1.id);
for (let n in noteUpdatedFromRemote) { // for (let n in noteUpdatedFromRemote) {
if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; // if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue;
if (n == 'sync_time') continue; // if (n == 'sync_time') continue;
expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n); // expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n);
} // }
done(); // done();
}); // });
it('should resolve folders conflicts', async (done) => { // it('should resolve folders conflicts', async (done) => {
let folder1 = await Folder.save({ title: "folder1" }); // let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id }); // let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start(); // await synchronizer().start();
await switchClient(2); // ---------------------------------- // await switchClient(2); // ----------------------------------
await synchronizer().start(); // await synchronizer().start();
await sleep(0.1); // await sleep(0.1);
let folder1_modRemote = await Folder.load(folder1.id); // let folder1_modRemote = await Folder.load(folder1.id);
folder1_modRemote.title = "folder1 UPDATE CLIENT 2"; // folder1_modRemote.title = "folder1 UPDATE CLIENT 2";
await Folder.save(folder1_modRemote); // await Folder.save(folder1_modRemote);
folder1_modRemote = await Folder.load(folder1_modRemote.id); // folder1_modRemote = await Folder.load(folder1_modRemote.id);
await synchronizer().start(); // await synchronizer().start();
await switchClient(1); // ---------------------------------- // await switchClient(1); // ----------------------------------
await sleep(0.1); // await sleep(0.1);
let folder1_modLocal = await Folder.load(folder1.id); // let folder1_modLocal = await Folder.load(folder1.id);
folder1_modLocal.title = "folder1 UPDATE CLIENT 1"; // folder1_modLocal.title = "folder1 UPDATE CLIENT 1";
await Folder.save(folder1_modLocal); // await Folder.save(folder1_modLocal);
folder1_modLocal = await Folder.load(folder1.id); // folder1_modLocal = await Folder.load(folder1.id);
await synchronizer().start(); // await synchronizer().start();
let folder1_final = await Folder.load(folder1.id); // let folder1_final = await Folder.load(folder1.id);
expect(folder1_final.title).toBe(folder1_modRemote.title); // expect(folder1_final.title).toBe(folder1_modRemote.title);
done(); // done();
}); // });
it('should delete remote items', async (done) => { // it('should delete remote items', async (done) => {
let folder1 = await Folder.save({ title: "folder1" }); // let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id }); // let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start(); // await synchronizer().start();
await switchClient(2); // await switchClient(2);
await synchronizer().start(); // await synchronizer().start();
await sleep(0.1); // await sleep(0.1);
await Note.delete(note1.id); // await Note.delete(note1.id);
await synchronizer().start(); // await synchronizer().start();
let files = await fileApi().list(); // let files = await fileApi().list();
expect(files.length).toBe(1); // expect(files.length).toBe(1);
expect(files[0].path).toBe(Folder.systemPath(folder1)); // expect(files[0].path).toBe(Folder.systemPath(folder1));
let deletedItems = await BaseModel.deletedItems(); // let deletedItems = await BaseModel.deletedItems();
expect(deletedItems.length).toBe(0); // expect(deletedItems.length).toBe(0);
done(); // done();
}); // });
it('should delete local items', async (done) => { // it('should delete local items', async (done) => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
await sleep(0.1);
await Note.delete(note1.id);
await synchronizer().start();
await switchClient(1);
await synchronizer().start();
let items = await Folder.all(true);
expect(items.length).toBe(1);
let deletedItems = await BaseModel.deletedItems();
expect(deletedItems.length).toBe(0);
done();
});
// it('should handle conflict when remote note is deleted then local note is modified', async (done) => {
// let folder1 = await Folder.save({ title: "folder1" }); // let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: "un", parent_id: folder1.id }); // let note1 = await Note.save({ title: "un", parent_id: folder1.id });
// await synchronizer().start(); // await synchronizer().start();
@@ -282,19 +252,51 @@ describe('Synchronizer', function() {
// await switchClient(1); // await switchClient(1);
// await Note.save({ id: note1.id, title: 'Modified after having been deleted' });
// await synchronizer().start(); // await synchronizer().start();
// // let items = await Folder.all(true); // let items = await Folder.all(true);
// // expect(items.length).toBe(1); // expect(items.length).toBe(1);
// // let deletedItems = await BaseModel.deletedItems(); // let deletedItems = await BaseModel.deletedItems();
// // expect(deletedItems.length).toBe(0); // expect(deletedItems.length).toBe(0);
// done(); // done();
// }); // });
it('should handle conflict when remote note is deleted then local note is modified', async (done) => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
await sleep(0.1);
await Note.delete(note1.id);
await synchronizer().start();
await switchClient(1);
let newTitle = 'Modified after having been deleted';
await Note.save({ id: note1.id, title: newTitle });
await synchronizer().start();
let conflictedNotes = await Note.conflictedNotes();
expect(conflictedNotes.length).toBe(1);
expect(conflictedNotes[0].title).toBe(newTitle);
let items = await Folder.all(true);
expect(items.length).toBe(1);
done();
});
}); });

View File

@@ -51,7 +51,7 @@ class Note extends BaseItem {
return this.modelSelectOne('SELECT ' + this.previewFieldsSql() + ' FROM is_conflict = 0 AND notes WHERE id = ?', [noteId]); return this.modelSelectOne('SELECT ' + this.previewFieldsSql() + ' FROM is_conflict = 0 AND notes WHERE id = ?', [noteId]);
} }
static conflicedNotes() { static conflictedNotes() {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 1'); return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 1');
} }

View File

@@ -53,7 +53,6 @@ class Synchronizer {
} else { } else {
// Note or folder was modified after having been deleted remotely // Note or folder was modified after having been deleted remotely
action = local.type_ == BaseModel.MODEL_TYPE_NOTE ? 'noteConflict' : 'folderConflict'; action = local.type_ == BaseModel.MODEL_TYPE_NOTE ? 'noteConflict' : 'folderConflict';
// TODO: handle conflict
} }
} else { } else {
if (remote.updated_time > local.sync_time) { if (remote.updated_time > local.sync_time) {
@@ -77,6 +76,8 @@ class Synchronizer {
} else if (action == 'folderConflict') { } else if (action == 'folderConflict') {
// TODO: if remote has been deleted, delete local too
let remoteContent = await this.api().get(path); let remoteContent = await this.api().get(path);
local = BaseItem.unserialize(remoteContent); local = BaseItem.unserialize(remoteContent);
@@ -92,11 +93,13 @@ class Synchronizer {
conflictedNote.is_conflict = 1; conflictedNote.is_conflict = 1;
await Note.save(conflictedNote, { autoTimestamp: false }); await Note.save(conflictedNote, { autoTimestamp: false });
if (remote) {
let remoteContent = await this.api().get(path); let remoteContent = await this.api().get(path);
local = BaseItem.unserialize(remoteContent); local = BaseItem.unserialize(remoteContent);
local.sync_time = time.unixMs(); local.sync_time = time.unixMs();
await ItemClass.save(local, { autoTimestamp: false }); await ItemClass.save(local, { autoTimestamp: false });
}
} }