From 0e5f1f6478596b438178d4414aab18e360dc53e9 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 27 Jun 2018 10:39:47 -0500 Subject: [PATCH] Refactor model manager to expect external lib --- .../app/directives/views/accountMenu.js | 2 +- app/assets/javascripts/app/models/api/item.js | 11 - .../javascripts/app/models/app/editor.js | 6 +- app/assets/javascripts/app/models/app/note.js | 5 +- app/assets/javascripts/app/models/app/tag.js | 4 - .../javascripts/app/services/modelManager.js | 404 +----------------- 6 files changed, 11 insertions(+), 421 deletions(-) diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 7142dc0fd..31b677134 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -248,7 +248,7 @@ class AccountMenu { items.forEach(function(item){ item.setDirty(true, true); item.deleted = false; - item.markAllReferencesDirty(true); + modelManager.markAllReferencesDirtyForItem(item, true); // We don't want to activate any components during import process in case of exceptions // breaking up the import proccess diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index 425b354af..52a28c1d4 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -122,12 +122,6 @@ class Item { } } - markAllReferencesDirty(dontUpdateClientDate) { - this.allReferencedObjects().forEach(function(reference){ - reference.setDirty(true, dontUpdateClientDate); - }) - } - addObserver(observer, callback) { if(!_.find(this.observers, observer)) { this.observers.push({observer: observer, callback: callback}); @@ -182,11 +176,6 @@ class Item { } } - allReferencedObjects() { - // must override - return []; - } - doNotEncrypt() { return false; } diff --git a/app/assets/javascripts/app/models/app/editor.js b/app/assets/javascripts/app/models/app/editor.js index a1cb8c5b5..b25c50885 100644 --- a/app/assets/javascripts/app/models/app/editor.js +++ b/app/assets/javascripts/app/models/app/editor.js @@ -79,11 +79,7 @@ class Editor extends Item { this.notes.push(newItem); } } - - allReferencedObjects() { - return this.notes; - } - + toJSON() { return {uuid: this.uuid} } diff --git a/app/assets/javascripts/app/models/app/note.js b/app/assets/javascripts/app/models/app/note.js index 9070070aa..b1140acf8 100644 --- a/app/assets/javascripts/app/models/app/note.js +++ b/app/assets/javascripts/app/models/app/note.js @@ -97,16 +97,13 @@ class Note extends Item { } informReferencesOfUUIDChange(oldUUID, newUUID) { + super.informReferencesOfUUIDChange(); for(var tag of this.tags) { _.pull(tag.notes, {uuid: oldUUID}); tag.notes.push(this); } } - allReferencedObjects() { - return this.tags; - } - safeText() { return this.text || ""; } diff --git a/app/assets/javascripts/app/models/app/tag.js b/app/assets/javascripts/app/models/app/tag.js index 04bc11882..69e3437da 100644 --- a/app/assets/javascripts/app/models/app/tag.js +++ b/app/assets/javascripts/app/models/app/tag.js @@ -84,10 +84,6 @@ class Tag extends Item { return "Tag"; } - allReferencedObjects() { - return this.notes; - } - static arrayToDisplayString(tags) { return tags.sort((a, b) => {return a.title > b.title}).map(function(tag, i){ return "#" + tag.title; diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index df9d461c4..018a806bc 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -1,112 +1,19 @@ class ModelManager { constructor(storageManager) { - ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved"; - ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved"; - ModelManager.MappingSourceLocalSaved = "MappingSourceLocalSaved"; - ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved"; - ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved"; - ModelManager.MappingSourceDesktopInstalled = "MappingSourceDesktopInstalled"; // When a component is installed by the desktop and some of its values change - ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */ - ModelManager.MappingSourceFileImport = "MappingSourceFileImport"; - - ModelManager.isMappingSourceRetrieved = (source) => { - return [ - ModelManager.MappingSourceRemoteRetrieved, - ModelManager.MappingSourceComponentRetrieved, - ModelManager.MappingSourceRemoteActionRetrieved - ].includes(source); - } - - this.storageManager = storageManager; + super(storageManager); this.notes = []; this.tags = []; - this.itemSyncObservers = []; - this.itemChangeObservers = []; - this.itemsPendingRemoval = []; - this.items = []; this._extensions = []; } resetLocalMemory() { + super.resetLocalMemory(); this.notes.length = 0; this.tags.length = 0; - this.items.length = 0; this._extensions.length = 0; } - get allItems() { - return this.items.filter(function(item){ - return !item.dummy; - }) - } - - get extensions() { - return this._extensions.filter(function(ext){ - return !ext.deleted; - }) - } - - alternateUUIDForItem(item, callback, removeOriginal) { - // We need to clone this item and give it a new uuid, then delete item with old uuid from db (you can't modify uuid's in our indexeddb setup) - - // Collapse in memory properties to item's content object, as the new item will be created based on the content object, and not the physical properties. (like note.text or note.title) - item.refreshContentObject(); - - var newItem = this.createItem(item); - - newItem.uuid = SFJS.crypto.generateUUIDSync(); - - // Update uuids of relationships - newItem.informReferencesOfUUIDChange(item.uuid, newItem.uuid); - - this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid); - - console.log(item.uuid, "-->", newItem.uuid); - - var block = () => { - this.addItem(newItem); - newItem.setDirty(true); - newItem.markAllReferencesDirty(); - callback(); - } - - if(removeOriginal) { - // Set to deleted, then run through mapping function so that observers can be notified - item.deleted = true; - this.mapResponseItemsToLocalModels([item], ModelManager.MappingSourceLocalSaved); - block(); - } else { - block(); - } - } - - informModelsOfUUIDChangeForItem(newItem, oldUUID, newUUID) { - // some models that only have one-way relationships might be interested to hear that an item has changed its uuid - // for example, editors have a one way relationship with notes. When a note changes its UUID, it has no way to inform the editor - // to update its relationships - - for(var model of this.items) { - model.potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID); - } - } - - allItemsMatchingTypes(contentTypes) { - return this.allItems.filter(function(item){ - return (_.includes(contentTypes, item.content_type) || _.includes(contentTypes, "*")) && !item.dummy; - }) - } - - validItemsForContentType(contentType) { - return this.allItems.filter((item) => { - return item.content_type == contentType && !item.errorDecrypting; - }); - } - - findItem(itemId) { - return _.find(this.items, {uuid: itemId}); - } - findOrCreateTagByTitle(title) { var tag = _.find(this.tags, {title: title}) if(!tag) { @@ -116,181 +23,10 @@ class ModelManager { return tag; } - didSyncModelsOffline(items) { - this.notifySyncObserversOfModels(items, ModelManager.MappingSourceLocalSaved); - } - - mapResponseItemsToLocalModels(items, source, sourceKey) { - return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source, sourceKey); - } - - mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source, sourceKey) { - var models = [], processedObjects = [], modelsToNotifyObserversOf = []; - - // first loop should add and process items - for (var json_obj of items) { - if((!json_obj.content_type || !json_obj.content) && !json_obj.deleted && !json_obj.errorDecrypting) { - // An item that is not deleted should never have empty content - console.error("Server response item is corrupt:", json_obj); - continue; - } - - // Lodash's _.omit, which was previously used, seems to cause unexpected behavior - // when json_obj is an ES6 item class. So we instead manually omit each key. - if(Array.isArray(omitFields)) { - for(var key of omitFields) { - delete json_obj[key]; - } - } - - var item = this.findItem(json_obj.uuid); - - if(item) { - item.updateFromJSON(json_obj); - // If an item goes through mapping, it can no longer be a dummy. - item.dummy = false; - } - - if(this.itemsPendingRemoval.includes(json_obj.uuid)) { - _.pull(this.itemsPendingRemoval, json_obj.uuid); - continue; - } - - let contentType = json_obj["content_type"] || (item && item.content_type); - var isDirtyItemPendingDelete = false; - if(json_obj.deleted == true) { - if(json_obj.deleted && json_obj.dirty) { - // Item was marked as deleted but not yet synced - // We need to create this item as usual, but just not add it to individual arrays - // i.e add to this.items but not this.notes (so that it can be retrieved with getDirtyItems) - isDirtyItemPendingDelete = true; - } else { - if(item) { - modelsToNotifyObserversOf.push(item); - this.removeItemLocally(item); - } - continue; - } - } - - if(!item) { - item = this.createItem(json_obj, true); - } - - this.addItem(item, isDirtyItemPendingDelete); - - // Observers do not need to handle items that errored while decrypting. - if(!item.errorDecrypting) { - modelsToNotifyObserversOf.push(item); - } - - models.push(item); - processedObjects.push(json_obj); - } - - // second loop should process references - for (var index in processedObjects) { - var json_obj = processedObjects[index]; - if(json_obj.content) { - this.resolveReferencesForItem(models[index]); - } - } - - this.notifySyncObserversOfModels(modelsToNotifyObserversOf, source, sourceKey); - - return models; - } - - /* Note that this function is public, and can also be called manually (desktopManager uses it) */ - notifySyncObserversOfModels(models, source, sourceKey) { - for(var observer of this.itemSyncObservers) { - var allRelevantItems = observer.type == "*" ? models : models.filter(function(item){return item.content_type == observer.type}); - var validItems = [], deletedItems = []; - for(var item of allRelevantItems) { - if(item.deleted) { - deletedItems.push(item); - } else { - validItems.push(item); - } - } - - if(allRelevantItems.length > 0) { - observer.callback(allRelevantItems, validItems, deletedItems, source, sourceKey); - } - } - } - - notifyItemChangeObserversOfModels(models) { - for(var observer of this.itemChangeObservers) { - var relevantItems = models.filter(function(item){ - return _.includes(observer.content_types, item.content_type) || _.includes(observer.content_types, "*"); - }); - - if(relevantItems.length > 0) { - observer.callback(relevantItems); - } - } - } - - createItem(json_obj, dontNotifyObservers) { - var item; - if(json_obj.content_type == "Note") { - item = new Note(json_obj); - } else if(json_obj.content_type == "Tag") { - item = new Tag(json_obj); - } else if(json_obj.content_type == "Extension") { - item = new Extension(json_obj); - } else if(json_obj.content_type == "SN|Editor") { - item = new Editor(json_obj); - } else if(json_obj.content_type == "SN|Theme") { - item = new Theme(json_obj); - } else if(json_obj.content_type == "SN|Component") { - item = new Component(json_obj); - } else if(json_obj.content_type == "SF|Extension") { - item = new ServerExtension(json_obj); - } else if(json_obj.content_type == "SF|MFA") { - item = new Mfa(json_obj); - } - - else { - item = new Item(json_obj); - } - - // Some observers would be interested to know when an an item is locally created - // If we don't send this out, these observers would have to wait until MappingSourceRemoteSaved - // to hear about it, but sometimes, RemoveSaved is explicitly ignored by the observer to avoid - // recursive callbacks. See componentManager's syncObserver callback. - // dontNotifyObservers is currently only set true by modelManagers mapResponseItemsToLocalModels - if(!dontNotifyObservers) { - this.notifySyncObserversOfModels([item], ModelManager.MappingSourceLocalSaved); - } - - item.addObserver(this, function(changedItem){ - this.notifyItemChangeObserversOfModels([changedItem]); - }.bind(this)); - - return item; - } - - /* - Be sure itemResponse is a generic Javascript object, and not an Item. - An Item needs to collapse its properties into its content object before it can be duplicated. - Note: the reason we need this function is specificallty for the call to resolveReferencesForItem. - This method creates but does not add the item to the global inventory. It's used by syncManager - to check if this prospective duplicate item is identical to another item, including the references. - */ - createDuplicateItem(itemResponse) { - var dup = this.createItem(itemResponse, true); - this.resolveReferencesForItem(dup); - return dup; - } - - addItem(item, globalOnly = false) { - this.addItems([item], globalOnly); - } - addItems(items, globalOnly = false) { - items.forEach(function(item){ + super.addItems(items, globalOnly); + + items.forEach((item) => { // In some cases, you just want to add the item to this.items, and not to the individual arrays // This applies when you want to keep an item syncable, but not display it via the individual arrays if(!globalOnly) { @@ -311,11 +47,7 @@ class ModelManager { } } } - - if(!_.find(this.items, {uuid: item.uuid})) { - this.items.push(item); - } - }.bind(this)); + }); } resortTag(tag) { @@ -326,76 +58,12 @@ class ModelManager { }), 0, tag); } - resolveReferencesForItem(item, markReferencesDirty = false) { - - var contentObject = item.contentObject; - - // If another client removes an item's references, this client won't pick up the removal unless - // we remove everything not present in the current list of references - item.removeReferencesNotPresentIn(contentObject.references || []); - - if(!contentObject.references) { - return; - } - - for(var reference of contentObject.references) { - var referencedItem = this.findItem(reference.uuid); - if(referencedItem) { - item.addItemAsRelationship(referencedItem); - referencedItem.addItemAsRelationship(item); - - if(markReferencesDirty) { - referencedItem.setDirty(true); - } - } else { - // console.log("Unable to find reference:", reference.uuid, "for item:", item); - } - } - } - - addItemSyncObserver(id, type, callback) { - this.itemSyncObservers.push({id: id, type: type, callback: callback}); - } - - removeItemSyncObserver(id) { - _.remove(this.itemSyncObservers, _.find(this.itemSyncObservers, {id: id})); - } - - addItemChangeObserver(id, content_types, callback) { - this.itemChangeObservers.push({id: id, content_types: content_types, callback: callback}); - } - - removeItemChangeObserver(id) { - _.remove(this.itemChangeObservers, _.find(this.itemChangeObservers, {id: id})); - } - get filteredNotes() { return Note.filterDummyNotes(this.notes); } - getDirtyItems() { - return this.items.filter((item) => { - // An item that has an error decrypting can be synced only if it is being deleted. - // Otherwise, we don't want to send corrupt content up to the server. - return item.dirty == true && !item.dummy && (!item.errorDecrypting || item.deleted); - }) - } - - clearDirtyItems(items) { - for(var item of items) { - item.setDirty(false); - } - } - - clearAllDirtyItems() { - this.clearDirtyItems(this.getDirtyItems()); - } - setItemToBeDeleted(item) { - item.deleted = true; - if(!item.dummy) { - item.setDirty(true); - } + super.setItemToBeDeleted(item); // remove from relevant array, but don't remove from all items. // This way, it's removed from the display, but still synced via get dirty items @@ -406,25 +74,10 @@ class ModelManager { } else if(item.content_type == "Extension") { _.pull(this._extensions, item); } - - item.removeAndDirtyAllRelationships(); - } - - /* Used when changing encryption key */ - setAllItemsDirty(dontUpdateClientDates = true) { - var relevantItems = this.allItems; - - for(var item of relevantItems) { - item.setDirty(true, dontUpdateClientDates); - } } removeItemLocally(item, callback) { - _.pull(this.items, item); - - item.isBeingRemovedLocally(); - - this.itemsPendingRemoval.push(item.uuid); + super.removeItemLocally(item, callback); if(item.content_type == "Tag") { _.pull(this.tags, item); @@ -433,49 +86,8 @@ class ModelManager { } else if(item.content_type == "Extension") { _.pull(this._extensions, item); } - - this.storageManager.deleteModel(item, callback); } - /* - Relationships - */ - - createRelationshipBetweenItems(itemOne, itemTwo) { - itemOne.addItemAsRelationship(itemTwo); - itemTwo.addItemAsRelationship(itemOne); - - itemOne.setDirty(true); - itemTwo.setDirty(true); - } - - - /* - Archives - */ - - async getAllItemsJSONData(keys, authParams, protocolVersion, returnNullIfEmpty) { - return Promise.all(this.allItems.map((item) => { - var itemParams = new ItemParams(item, keys, protocolVersion); - return itemParams.paramsForExportFile(); - })).then((items) => { - if(returnNullIfEmpty && items.length == 0) { - return null; - } - - var data = {items: items} - - if(keys) { - // auth params are only needed when encrypted with a standard file key - data["auth_params"] = authParams; - } - - return JSON.stringify(data, null, 2 /* pretty print */); - }) - - } - - /* Misc */