diff --git a/ReactNativeClient/lib/base-model.js b/ReactNativeClient/lib/base-model.js index 613531a97..d4a9a0cbf 100644 --- a/ReactNativeClient/lib/base-model.js +++ b/ReactNativeClient/lib/base-model.js @@ -62,11 +62,12 @@ class BaseModel { return temp; } - static fieldType(name) { + static fieldType(name, defaultValue = null) { let fields = this.fields(); for (let i = 0; i < fields.length; i++) { if (fields[i].name == name) return fields[i].type; } + if (defaultValue !== null) return defaultValue; throw new Error('Unknown field: ' + name); } @@ -238,6 +239,9 @@ class BaseModel { 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')) { o.user_updated_time = timeNow; } @@ -278,6 +282,18 @@ class BaseModel { options = this.modOptions(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); 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_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time; 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); }).catch((error) => { Log.error('Cannot save model', error); @@ -328,9 +353,18 @@ class BaseModel { let output = Object.assign({}, model); for (let n in output) { if (!output.hasOwnProperty(n)) continue; + // The SQLite database doesn't have booleans so cast everything to int - if (output[n] === true) output[n] = 1; - if (output[n] === false) output[n] = 0; + if (output[n] === true) { + 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; diff --git a/ReactNativeClient/lib/database.js b/ReactNativeClient/lib/database.js index 20331a1fa..c39901857 100644 --- a/ReactNativeClient/lib/database.js +++ b/ReactNativeClient/lib/database.js @@ -308,6 +308,7 @@ class Database { } +Database.TYPE_UNKNOWN = 0; Database.TYPE_INT = 1; Database.TYPE_TEXT = 2; Database.TYPE_NUMERIC = 3; diff --git a/ReactNativeClient/lib/synchronizer.js b/ReactNativeClient/lib/synchronizer.js index af810f9a5..fbf0af623 100644 --- a/ReactNativeClient/lib/synchronizer.js +++ b/ReactNativeClient/lib/synchronizer.js @@ -410,14 +410,18 @@ class Synchronizer { let action = null; let reason = ''; let local = await BaseItem.loadItemByPath(path); + let ItemClass = null; let content = null; if (!local) { if (remote.isDeleted !== true) { action = 'createLocal'; reason = 'remote exists but local does not'; content = await loadContent(); + ItemClass = content ? BaseItem.itemClass(content) : null; } } else { + ItemClass = BaseItem.itemClass(local); + local = ItemClass.filter(local); if (remote.isDeleted) { action = 'deleteLocal'; 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); continue; } - let ItemClass = BaseItem.itemClass(content); content = ItemClass.filter(content); // 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_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 = { autoTimestamp: false, - nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), + nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, content, time.unixMs()), }; if (action == 'createLocal') options.isNew = true; + if (action == 'updateLocal') options.oldItem = local; - if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { - let localResourceContentPath = Resource.fullPath(newContent); - let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id; + if (content.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') { + let localResourceContentPath = Resource.fullPath(content); + let remoteResourceContentPath = this.resourceDirName_ + '/' + content.id; await this.api().get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' }); } - // if (!newContent.user_updated_time) newContent.user_updated_time = newContent.updated_time; - // if (!newContent.user_created_time) newContent.user_created_time = newContent.created_time; - - await ItemClass.save(newContent, options); + await ItemClass.save(content, options); } else if (action == 'deleteLocal') {