1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-02-01 19:15:01 +02:00

Handle sync conflicts

This commit is contained in:
Laurent Cozic 2017-06-19 18:58:49 +00:00
parent 02ff02a9d9
commit baa1a01ed0
7 changed files with 51 additions and 9 deletions

View File

View File

@ -3,6 +3,7 @@ import { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi,
import { createFoldersAndNotes } from 'test-data.js';
import { Folder } from 'src/models/folder.js';
import { Note } from 'src/models/note.js';
import { Setting } from 'src/models/setting.js';
import { BaseItem } from 'src/models/base-item.js';
import { BaseModel } from 'src/base-model.js';
@ -125,5 +126,5 @@ describe('Synchronizer', function() {
done();
});
});

View File

@ -4,6 +4,7 @@ import { DatabaseDriverNode } from 'src/database-driver-node.js';
import { BaseModel } from 'src/base-model.js';
import { Folder } from 'src/models/folder.js';
import { Note } from 'src/models/note.js';
import { Setting } from 'src/models/setting.js';
import { BaseItem } from 'src/models/base-item.js';
import { Synchronizer } from 'src/synchronizer.js';
import { FileApi } from 'src/file-api.js';
@ -47,7 +48,9 @@ function setupDatabase(id = null) {
if (id === null) id = currentClient_;
if (databases_[id]) {
return clearDatabase(id);
return clearDatabase(id).then(() => {
return Setting.load();
});
}
const filePath = __dirname + '/data/test-' + id + '.sqlite';

View File

@ -1,6 +1,7 @@
import { BaseModel } from 'src/base-model.js';
import { Note } from 'src/models/note.js';
import { Folder } from 'src/models/folder.js';
import { Setting } from 'src/models/setting.js';
import { folderItemFilename } from 'src/string-utils.js'
import { Database } from 'src/database.js';
import { time } from 'src/time-utils.js';
@ -129,9 +130,10 @@ class BaseItem extends BaseModel {
}
static itemsThatNeedSync(limit = 100) {
return Folder.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit).then((items) => {
let conflictFolderId = Setting.value('sync.conflictFolderId');
return Folder.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time AND id != ? LIMIT ' + limit, [conflictFolderId]).then((items) => {
if (items.length) return { hasMore: true, items: items };
return Note.modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time LIMIT ' + limit).then((items) => {
return Note.modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND parent_id != ? LIMIT ' + limit, [conflictFolderId]).then((items) => {
return { hasMore: items.length >= limit, items: items };
});
});

View File

@ -2,6 +2,7 @@ import { BaseModel } from 'src/base-model.js';
import { Log } from 'src/log.js';
import { promiseChain } from 'src/promise-utils.js';
import { Note } from 'src/models/note.js';
import { Setting } from 'src/models/setting.js';
import { folderItemFilename } from 'src/string-utils.js'
import { _ } from 'src/locale.js';
import moment from 'moment';
@ -80,7 +81,18 @@ class Folder extends BaseItem {
let notes = await Note.modelSelectAll('SELECT * FROM notes');
return folders.concat(notes);
}
static conflictFolder() {
let folderId = Setting.value('sync.conflictFolderId');
if (!folderId) {
return Folder.save({ title: _('Conflicts') }).then((folder) => {
Setting.setValue('sync.conflictFolderId', folder.id);
return folder;
});
}
return Folder.load(folderId);
}
static save(o, options = null) {

View File

@ -33,6 +33,8 @@ class Setting extends BaseModel {
}
static setValue(key, value) {
if (!this.cache_) throw new Error('Settings have not been initialized!');
for (let i = 0; i < this.cache_.length; i++) {
if (this.cache_[i].key == key) {
if (this.cache_[i].value === value) return;
@ -49,6 +51,8 @@ class Setting extends BaseModel {
}
static value(key) {
if (!this.cache_) throw new Error('Settings have not been initialized!');
for (let i = 0; i < this.cache_.length; i++) {
if (this.cache_[i].key == key) {
return this.cache_[i].value;
@ -118,6 +122,7 @@ Setting.defaults_ = {
'user.session': { value: '', type: 'string' },
'sync.lastRevId': { value: 0, type: 'int' }, // DEPRECATED
'sync.lastUpdateTime': { value: 0, type: 'int' },
'sync.conflictFolderId': { value: '', type: 'string' },
};
export { Setting };

View File

@ -1,6 +1,7 @@
require('babel-plugin-transform-runtime');
import { BaseItem } from 'src/models/base-item.js';
import { BaseModel } from 'src/base-model.js';
import { sprintf } from 'sprintf-js';
import { time } from 'src/time-utils.js';
import { Log } from 'src/log.js'
@ -42,12 +43,13 @@ class Synchronizer {
let remote = await this.api().stat(path);
let content = ItemClass.serialize(local);
let action = null;
let updateSyncTimeOnly = true;
if (!remote) {
action = 'createRemote';
} else {
if (remote.updated_time > local.updated_time) {
action = 'conflict';
if (remote.updated_time > local.updated_time && local.type_ == BaseModel.ITEM_TYPE_NOTE) {
action = 'noteConflict';
} else {
action = 'updateRemote';
}
@ -56,11 +58,28 @@ class Synchronizer {
if (action == 'createRemote' || action == 'updateRemote') {
await this.api().put(path, content);
await this.api().setTimestamp(path, local.updated_time);
} else if (action == 'conflict') {
console.warn('FOUND CONFLICT', local, remote);
} 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
let conflictFolder = await Folder.conflictFolder();
let conflictedNote = Object.assign({}, local);
delete conflictedNote.id;
conflictedNote.parent_id = conflictFolder.id;
await Note.save(conflictedNote);
let remoteContent = await this.api().get(path);
local = BaseItem.unserialize(remoteContent);
updateSyncTimeOnly = false;
}
let newLocal = null;
if (updateSyncTimeOnly) {
newLocal = { id: local.id, sync_time: time.unixMs(), type_: local.type_ };
} else {
newLocal = local;
newLocal.sync_time = time.unixMs();
}
let newLocal = { id: local.id, sync_time: time.unixMs(), type_: local.type_ };
await ItemClass.save(newLocal, { autoTimestamp: false });
donePaths.push(path);