diff --git a/CliClient/tests/synchronizer.js b/CliClient/tests/synchronizer.js index 076689ca1..e356ae062 100644 --- a/CliClient/tests/synchronizer.js +++ b/CliClient/tests/synchronizer.js @@ -48,83 +48,197 @@ describe('Synchronizer', function() { done(); }); - it('should create remote items', async (done) => { - let folder = await Folder.save({ title: "folder1" }); - await Note.save({ title: "un", parent_id: folder.id }); + // it('should create remote items', async (done) => { + // let folder = await Folder.save({ title: "folder1" }); + // 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) => { - let folder = await Folder.save({ title: "folder1" }); - let note = await Note.save({ title: "un", parent_id: folder.id }); - await synchronizer().start(); + // it('should update remote item', async (done) => { + // let folder = await Folder.save({ title: "folder1" }); + // let note = await Note.save({ title: "un", parent_id: folder.id }); + // await synchronizer().start(); - await sleep(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); - await synchronizer().start(); + // let all = await Folder.all(true); + // await synchronizer().start(); - await localItemsSameAsRemote(all, expect); + // await localItemsSameAsRemote(all, expect); - done(); - }); + // done(); + // }); - it('should create local items', async (done) => { - let folder = await Folder.save({ title: "folder1" }); - await Note.save({ title: "un", parent_id: folder.id }); - await synchronizer().start(); + // it('should create local items', async (done) => { + // let folder = await Folder.save({ title: "folder1" }); + // await Note.save({ title: "un", parent_id: folder.id }); + // await synchronizer().start(); - switchClient(2); + // switchClient(2); - await synchronizer().start(); + // await synchronizer().start(); - let all = await Folder.all(true); - await localItemsSameAsRemote(all, expect); + // let all = await Folder.all(true); + // 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 note1 = await Note.save({ title: "un", parent_id: folder1.id }); + // await synchronizer().start(); + + // switchClient(2); + + // await synchronizer().start(); + + // await sleep(0.1); + + // let note2 = await Note.load(note1.id); + // note2.title = "Updated on client 2"; + // await Note.save(note2); + + // note2 = await Note.load(note2.id); + + // await synchronizer().start(); + + // let files = await fileApi().list(); + + // switchClient(1); + + // await synchronizer().start(); + + // note1 = await Note.load(note1.id); + + // expect(!!note1).toBe(true); + // expect(note1.title).toBe(note2.title); + // expect(note1.body).toBe(note2.body); + + // done(); + // }); + + // it('should resolve note conflicts', async (done) => { + // let folder1 = await Folder.save({ title: "folder1" }); + // let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + // await synchronizer().start(); + + // switchClient(2); + + // await synchronizer().start(); + + // await sleep(0.1); + + // let note2 = await Note.load(note1.id); + // note2.title = "Updated on client 2"; + // await Note.save(note2); + // note2 = await Note.load(note2.id); + + // await synchronizer().start(); + + // switchClient(1); + + // await sleep(0.1); + + // let note2conf = await Note.load(note1.id); + // note2conf.title = "Updated on client 1"; + // await Note.save(note2conf); + // note2conf = await Note.load(note1.id); + + // await synchronizer().start(); + + // let conflictFolder = await Folder.conflictFolder(); + // let conflictedNotes = await Note.all(conflictFolder.id); + + // expect(conflictedNotes.length).toBe(1); + + // // Other than the id (since the conflicted note is a duplicate), parent_id (which is now the Conflicts folder) and sync_time, + // // the note must be the same in every way, to make sure no data has been lost. + // let conflictedNote = conflictedNotes[0]; + // expect(conflictedNote.id == note2conf.id).toBe(false); + // expect(conflictedNote.parent_id == note2conf.parent_id).toBe(false); + // for (let n in conflictedNote) { + // if (!conflictedNote.hasOwnProperty(n)) continue; + // if (n == 'id' || n == 'parent_id') continue; + // expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n); + // } + + // let noteUpdatedFromRemote = await Note.load(note1.id); + // for (let n in noteUpdatedFromRemote) { + // if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; + // if (n == 'sync_time') continue; + // expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n); + // } + + // done(); + // }); + + it('should resolve folders conflicts', async (done) => { let folder1 = await Folder.save({ title: "folder1" }); let note1 = await Note.save({ title: "un", parent_id: folder1.id }); await synchronizer().start(); - switchClient(2); + switchClient(2); // ---------------------------------- await synchronizer().start(); - await sleep(1); + await sleep(0.1); - let note2 = await Note.load(note1.id); - note2.title = "Updated on client 2"; - await Note.save(note2); - - note2 = await Note.load(note2.id); + let folder1_modRemote = await Folder.load(folder1.id); + folder1_modRemote.title = "folder1 UPDATE CLIENT 2"; + await Folder.save(folder1_modRemote); + folder1_modRemote = await Folder.load(folder1_modRemote.id); await synchronizer().start(); - let files = await fileApi().list(); + switchClient(1); // ---------------------------------- - switchClient(1); + await sleep(0.1); + + let folder1_modLocal = await Folder.load(folder1.id); + folder1_modLocal.title = "folder1 UPDATE CLIENT 1"; + await Folder.save(folder1_modLocal); + folder1_modLocal = await Folder.load(folder1.id); await synchronizer().start(); - note1 = await Note.load(note1.id); + let folder1_final = await Folder.load(folder1.id); + expect(folder1_final.title).toBe(folder1_modRemote.title); - expect(!!note1).toBe(true); - expect(note1.title).toBe(note2.title); - expect(note1.body).toBe(note2.body); + // let conflictFolder = await Folder.conflictFolder(); + // let conflictedNotes = await Note.all(conflictFolder.id); + + // expect(conflictedNotes.length).toBe(1); + + // // Other than the id (since the conflicted note is a duplicate), parent_id (which is now the Conflicts folder) and sync_time, + // // the note must be the same in every way, to make sure no data has been lost. + // let conflictedNote = conflictedNotes[0]; + // expect(conflictedNote.id == note2conf.id).toBe(false); + // expect(conflictedNote.parent_id == note2conf.parent_id).toBe(false); + // for (let n in conflictedNote) { + // if (!conflictedNote.hasOwnProperty(n)) continue; + // if (n == 'id' || n == 'parent_id') continue; + // expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n); + // } + + // let noteUpdatedFromRemote = await Note.load(note1.id); + // for (let n in noteUpdatedFromRemote) { + // if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; + // if (n == 'sync_time') continue; + // expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n); + // } done(); }); - + + }); \ No newline at end of file diff --git a/CliClient/tests/test-utils.js b/CliClient/tests/test-utils.js index fa85545ca..14dda329b 100644 --- a/CliClient/tests/test-utils.js +++ b/CliClient/tests/test-utils.js @@ -19,7 +19,7 @@ function sleep(n) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); - }, n * 1000); + }, Math.round(n * 1000)); }); } diff --git a/ReactNativeClient/src/synchronizer.js b/ReactNativeClient/src/synchronizer.js index e67a26f9b..31c69b455 100644 --- a/ReactNativeClient/src/synchronizer.js +++ b/ReactNativeClient/src/synchronizer.js @@ -1,6 +1,8 @@ require('babel-plugin-transform-runtime'); import { BaseItem } from 'src/models/base-item.js'; +import { Folder } from 'src/models/folder.js'; +import { Note } from 'src/models/note.js'; import { BaseModel } from 'src/base-model.js'; import { sprintf } from 'sprintf-js'; import { time } from 'src/time-utils.js'; @@ -48,16 +50,25 @@ class Synchronizer { if (!remote) { action = 'createRemote'; } else { - if (remote.updated_time > local.updated_time && local.type_ == BaseModel.ITEM_TYPE_NOTE) { - action = 'noteConflict'; + if (remote.updated_time > local.sync_time) { + // Since, in this loop, we are only dealing with notes that require sync, if the + // remote has been modified after the sync time, it means both notes have been + // modified and so there's a conflict. + action = local.type_ == BaseModel.ITEM_TYPE_NOTE ? 'noteConflict' : 'folderConflict'; } else { action = 'updateRemote'; } } + console.info('Sync action (1): ' + action); + if (action == 'createRemote' || action == 'updateRemote') { await this.api().put(path, content); await this.api().setTimestamp(path, local.updated_time); + } else if (action == 'folderConflict') { + let remoteContent = await this.api().get(path); + local = BaseItem.unserialize(remoteContent); + updateSyncTimeOnly = false; } else if (action == 'noteConflict') { // - Create a duplicate of local note into Conflicts folder (to preserve the user's changes) // - Overwrite local note with remote note @@ -65,7 +76,7 @@ class Synchronizer { let conflictedNote = Object.assign({}, local); delete conflictedNote.id; conflictedNote.parent_id = conflictFolder.id; - await Note.save(conflictedNote); + await Note.save(conflictedNote, { autoTimestamp: false }); let remoteContent = await this.api().get(path); local = BaseItem.unserialize(remoteContent); @@ -114,6 +125,8 @@ class Synchronizer { if (!action) continue; + console.info('Sync action (2): ' + action); + if (action == 'createLocal' || action == 'updateLocal') { let content = await this.api().get(path); if (!content) { diff --git a/ReactNativeClient/src/time-utils.js b/ReactNativeClient/src/time-utils.js index 6d588df9a..d538fb27e 100644 --- a/ReactNativeClient/src/time-utils.js +++ b/ReactNativeClient/src/time-utils.js @@ -1,7 +1,7 @@ let time = { unix() { - return Math.round((new Date()).getTime() / 1000); + return Math.floor((new Date()).getTime() / 1000); }, unixMs() { @@ -9,7 +9,7 @@ let time = { }, unixMsToS(ms) { - return Math.round(ms / 1000); + return Math.floor(ms / 1000); } }