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:
parent
02ff02a9d9
commit
baa1a01ed0
@ -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();
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -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';
|
||||
|
@ -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 };
|
||||
});
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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 };
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user