Просмотр исходного кода

CoreDataObserver class, update LA also when the OverrideStored entity has updated

polscm32 aka Marvout 1 год назад
Родитель
Сommit
fcb1215400

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -295,6 +295,7 @@
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
+		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
@@ -888,6 +889,7 @@
 		BD1CF8B72C1A4A8400CB930A /* ConfigOverride.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ConfigOverride.xcconfig; sourceTree = "<group>"; };
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
+		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
@@ -2077,6 +2079,7 @@
 				BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */,
 				5825D1622BD405AE00F36E9B /* Helper */,
 				DDD1631D2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld */,
+				BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */,
 			);
 			path = Model;
 			sourceTree = "<group>";
@@ -3074,6 +3077,7 @@
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */,
 				CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */,
+				BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */,

+ 26 - 35
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -99,10 +99,15 @@ extension Bolus {
         let context = CoreDataStack.shared.persistentContainer.viewContext
         let backgroundContext = CoreDataStack.shared.newTaskContext()
 
+        private var coreDataObserver: CoreDataObserver?
+
         typealias PumpEvent = PumpEventStored.EventType
 
         override func subscribe() {
-            setupNotification()
+            setupGlucoseNotification()
+            coreDataObserver = CoreDataObserver()
+            registerHandlers()
+
             setupGlucoseArray()
             setupDeterminationsArray()
 
@@ -528,46 +533,32 @@ extension Bolus.StateModel: DeterminationObserver, BolusFailureObserver {
     }
 }
 
-// MARK: - Setup Notifications
-
 extension Bolus.StateModel {
-    /// listens for the notifications sent when the managedObjectContext has saved!
-    func setupNotification() {
-        Foundation.NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(contextDidSave(_:)),
-            name: Notification.Name.NSManagedObjectContextDidSave,
-            object: backgroundContext
-        )
-    }
-
-    /// determine the actions when the context has changed
-    ///
-    /// its done on a background thread and after that the UI gets updated on the main thread
-    @objc private func contextDidSave(_ notification: Notification) {
-        guard let userInfo = notification.userInfo else { return }
+    private func registerHandlers() {
+        coreDataObserver?.registerHandler(for: "OrefDetermination") { [weak self] in
+            guard let self = self else { return }
+            self.setupDeterminationsArray()
+        }
 
-        Task { [weak self] in
-            await self?.processUpdates(userInfo: userInfo)
+        // Due to the Batch insert this only is used for observing Deletion of Glucose entries
+        coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
+            guard let self = self else { return }
+            self.setupGlucoseArray()
         }
     }
 
-    private func processUpdates(userInfo: [AnyHashable: Any]) async {
-        var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
-
-        let glucoseUpdates = objects.filter { $0 is GlucoseStored }
-        let determinationUpdates = objects.filter { $0 is OrefDetermination }
+    private func setupGlucoseNotification() {
+        /// custom notification that is sent when a batch insert of glucose objects is done
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleBatchInsert),
+            name: .didPerformBatchInsert,
+            object: nil
+        )
+    }
 
-        DispatchQueue.global(qos: .background).async {
-            if glucoseUpdates.isNotEmpty {
-                self.setupGlucoseArray()
-            }
-            if determinationUpdates.isNotEmpty {
-                self.setupDeterminationsArray()
-            }
-        }
+    @objc private func handleBatchInsert() {
+        setupGlucoseArray()
     }
 }
 

+ 47 - 66
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -83,10 +83,14 @@ extension Home {
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
+        private var coreDataObserver: CoreDataObserver?
+
         typealias PumpEvent = PumpEventStored.EventType
 
         override func subscribe() {
             setupNotification()
+            coreDataObserver = CoreDataObserver()
+            registerHandlers()
             setupGlucoseArray()
             setupManualGlucoseArray()
             setupCarbsArray()
@@ -219,6 +223,49 @@ extension Home {
                 .store(in: &lifetime)
         }
 
+        private func registerHandlers() {
+            coreDataObserver?.registerHandler(for: "OrefDetermination") { [weak self] in
+                guard let self = self else { return }
+                Task {
+                    self.setupDeterminationsArray()
+                    await self.updateForecastData()
+                }
+            }
+
+            coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupGlucoseArray()
+                self.setupManualGlucoseArray()
+            }
+
+            coreDataObserver?.registerHandler(for: "CarbEntryStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupCarbsArray()
+            }
+
+            coreDataObserver?.registerHandler(for: "PumpEventStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupInsulinArray()
+                self.setupLastBolus()
+                self.displayPumpStatusHighlightMessage()
+            }
+
+            coreDataObserver?.registerHandler(for: "OpenAPS_Battery") { [weak self] in
+                guard let self = self else { return }
+                self.setupBatteryArray()
+            }
+
+            coreDataObserver?.registerHandler(for: "OverrideStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupOverrides()
+            }
+
+            coreDataObserver?.registerHandler(for: "OverrideRunStored") { [weak self] in
+                guard let self = self else { return }
+                self.setupOverrideRunStored()
+            }
+        }
+
         /// Display the eventual status message provided by the manager of the pump
         /// Only display if state is warning or critical message else return nil
         private func displayPumpStatusHighlightMessage(_ didDeactivate: Bool = false) {
@@ -469,13 +516,6 @@ extension Home.StateModel: PumpManagerOnboardingDelegate {
 extension Home.StateModel {
     /// listens for the notifications sent when the managedObjectContext has saved!
     func setupNotification() {
-        Foundation.NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(contextDidSave(_:)),
-            name: Notification.Name.NSManagedObjectContextDidSave,
-            object: nil
-        )
-
         /// custom notification that is sent when a batch insert of glucose objects is done
         Foundation.NotificationCenter.default.addObserver(
             self,
@@ -493,17 +533,6 @@ extension Home.StateModel {
         )
     }
 
-    /// determine the actions when the context has changed
-    ///
-    /// its done on a background thread and after that the UI gets updated on the main thread
-    @objc private func contextDidSave(_ notification: Notification) {
-        guard let userInfo = notification.userInfo else { return }
-
-        Task { [weak self] in
-            await self?.processUpdates(userInfo: userInfo)
-        }
-    }
-
     @objc private func handleBatchInsert() {
         setupFPUsArray()
         setupGlucoseArray()
@@ -512,54 +541,6 @@ extension Home.StateModel {
     @objc private func handleBatchDelete() {
         setupFPUsArray()
     }
-
-    private func processUpdates(userInfo: [AnyHashable: Any]) async {
-        var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
-
-        let glucoseUpdates = objects.filter { $0 is GlucoseStored }
-        let manualGlucoseUpdates = objects.filter { $0 is GlucoseStored }
-        let determinationUpdates = objects.filter { $0 is OrefDetermination }
-        let carbUpdates = objects.filter { $0 is CarbEntryStored }
-        let insulinUpdates = objects.filter { $0 is PumpEventStored }
-        let batteryUpdates = objects.filter { $0 is OpenAPS_Battery }
-        let overrideUpdates = objects.filter { $0 is OverrideStored }
-        let overrideRunStoredUpdates = objects.filter { $0 is OverrideRunStored }
-
-        DispatchQueue.global(qos: .background).async {
-            if !glucoseUpdates.isEmpty {
-                self.setupGlucoseArray()
-            }
-            if !manualGlucoseUpdates.isEmpty {
-                self.setupManualGlucoseArray()
-            }
-            if !determinationUpdates.isEmpty {
-                Task {
-                    self.setupDeterminationsArray()
-                    await self.updateForecastData()
-                }
-            }
-            if !carbUpdates.isEmpty {
-                self.setupCarbsArray()
-                self.setupFPUsArray()
-            }
-            if !insulinUpdates.isEmpty {
-                self.setupInsulinArray()
-                self.setupLastBolus()
-                self.displayPumpStatusHighlightMessage()
-            }
-            if !batteryUpdates.isEmpty {
-                self.setupBatteryArray()
-            }
-            if !overrideUpdates.isEmpty {
-                self.setupOverrides()
-            }
-            if !overrideRunStoredUpdates.isEmpty {
-                self.setupOverrideRunStored()
-            }
-        }
-    }
 }
 
 // MARK: - Handle Core Data changes and update Arrays to display them in the UI

+ 21 - 0
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -45,10 +45,14 @@ import UIKit
 
     let context = CoreDataStack.shared.newTaskContext()
 
+    private var coreDataObserver: CoreDataObserver?
+
     init(resolver: Resolver) {
         systemEnabled = activityAuthorizationInfo.areActivitiesEnabled
         injectServices(resolver)
         setupNotifications()
+        coreDataObserver = CoreDataObserver()
+        registerHandler()
         monitorForLiveActivityAuthorizationChanges()
         setupGlucoseArray()
     }
@@ -67,6 +71,14 @@ import UIKit
             }
     }
 
+    private func registerHandler() {
+        // Since we are only using this info to show if an Override is active or not in the Live Activity it is enough to observe only the 'OverrideStored' Entity
+        coreDataObserver?.registerHandler(for: "OverrideStored") { [weak self] in
+            guard let self = self else { return }
+            self.overridesDidUpdate()
+        }
+    }
+
     @objc private func handleBatchInsert() {
         setupGlucoseArray()
     }
@@ -80,6 +92,15 @@ import UIKit
         }
     }
 
+    @objc private func overridesDidUpdate() {
+        Task {
+            await fetchAndMapOverride()
+            if let determination = determination {
+                await self.pushDeterminationUpdate(determination)
+            }
+        }
+    }
+
     private func setupGlucoseArray() {
         Task {
             // Fetch and map glucose to GlucoseData struct

+ 43 - 64
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -69,18 +69,60 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     private var lastEnactedDetermination: Determination?
     private var lastSuggestedDetermination: Determination?
 
+    private var coreDataObserver: CoreDataObserver?
+
     init(resolver: Resolver) {
         injectServices(resolver)
         subscribe()
+        coreDataObserver = CoreDataObserver()
+        registerHandlers()
     }
 
     private func subscribe() {
-        setupNotification()
         _ = reachabilityManager.startListening(onQueue: processQueue) { status in
             debug(.nightscout, "Network status: \(status)")
         }
     }
 
+    private func registerHandlers() {
+        coreDataObserver?.registerHandler(for: "OrefDetermination") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadStatus()
+            }
+        }
+        coreDataObserver?.registerHandler(for: "OverrideStored") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadOverrides()
+            }
+        }
+        coreDataObserver?.registerHandler(for: "OverrideRunStored") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadOverrides()
+            }
+        }
+        coreDataObserver?.registerHandler(for: "PumpEventStored") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadPumpHistory()
+            }
+        }
+        coreDataObserver?.registerHandler(for: "CarbEntryStored") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadCarbs()
+            }
+        }
+        coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadManualGlucose()
+            }
+        }
+    }
+
     func sourceInfo() -> [String: Any]? {
         if let ping = ping {
             return [GlucoseSourceKey.nightscoutPing.rawValue: ping]
@@ -935,66 +977,3 @@ extension Array {
         }
     }
 }
-
-extension BaseNightscoutManager {
-    /// listens for the notifications sent when the managedObjectContext has saved!
-    func setupNotification() {
-        Foundation.NotificationCenter.default.addObserver(
-            self,
-            selector: #selector(contextDidSave(_:)),
-            name: Notification.Name.NSManagedObjectContextDidSave,
-            object: nil
-        )
-    }
-
-    /// determine the actions when the context has changed
-    ///
-    /// its done on a background thread and after that the UI gets updated on the main thread
-    @objc private func contextDidSave(_ notification: Notification) {
-        guard let userInfo = notification.userInfo else {
-            return
-        }
-
-        Task { [weak self] in
-            await self?.processUpdates(userInfo: userInfo)
-        }
-    }
-
-    private func processUpdates(userInfo: [AnyHashable: Any]) async {
-        var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
-        objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
-
-        let manualGlucoseUpdates = objects.filter { $0 is GlucoseStored }
-        let carbUpdates = objects.filter { $0 is CarbEntryStored }
-        let pumpHistoryUpdates = objects.filter { $0 is PumpEventStored }
-        let overrideUpdates = objects.filter { $0 is OverrideStored || $0 is OverrideRunStored }
-        let determinationUpdates = objects.filter { $0 is OrefDetermination }
-
-        if manualGlucoseUpdates.isNotEmpty {
-            Task.detached {
-                await self.uploadManualGlucose()
-            }
-        }
-        if carbUpdates.isNotEmpty {
-            Task.detached {
-                await self.uploadCarbs()
-            }
-        }
-        if pumpHistoryUpdates.isNotEmpty {
-            Task.detached {
-                await self.uploadPumpHistory()
-            }
-        }
-        if overrideUpdates.isNotEmpty {
-            Task.detached {
-                await self.uploadOverrides()
-            }
-        }
-        if determinationUpdates.isNotEmpty {
-            Task.detached {
-                await self.uploadStatus()
-            }
-        }
-    }
-}

+ 46 - 0
Model/CoreDataObserver.swift

@@ -0,0 +1,46 @@
+import CoreData
+import Foundation
+
+class CoreDataObserver {
+    private var entityUpdateHandlers: [String: () -> Void] = [:] // Dictionary to store pairs of entities and handlers
+
+    init() {
+        setupNotification()
+    }
+
+    func registerHandler(for entityName: String, handler: @escaping () -> Void) {
+        entityUpdateHandlers[entityName] = handler
+    }
+
+    private func setupNotification() {
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(contextDidSave(_:)),
+            name: NSNotification.Name.NSManagedObjectContextDidSave,
+            object: nil
+        )
+    }
+
+    @objc private func contextDidSave(_ notification: Notification) {
+        guard let userInfo = notification.userInfo else { return }
+
+        Task {
+            await processUpdates(userInfo: userInfo)
+        }
+    }
+
+    private func processUpdates(userInfo: [AnyHashable: Any]) async {
+        var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
+        objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
+        objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
+
+        for (entityName, handler) in entityUpdateHandlers {
+            let entityUpdates = objects.filter { $0.entity.name == entityName }
+            DispatchQueue.global(qos: .background).async {
+                if entityUpdates.isNotEmpty {
+                    handler()
+                }
+            }
+        }
+    }
+}