From 909d66067b3f747c92f4f2540deabc305a0c7dd4 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 13 Nov 2017 10:12:07 -0600 Subject: [PATCH] ModelManager mapping sources --- .../app/services/componentManager.js | 34 ++++++++++++++----- .../services/directives/views/accountMenu.js | 2 +- .../app/services/extensionManager.js | 2 +- .../javascripts/app/services/modelManager.js | 33 +++++++----------- .../javascripts/app/services/syncManager.js | 10 +++--- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 031035b8a..c22cdbd85 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -31,7 +31,15 @@ class ComponentManager { this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data); }.bind(this), false); - this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems) { + this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems, source) { + + /* If the source of these new or updated items is from a Component itself saving items, we don't need to notify + components again of the same item. Regarding notifying other components than the issuing component, other mapping sources + will take care of that, like ModelManager.MappingSourceRemoteSaved + */ + if(source == ModelManager.MappingSourceComponentRetrieved) { + return; + } var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" }); for(var component of syncedComponents) { @@ -76,7 +84,7 @@ class ComponentManager { if(itemInContext) { var matchingItem = _.find(allItems, {uuid: itemInContext.uuid}); if(matchingItem) { - this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage); + this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage, source); } } } @@ -118,29 +126,37 @@ class ComponentManager { } } - jsonForItem(item, component) { + jsonForItem(item, component, source) { var params = {uuid: item.uuid, content_type: item.content_type, created_at: item.created_at, updated_at: item.updated_at, deleted: item.deleted}; params.content = item.createContentJSONFromProperties(); params.clientData = item.getDomainDataItem(component.url, ClientDataDomain) || {}; - params.isMetadataUpdate = item.lastTouchSaved; + + /* This means the this function is being triggered through a remote Saving response, which should not update + actual local content values. The reason is, Save responses may be delayed, and a user may have changed some values + in between the Save was initiated, and the time it completes. So we only want to update actual content values (and not just metadata) + when its another source, like ModelManager.MappingSourceRemoteRetrieved. + */ + if(source && source == ModelManager.MappingSourceRemoteSaved) { + params.isMetadataUpdate = true; + } this.removePrivatePropertiesFromResponseItems([params]); return params; } - sendItemsInReply(component, items, message) { + sendItemsInReply(component, items, message, source) { if(this.loggingEnabled) {console.log("Web|componentManager|sendItemsInReply", component, items, message)}; var response = {items: {}}; var mapped = items.map(function(item) { - return this.jsonForItem(item, component); + return this.jsonForItem(item, component, source); }.bind(this)); response.items = mapped; this.replyToMessage(component, message, response); } - sendContextItemInReply(component, item, originalMessage) { + sendContextItemInReply(component, item, originalMessage, source) { if(this.loggingEnabled) {console.log("Web|componentManager|sendContextItemInReply", component, item, originalMessage)}; - var response = {item: this.jsonForItem(item, component)}; + var response = {item: this.jsonForItem(item, component, source)}; this.replyToMessage(component, originalMessage, response); } @@ -240,7 +256,7 @@ class ComponentManager { We map the items here because modelManager is what updates the UI. If you were to instead get the items directly, this would update them server side via sync, but would never make its way back to the UI. */ - var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, {dontNotifyObservers: true}); + var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved); for(var item of localItems) { var responseItem = _.find(responseItems, {uuid: item.uuid}); diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 68806f322..b8a2b7ad6 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -242,7 +242,7 @@ class AccountMenu { $scope.importJSONData = function(data, password, callback) { var onDataReady = function(errorCount) { - var items = modelManager.mapResponseItemsToLocalModels(data.items); + var items = modelManager.mapResponseItemsToLocalModels(data.items, ModelManager.MappingSourceFileImport); items.forEach(function(item){ item.setDirty(true); item.deleted = false; diff --git a/app/assets/javascripts/app/services/extensionManager.js b/app/assets/javascripts/app/services/extensionManager.js index 8a84d2188..ff7b66cd1 100644 --- a/app/assets/javascripts/app/services/extensionManager.js +++ b/app/assets/javascripts/app/services/extensionManager.js @@ -161,7 +161,7 @@ class ExtensionManager { action.error = false; var items = response.items || [response.item]; EncryptionHelper.decryptMultipleItems(items, this.authManager.keys()); - items = this.modelManager.mapResponseItemsToLocalModels(items); + items = this.modelManager.mapResponseItemsToLocalModels(items, ModelManager.MappingSourceRemoteActionRetrieved); for(var item of items) { item.setDirty(true); } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 7d8e1df9f..41861d037 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,6 +1,13 @@ class ModelManager { constructor(storageManager) { + ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved"; + ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved"; + ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved"; + ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved"; + ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */ + ModelManager.MappingSourceFileImport = "MappingSourceFileImport"; + this.storageManager = storageManager; this.notes = []; this.tags = []; @@ -96,11 +103,11 @@ class ModelManager { return tag; } - mapResponseItemsToLocalModels(items, options) { - return this.mapResponseItemsToLocalModelsOmittingFields(items, null, options); + mapResponseItemsToLocalModels(items, source) { + return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source); } - mapResponseItemsToLocalModelsOmittingFields(items, omitFields, options) { + mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source) { var models = [], processedObjects = [], modelsToNotifyObserversOf = []; // first loop should add and process items @@ -136,14 +143,6 @@ class ModelManager { item = this.createItem(json_obj); } - /* If content is being omitted from the json_obj, this means this is a metadata save only. - This happens by the sync manager on sync completion when processing saved items to update - their meta fields, like updated_at that comes from the server. We omit content in such a case - because content may be outdated from the time a sync begins to when it completes (user performed action in between). - So we will only ever update content from a remote source when it is retrieved by the server (serverResponse.retrieved_items) - */ - item.lastTouchSaved = omitFields && omitFields.includes("content"); - this.addItem(item); modelsToNotifyObserversOf.push(item); @@ -159,18 +158,12 @@ class ModelManager { } } - /* Sometimes, a controller will want to map incoming state to the UI, but without yet notifiny all observers of an item change - Particulary, the componentManager sets dontNotifyObservers to true when it receives an update from a component and wnats - to update the remaining UI, but without receiving a (useless) syncObserverCallback immediately. - */ - if(!(options && options.dontNotifyObservers)) { - this.notifySyncObserversOfModels(modelsToNotifyObserversOf); - } + this.notifySyncObserversOfModels(modelsToNotifyObserversOf, source); return models; } - notifySyncObserversOfModels(models) { + notifySyncObserversOfModels(models, source) { for(var observer of this.itemSyncObservers) { var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"}); var validItems = [], deletedItems = []; @@ -183,7 +176,7 @@ class ModelManager { } if(allRelevantItems.length > 0) { - observer.callback(allRelevantItems, validItems, deletedItems); + observer.callback(allRelevantItems, validItems, deletedItems, source); } } } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index cac99a8dd..8e5bc90e6 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -47,7 +47,7 @@ class SyncManager { loadLocalItems(callback) { var params = this.storageManager.getAllModels(function(items){ - var items = this.handleItemsResponse(items, null); + var items = this.handleItemsResponse(items, null, ModelManager.MappingSourceLocalRetrieved); Item.sortItemsByDate(items); callback(items); }.bind(this)) @@ -267,7 +267,7 @@ class SyncManager { // Map retrieved items to local data var retrieved - = this.handleItemsResponse(response.retrieved_items, null); + = this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved); // Append items to master list of retrieved items for this ongoing sync operation this.allRetreivedItems = this.allRetreivedItems.concat(retrieved); @@ -279,7 +279,7 @@ class SyncManager { // Map saved items to local data var saved = - this.handleItemsResponse(response.saved_items, omitFields); + this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved); // Create copies of items or alternate their uuids if neccessary var unsaved = response.unsaved; @@ -355,10 +355,10 @@ class SyncManager { } } - handleItemsResponse(responseItems, omitFields) { + handleItemsResponse(responseItems, omitFields, source) { var keys = this.authManager.keys() || this.passcodeManager.keys(); EncryptionHelper.decryptMultipleItems(responseItems, keys); - var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields); + var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields, source); return items; }