diff --git a/CliClient/app/cmd.js b/CliClient/app/cmd.js index d3a8f5071..c09f405fa 100644 --- a/CliClient/app/cmd.js +++ b/CliClient/app/cmd.js @@ -23,6 +23,14 @@ let fileDriver = new FileApiDriverLocal(); let fileApi = new FileApi('/home/laurent/Temp/TestImport', fileDriver); let synchronizer = new Synchronizer(db, fileApi); +function sleep(n) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, n * 1000); + }); +} + function clearDatabase() { let queries = [ 'DELETE FROM changes', @@ -34,6 +42,35 @@ function clearDatabase() { return db.transactionExecBatch(queries); } +async function runTest() { + db.setDebugEnabled(!true); + await db.open({ name: '/home/laurent/Temp/test-sync.sqlite3' }); + + BaseModel.db_ = db; + + await clearDatabase(); + + let folder = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder.id }); + await Note.save({ title: "deux", parent_id: folder.id }); + folder = await Folder.save({ title: "folder2" }); + await Note.save({ title: "trois", parent_id: folder.id }); + + await synchronizer.start(); + + note1 = await Note.load(note1.id); + note1.title = 'un update'; + await Note.save(note1); + + await synchronizer.start(); +} + +runTest(); + + + + + function createRemoteItems() { let a = fileApi; return Promise.all([a.mkdir('test1'), a.mkdir('test2'), a.mkdir('test3')]).then(() => { @@ -56,6 +93,7 @@ async function createLocalItems() { folder = await Folder.save({ title: "folder2" }); await Note.save({ title: "trois", parent_id: folder.id }); + // let folder = await Folder.save({ title: "folder1" }); // await Note.save({ title: "un", parent_id: folder.id }); // await Note.save({ title: "deux", parent_id: folder.id }); @@ -73,16 +111,19 @@ async function createLocalItems() { // await Note.save({ title: "huit", parent_id: folder.id }); } -db.setDebugEnabled(!true); -db.open({ name: '/home/laurent/Temp/test-sync.sqlite3' }).then(() => { - BaseModel.db_ = db; - //return clearDatabase(); - return clearDatabase().then(createLocalItems); -}).then(() => { - return synchronizer.start(); -}).catch((error) => { - console.error(error); -}); + + + +// db.setDebugEnabled(!true); +// db.open({ name: '/home/laurent/Temp/test-sync.sqlite3' }).then(() => { +// BaseModel.db_ = db; +// //return clearDatabase(); +// return clearDatabase().then(createLocalItems); +// }).then(() => { +// return synchronizer.start(); +// }).catch((error) => { +// console.error(error); +// }); diff --git a/ReactNativeClient/src/synchronizer.js b/ReactNativeClient/src/synchronizer.js index 5d42fe762..47ef8dbd2 100644 --- a/ReactNativeClient/src/synchronizer.js +++ b/ReactNativeClient/src/synchronizer.js @@ -10,6 +10,7 @@ import { BaseModel } from 'src/base-model.js'; import { promiseChain } from 'src/promise-utils.js'; import { NoteFolderService } from 'src/services/note-folder-service.js'; import { time } from 'src/time-utils.js'; +import { sprintf } from 'sprintf-js'; //import { promiseWhile } from 'src/promise-utils.js'; import moment from 'moment'; @@ -83,10 +84,6 @@ class Synchronizer { }); } - // isNewerThan(date1, date2) { - // return date1 > date2; - // } - itemByPath(items, path) { for (let i = 0; i < items.length; i++) { if (items[i].path == path) return items[i]; @@ -95,16 +92,14 @@ class Synchronizer { } itemIsSameDate(item, date) { - return Math.abs(item.updatedTime - date) <= 1; + return item.updatedTime === date; } - itemIsNewerThan(item, date) { - if (this.itemIsSameDate(item, date)) return false; + itemIsStrictlyNewerThan(item, date) { return item.updatedTime > date; } - itemIsOlderThan(item, date) { - if (this.itemIsSameDate(item, date)) return false; + itemIsStrictlyOlderThan(item, date) { return item.updatedTime < date; } @@ -177,13 +172,19 @@ class Synchronizer { action.dest = 'remote'; } } else { - if (this.itemIsOlderThan(local, local.syncTime)) continue; + if (this.itemIsStrictlyOlderThan(local, local.syncTime)) continue; - if (this.itemIsOlderThan(remote, local.syncTime)) { + if (this.itemIsStrictlyOlderThan(remote, local.syncTime)) { action.type = 'update'; action.dest = 'remote'; - } else { + } else if (this.itemIsStrictlyNewerThan(remote, local.syncTime)) { action.type = 'conflict'; + action.reason = sprintf('Both remote (%s) and local items (%s) were modified after the last sync (%s).', + moment.unix(remote.updatedTime).toISOString(), + moment.unix(local.updatedTime).toISOString(), + moment.unix(local.syncTime).toISOString() + ); + if (local.type == 'folder') { // For folders, currently we don't completely handle conflicts, we just // we just update the local dir (.folder metadata file) with the remote @@ -199,6 +200,8 @@ class Synchronizer { { type: 'update', dest: 'local' }, ]; } + } else { + continue; // Neither local nor remote item have been changed recently } } @@ -226,7 +229,7 @@ class Synchronizer { action.dest = 'local'; } } else { - if (this.itemIsOlderThan(remote, local.syncTime)) continue; // Already have this version + if (this.itemIsStrictlyOlderThan(remote, local.syncTime)) continue; // Already have this version // Note: no conflict is possible here since if the local item has been // modified since the last sync, it's been processed in the previous loop. action.type = 'update'; @@ -265,7 +268,10 @@ class Synchronizer { if (!action) return Promise.resolve(); + console.info('Sync action: ' + action.type + ' ' + action.dest); + if (action.type == 'conflict') { + console.info(action); } else { let syncItem = action[action.dest == 'local' ? 'remote' : 'local']; @@ -372,6 +378,8 @@ class Synchronizer { for (let i = 0; i < items.length; i++) { await this.processRemoteItem(items[i]); } + + return this.processState('idle'); } start() {