You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-16 00:14:34 +02:00
All: Improved synchronisation process and saving of models so that reducer can deal with full objects
This commit is contained in:
@ -62,11 +62,12 @@ class BaseModel {
|
|||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fieldType(name) {
|
static fieldType(name, defaultValue = null) {
|
||||||
let fields = this.fields();
|
let fields = this.fields();
|
||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
if (fields[i].name == name) return fields[i].type;
|
if (fields[i].name == name) return fields[i].type;
|
||||||
}
|
}
|
||||||
|
if (defaultValue !== null) return defaultValue;
|
||||||
throw new Error('Unknown field: ' + name);
|
throw new Error('Unknown field: ' + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +239,9 @@ class BaseModel {
|
|||||||
o.updated_time = timeNow;
|
o.updated_time = timeNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The purpose of user_updated_time is to allow the user to manually set the time of a note (in which case
|
||||||
|
// options.autoTimestamp will be `false`). However note that if the item is later changed, this timestamp
|
||||||
|
// will be set again to the current time.
|
||||||
if (options.autoTimestamp && this.hasField('user_updated_time')) {
|
if (options.autoTimestamp && this.hasField('user_updated_time')) {
|
||||||
o.user_updated_time = timeNow;
|
o.user_updated_time = timeNow;
|
||||||
}
|
}
|
||||||
@ -278,6 +282,18 @@ class BaseModel {
|
|||||||
options = this.modOptions(options);
|
options = this.modOptions(options);
|
||||||
options.isNew = this.isNew(o, options);
|
options.isNew = this.isNew(o, options);
|
||||||
|
|
||||||
|
// Diff saving is an optimisation which takes a new version of the item and an old one,
|
||||||
|
// do a diff and save only this diff. IMPORTANT: When using this make sure that both
|
||||||
|
// models have been normalised using ItemClass.filter()
|
||||||
|
const isDiffSaving = options && options.oldItem && !options.isNew;
|
||||||
|
|
||||||
|
if (isDiffSaving) {
|
||||||
|
const newObject = BaseModel.diffObjects(options.oldItem, o);
|
||||||
|
newObject.type_ = o.type_;
|
||||||
|
newObject.id = o.id;
|
||||||
|
o = newObject;
|
||||||
|
}
|
||||||
|
|
||||||
o = this.filter(o);
|
o = this.filter(o);
|
||||||
|
|
||||||
let queries = [];
|
let queries = [];
|
||||||
@ -298,6 +314,15 @@ class BaseModel {
|
|||||||
if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time;
|
if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time;
|
||||||
if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time;
|
if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time;
|
||||||
o = this.addModelMd(o);
|
o = this.addModelMd(o);
|
||||||
|
|
||||||
|
if (isDiffSaving) {
|
||||||
|
for (let n in options.oldItem) {
|
||||||
|
if (!options.oldItem.hasOwnProperty(n)) continue;
|
||||||
|
if (n in o) continue;
|
||||||
|
o[n] = options.oldItem[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.filter(o);
|
return this.filter(o);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
Log.error('Cannot save model', error);
|
Log.error('Cannot save model', error);
|
||||||
@ -328,9 +353,18 @@ class BaseModel {
|
|||||||
let output = Object.assign({}, model);
|
let output = Object.assign({}, model);
|
||||||
for (let n in output) {
|
for (let n in output) {
|
||||||
if (!output.hasOwnProperty(n)) continue;
|
if (!output.hasOwnProperty(n)) continue;
|
||||||
|
|
||||||
// The SQLite database doesn't have booleans so cast everything to int
|
// The SQLite database doesn't have booleans so cast everything to int
|
||||||
if (output[n] === true) output[n] = 1;
|
if (output[n] === true) {
|
||||||
if (output[n] === false) output[n] = 0;
|
output[n] = 1;
|
||||||
|
} else if (output[n] === false) {
|
||||||
|
output[n] = 0;
|
||||||
|
} else {
|
||||||
|
const t = this.fieldType(n, Database.TYPE_UNKNOWN);
|
||||||
|
if (t === Database.TYPE_INT) {
|
||||||
|
output[n] = !n ? 0 : parseInt(output[n], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
@ -308,6 +308,7 @@ class Database {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Database.TYPE_UNKNOWN = 0;
|
||||||
Database.TYPE_INT = 1;
|
Database.TYPE_INT = 1;
|
||||||
Database.TYPE_TEXT = 2;
|
Database.TYPE_TEXT = 2;
|
||||||
Database.TYPE_NUMERIC = 3;
|
Database.TYPE_NUMERIC = 3;
|
||||||
|
@ -410,14 +410,18 @@ class Synchronizer {
|
|||||||
let action = null;
|
let action = null;
|
||||||
let reason = '';
|
let reason = '';
|
||||||
let local = await BaseItem.loadItemByPath(path);
|
let local = await BaseItem.loadItemByPath(path);
|
||||||
|
let ItemClass = null;
|
||||||
let content = null;
|
let content = null;
|
||||||
if (!local) {
|
if (!local) {
|
||||||
if (remote.isDeleted !== true) {
|
if (remote.isDeleted !== true) {
|
||||||
action = 'createLocal';
|
action = 'createLocal';
|
||||||
reason = 'remote exists but local does not';
|
reason = 'remote exists but local does not';
|
||||||
content = await loadContent();
|
content = await loadContent();
|
||||||
|
ItemClass = content ? BaseItem.itemClass(content) : null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
ItemClass = BaseItem.itemClass(local);
|
||||||
|
local = ItemClass.filter(local);
|
||||||
if (remote.isDeleted) {
|
if (remote.isDeleted) {
|
||||||
action = 'deleteLocal';
|
action = 'deleteLocal';
|
||||||
reason = 'remote has been deleted';
|
reason = 'remote has been deleted';
|
||||||
@ -440,7 +444,6 @@ class Synchronizer {
|
|||||||
this.logger().warn('Remote has been deleted between now and the list() call? In that case it will be handled during the next sync: ' + path);
|
this.logger().warn('Remote has been deleted between now and the list() call? In that case it will be handled during the next sync: ' + path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ItemClass = BaseItem.itemClass(content);
|
|
||||||
content = ItemClass.filter(content);
|
content = ItemClass.filter(content);
|
||||||
|
|
||||||
// 2017-12-03: This was added because the new user_updated_time and user_created_time properties were added
|
// 2017-12-03: This was added because the new user_updated_time and user_created_time properties were added
|
||||||
@ -451,34 +454,20 @@ class Synchronizer {
|
|||||||
if (!content.user_updated_time) content.user_updated_time = content.updated_time;
|
if (!content.user_updated_time) content.user_updated_time = content.updated_time;
|
||||||
if (!content.user_created_time) content.user_created_time = content.created_time;
|
if (!content.user_created_time) content.user_created_time = content.created_time;
|
||||||
|
|
||||||
let newContent = null;
|
|
||||||
|
|
||||||
if (action === 'createLocal') {
|
|
||||||
newContent = Object.assign({}, content);
|
|
||||||
} else if (action === 'updateLocal') {
|
|
||||||
newContent = BaseModel.diffObjects(local, content);
|
|
||||||
newContent.type_ = content.type_;
|
|
||||||
newContent.id = content.id;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unknown action: ' + action);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
autoTimestamp: false,
|
autoTimestamp: false,
|
||||||
nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()),
|
nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, content, time.unixMs()),
|
||||||
};
|
};
|
||||||
if (action == 'createLocal') options.isNew = true;
|
if (action == 'createLocal') options.isNew = true;
|
||||||
|
if (action == 'updateLocal') options.oldItem = local;
|
||||||
|
|
||||||
if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') {
|
if (content.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') {
|
||||||
let localResourceContentPath = Resource.fullPath(newContent);
|
let localResourceContentPath = Resource.fullPath(content);
|
||||||
let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id;
|
let remoteResourceContentPath = this.resourceDirName_ + '/' + content.id;
|
||||||
await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' });
|
await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!newContent.user_updated_time) newContent.user_updated_time = newContent.updated_time;
|
await ItemClass.save(content, options);
|
||||||
// if (!newContent.user_created_time) newContent.user_created_time = newContent.created_time;
|
|
||||||
|
|
||||||
await ItemClass.save(newContent, options);
|
|
||||||
|
|
||||||
} else if (action == 'deleteLocal') {
|
} else if (action == 'deleteLocal') {
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user