feat: add merge conflict handling

This commit is contained in:
thecodrr
2020-03-29 19:20:12 +05:00
parent d76709fe1e
commit defa827e4d
2 changed files with 47 additions and 6 deletions

View File

@@ -51,9 +51,20 @@ export default class Sync {
return await response.json(); return await response.json();
} }
async throwOnConflicts() {
let hasConflicts = await this.db.context.read("hasConflicts");
if (hasConflicts)
throw new Error(
"Merge conflicts detected. Please resolve all conflicts to continue syncing."
);
}
async start() { async start() {
let user = await this.db.user.get(); let user = await this.db.user.get();
if (!user) throw new Error("You need to login to sync."); if (!user) throw new Error("You need to login to sync.");
await this.throwOnConflicts();
let lastSyncedTimestamp = user.lastSynced || 0; let lastSyncedTimestamp = user.lastSynced || 0;
let serverResponse = await this._fetch(lastSyncedTimestamp); let serverResponse = await this._fetch(lastSyncedTimestamp);
@@ -62,9 +73,9 @@ export default class Sync {
const data = await prepare.get(lastSyncedTimestamp); const data = await prepare.get(lastSyncedTimestamp);
// merge the server response // merge the server response
const merger = new Merger(this.db); const merger = new Merger(this.db, lastSyncedTimestamp);
const mergeResult = await merger.merge(serverResponse); const mergeResult = await merger.merge(serverResponse);
await this.throwOnConflicts();
// send the data back to server // send the data back to server
await this._send(data); await this._send(data);
@@ -91,8 +102,9 @@ class Merger {
* *
* @param {Database} db * @param {Database} db
*/ */
constructor(db) { constructor(db, lastSynced) {
this._db = db; this._db = db;
this._lastSynced = lastSynced;
} }
async _mergeItem(remoteItem, get, add) { async _mergeItem(remoteItem, get, add) {
@@ -112,6 +124,25 @@ class Merger {
); );
} }
async _mergeItemWithConflicts(remoteItem, get, add, resolve) {
let localItem = await get(remoteItem.id);
if (!localItem) {
await add({ ...JSON.parse(remoteItem.data), remote: true });
} else if (localItem.dateEdited > this._lastSynced) {
// we have a conflict
await resolve(localItem, JSON.parse(remoteItem.data));
}
}
async _mergeArrayWithConflicts(array, get, set, resolve) {
return Promise.all(
array.map(
async item =>
await this._mergeItemWithConflicts(item, get, set, resolve)
)
);
}
async merge(serverResponse) { async merge(serverResponse) {
const { const {
notes, notes,
@@ -123,7 +154,9 @@ class Merger {
colors, colors,
trash trash
} = serverResponse; } = serverResponse;
if (synced || areAllEmpty(serverResponse)) return false; if (synced || areAllEmpty(serverResponse)) return false;
await this._mergeArray( await this._mergeArray(
notes, notes,
id => this._db.notes.note(id), id => this._db.notes.note(id),
@@ -135,11 +168,17 @@ class Merger {
item => this._db.notebooks.add(item) item => this._db.notebooks.add(item)
); );
await this._mergeArray( await this._mergeArrayWithConflicts(
delta, delta,
id => this._db.delta.raw(id), id => this._db.delta.raw(id),
item => this._db.delta.add(item) item => this._db.delta.add(item),
async (local, remote) => {
await this._db.delta.add({ ...local, conflicted: remote });
await this._db.notes.add({ id: local.noteId, conflicted: true });
await this._db.context.write("hasConflicts", true);
}
); );
await this._mergeArray( await this._mergeArray(
text, text,
id => this._db.text.raw(id), id => this._db.text.raw(id),
@@ -163,6 +202,7 @@ class Merger {
() => undefined, () => undefined,
item => this._db.trash.add(item) item => this._db.trash.add(item)
); );
return true; return true;
} }
} }

View File

@@ -105,7 +105,8 @@ export default class Notes {
tags: note.tags || [], tags: note.tags || [],
favorite: !!note.favorite, favorite: !!note.favorite,
headline: note.headline, headline: note.headline,
dateCreated: note.dateCreated dateCreated: note.dateCreated,
conflicted: !!note.conflicted
}; };
if (!oldNote) { if (!oldNote) {