Browse Source

Refactoring

polscm32 aka Marvout 1 year ago
parent
commit
b309ffa346

+ 3 - 1
FreeAPS/Sources/APS/FetchTreatmentsManager.swift

@@ -38,7 +38,9 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
 
                     let filteredTargets = await tempTargets.filter { !($0.enteredBy?.contains(TempTarget.manual) ?? false) }
                     if filteredTargets.isNotEmpty {
-                        self.tempTargetsStorage.storeTempTargets(filteredTargets)
+                        for tempTarget in filteredTargets {
+                            await self.tempTargetsStorage.storeTempTarget(tempTarget: tempTarget)
+                        }
                     }
                 }
             }

+ 1 - 1
FreeAPS/Sources/APS/Storage/OverrideStorage.swift

@@ -14,7 +14,7 @@ protocol OverrideStorage {
     func getOverrideRunsNotYetUploadedToNightscout() async -> [NightscoutExercise]
 }
 
-final class BaseOverrideStorage: OverrideStorage, Injectable {
+final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     @Injected() private var settingsManager: SettingsManager!
 
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext

+ 136 - 27
FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Foundation
 import SwiftDate
 import Swinject
@@ -7,11 +8,15 @@ protocol TempTargetsObserver {
 }
 
 protocol TempTargetsStorage {
-    func storeTempTargets(_ targets: [TempTarget])
+    func storeTempTarget(tempTarget: TempTarget) async
+    func fetchForTempTargetPresets() async -> [NSManagedObjectID]
+    func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
+    func deleteOverridePreset(_ objectID: NSManagedObjectID) async
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
     func syncDate() -> Date
     func recent() -> [TempTarget]
     func nightscoutTreatmentsNotUploaded() -> [NightscoutTreatment]
-    func storePresets(_ targets: [TempTarget])
+//    func storePresets(_ targets: [TempTarget])
     func presets() -> [TempTarget]
     func current() -> TempTarget?
 }
@@ -21,45 +26,138 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
 
+    private let backgroundContext = CoreDataStack.shared.newTaskContext()
+    private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
     init(resolver: Resolver) {
         injectServices(resolver)
     }
 
-    func storeTempTargets(_ targets: [TempTarget]) {
-        storeTempTargets(targets, isPresets: false)
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.lastActiveTempTarget,
+            key: "date",
+            ascending: true,
+            fetchLimit: fetchLimit
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            return fetchedResults.map(\.objectID)
+        }
+    }
+
+    /// Returns the NSManagedObjectID of the Temp Target Presets
+    func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.allTempTargetPresets,
+            key: "date",
+            ascending: true
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            return fetchedResults.map(\.objectID)
+        }
     }
 
-    private func storeTempTargets(_ targets: [TempTarget], isPresets: Bool) {
+    func storeTempTarget(tempTarget: TempTarget) async {
+        await backgroundContext.perform {
+            let newTempTarget = TempTargetStored(context: self.backgroundContext)
+            newTempTarget.date = tempTarget.createdAt
+            newTempTarget.id = UUID()
+            newTempTarget.enabled = tempTarget.enabled ?? false
+            newTempTarget.duration = tempTarget.duration as NSDecimalNumber
+            newTempTarget.isUploadedToNS = false
+            newTempTarget.name = tempTarget.name
+            newTempTarget.target = NSDecimalNumber(decimal: tempTarget.targetTop ?? 0)
+            newTempTarget.isPreset = tempTarget.isPreset ?? false
+
+            do {
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Temp Target to Core Data with error: \(error.userInfo)"
+                )
+            }
+
+            if tempTarget.isPreset ?? false {
+                self.saveTempTargetsToStorage([tempTarget], isPreset: true)
+            } else {
+                self.saveTempTargetsToStorage([tempTarget], isPreset: false)
+            }
+        }
+    }
+
+    private func saveTempTargetsToStorage(_ targets: [TempTarget], isPreset: Bool) {
         processQueue.sync {
-            var targets = targets
-            if !isPresets {
-                if current() != nil, let newActive = targets.last(where: {
-                    $0.createdAt.addingTimeInterval(Int($0.duration).minutes.timeInterval) > Date()
-                        && $0.createdAt <= Date()
-                }) {
-                    // cancel current
-                    targets += [TempTarget.cancel(at: newActive.createdAt.addingTimeInterval(-1))]
+            var updatedTargets = targets
+
+            if !isPreset, let currentTarget = current() {
+                if let newActive = updatedTargets.last(where: { $0.isActive }) {
+                    // Cancel current target
+                    updatedTargets.append(.cancel(at: newActive.createdAt.addingTimeInterval(-1)))
                 }
             }
 
-            let file = isPresets ? OpenAPS.FreeAPS.tempTargetsPresets : OpenAPS.Settings.tempTargets
+            let file = isPreset ? OpenAPS.FreeAPS.tempTargetsPresets : OpenAPS.Settings.tempTargets
+
             var uniqEvents: [TempTarget] = []
             self.storage.transaction { storage in
-                storage.append(targets, to: file, uniqBy: \.createdAt)
-                uniqEvents = storage.retrieve(file, as: [TempTarget].self)?
-                    .filter {
-                        guard !isPresets else { return true }
-                        return $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date()
-                    }
-                    .sorted { $0.createdAt > $1.createdAt } ?? []
-                storage.save(Array(uniqEvents), as: file)
+                storage.append(updatedTargets, to: file, uniqBy: \.createdAt)
+
+                let retrievedTargets = storage.retrieve(file, as: [TempTarget].self) ?? []
+                uniqEvents = retrievedTargets
+                    .filter { isPreset || $0.isWithinLastDay }
+                    .sorted(by: { $0.createdAt > $1.createdAt })
+
+                storage.save(uniqEvents, as: file)
             }
+
             broadcaster.notify(TempTargetsObserver.self, on: processQueue) {
                 $0.tempTargetsDidUpdate(uniqEvents)
             }
         }
     }
 
+    // Copy the current Temp Target if it is a RUNNING Preset
+    /// otherwise we would edit the Preset
+    @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
+        let newTempTarget = TempTargetStored(context: viewContext)
+        newTempTarget.date = tempTarget.date
+        newTempTarget.id = tempTarget.id
+        newTempTarget.enabled = tempTarget.enabled
+        newTempTarget.duration = tempTarget.duration
+        newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
+        newTempTarget.name = tempTarget.name
+        newTempTarget.target = tempTarget.target
+        newTempTarget.isPreset = false // no Preset
+
+        await viewContext.perform {
+            do {
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
+                )
+            }
+        }
+
+        return newTempTarget.objectID
+    }
+
+    @MainActor func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
+        await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
+    }
+
     func syncDate() -> Date {
         Date().addingTimeInterval(-1.days.timeInterval)
     }
@@ -107,13 +205,24 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         return Array(Set(treatments).subtracting(Set(uploaded)))
     }
 
-    func storePresets(_ targets: [TempTarget]) {
-        storage.remove(OpenAPS.FreeAPS.tempTargetsPresets)
+//    func storePresets(_ targets: [TempTarget]) {
+//        storage.remove(OpenAPS.FreeAPS.tempTargetsPresets)
+//
+//        saveTempTargetsToStorage(targets, isPreset: true)
+//    }
+//
+    func presets() -> [TempTarget] {
+        storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
+    }
+}
 
-        storeTempTargets(targets, isPresets: true)
+private extension TempTarget {
+    var isActive: Bool {
+        let expirationTime = createdAt.addingTimeInterval(Int(duration).minutes.timeInterval)
+        return expirationTime > Date() && createdAt <= Date()
     }
 
-    func presets() -> [TempTarget] {
-        storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
+    var isWithinLastDay: Bool {
+        createdAt.addingTimeInterval(1.days.timeInterval) > Date()
     }
 }

+ 7 - 1
FreeAPS/Sources/Models/TempTarget.swift

@@ -9,6 +9,8 @@ struct TempTarget: JSON, Identifiable, Equatable, Hashable {
     let duration: Decimal
     let enteredBy: String?
     let reason: String?
+    let isPreset: Bool?
+    let enabled: Bool?
 
     static let manual = "Trio"
     static let custom = "Temp target"
@@ -34,7 +36,9 @@ struct TempTarget: JSON, Identifiable, Equatable, Hashable {
             targetBottom: 0,
             duration: 0,
             enteredBy: TempTarget.manual,
-            reason: TempTarget.cancel
+            reason: TempTarget.cancel,
+            isPreset: nil,
+            enabled: nil
         )
     }
 }
@@ -49,5 +53,7 @@ extension TempTarget {
         case duration
         case enteredBy
         case reason
+        case isPreset
+        case enabled
     }
 }

+ 34 - 111
FreeAPS/Sources/Modules/OverrideConfig/OverrideStateModel.swift

@@ -4,7 +4,7 @@ import SwiftUI
 extension OverrideConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
-        @Injected() var storage: TempTargetsStorage!
+        @Injected() var tempTargetStorage: TempTargetsStorage!
         @Injected() var apsManager: APSManager!
         @Injected() var overrideStorage: OverrideStorage!
 
@@ -446,7 +446,7 @@ extension OverrideConfig.StateModel {
     /// This also needs to be called when we cancel an Temp Target via the Home View to update the State of the Button for this case
     func updateLatestTempTargetConfiguration() {
         Task {
-            let id = await loadLatestTempTargetConfigurations(fetchLimit: 1)
+            let id = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1)
             async let updateState: () = updateLatestTempTargetConfigurationOfState(from: id)
             async let setTempTarget: () = setCurrentTempTarget(from: id)
 
@@ -491,31 +491,14 @@ extension OverrideConfig.StateModel {
         }
     }
 
-    // Fill the array of the Override Presets to display them in the UI
+    // Fill the array of the Temp Target Presets to display them in the UI
     private func setupTempTargetPresetsArray() {
         Task {
-            let ids = await self.fetchForTempTargetPresets()
+            let ids = await tempTargetStorage.fetchForTempTargetPresets()
             await updateTempTargetPresetsArray(with: ids)
         }
     }
 
-    /// Returns the NSManagedObjectID of the Temp Target Presets
-    func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: TempTargetStored.self,
-            onContext: coredataContext,
-            predicate: NSPredicate.allTempTargetPresets,
-            key: "date",
-            ascending: true
-        )
-
-        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
-
-        return await coredataContext.perform {
-            return fetchedResults.map(\.objectID)
-        }
-    }
-
     @MainActor private func updateTempTargetPresetsArray(with IDs: [NSManagedObjectID]) async {
         do {
             let tempTargetObjects = try IDs.compactMap { id in
@@ -531,29 +514,24 @@ extension OverrideConfig.StateModel {
 
     // Creates and enacts a non Preset Temp Target
     func saveCustomTempTarget() async {
-        let newTempTarget = TempTargetStored(context: coredataContext)
-        newTempTarget.date = Date()
-        newTempTarget.id = UUID()
-        newTempTarget.enabled = true
-        newTempTarget.duration = tempTargetDuration as NSDecimalNumber
-        newTempTarget.isUploadedToNS = false
-        newTempTarget.name = tempTargetName
-        newTempTarget.target = tempTargetTarget as NSDecimalNumber
-        newTempTarget.isPreset = false
+        let tempTarget = TempTarget(
+            name: tempTargetName,
+            createdAt: Date(),
+            targetTop: tempTargetTarget,
+            targetBottom: tempTargetTarget,
+            duration: tempTargetDuration,
+            enteredBy: TempTarget.manual,
+            reason: TempTarget.custom,
+            isPreset: false,
+            enabled: true
+        )
+
+        // Save Temp Target to Core Data and to the storage
+        await tempTargetStorage.storeTempTarget(tempTarget: tempTarget)
 
         // disable all TempTargets
         await disableAllActiveOverrides(createOverrideRunEntry: true)
 
-        // Save Temp Target to Core Data
-        do {
-            guard coredataContext.hasChanges else { return }
-            try coredataContext.save()
-        } catch let error as NSError {
-            debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save TempTarget to Core Data with error: \(error.userInfo)"
-            )
-        }
-
         // Reset State variables
         await resetTempTargetState()
 
@@ -563,25 +541,19 @@ extension OverrideConfig.StateModel {
 
     // Creates a new Temp Target Preset
     func saveTempTargetPreset() async {
-        let newTempTarget = TempTargetStored(context: coredataContext)
-        newTempTarget.date = Date()
-        newTempTarget.id = UUID()
-        newTempTarget.enabled = false
-        newTempTarget.duration = tempTargetDuration as NSDecimalNumber
-        newTempTarget.isUploadedToNS = false
-        newTempTarget.name = tempTargetName
-        newTempTarget.target = tempTargetTarget as NSDecimalNumber
-        newTempTarget.isPreset = true
-
-        // Save Temp Target to Core Data
-        do {
-            guard coredataContext.hasChanges else { return }
-            try coredataContext.save()
-        } catch let error as NSError {
-            debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save TempTarget to Core Data with error: \(error.userInfo)"
-            )
-        }
+        let tempTarget = TempTarget(
+            name: tempTargetName,
+            createdAt: Date(),
+            targetTop: tempTargetTarget,
+            targetBottom: tempTargetTarget,
+            duration: tempTargetDuration,
+            enteredBy: TempTarget.manual,
+            reason: TempTarget.custom,
+            isPreset: true,
+            enabled: false
+        )
+
+        await tempTargetStorage.storeTempTarget(tempTarget: tempTarget)
 
         // Reset State variables
         await resetTempTargetState()
@@ -620,27 +592,9 @@ extension OverrideConfig.StateModel {
     }
 
     // Disable all active Temp Targets
-
-    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: TempTargetStored.self,
-            onContext: coredataContext,
-            predicate: NSPredicate.lastActiveTempTarget,
-            key: "date",
-            ascending: true,
-            fetchLimit: fetchLimit
-        )
-
-        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
-
-        return await coredataContext.perform {
-            return fetchedResults.map(\.objectID)
-        }
-    }
-
     @MainActor func disableAllActiveTempTargets(except id: NSManagedObjectID? = nil, createTempTargetRunEntry: Bool) async {
         // Get ALL NSManagedObject IDs of ALL active Temp Targets to cancel every single Temp Target
-        let ids = await loadLatestTempTargetConfigurations(fetchLimit: 0) // 0 = no fetch limit
+        let ids = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 0) // 0 = no fetch limit
 
         await viewContext.perform {
             do {
@@ -696,7 +650,7 @@ extension OverrideConfig.StateModel {
               tempTargetPresetToDuplicate.isPreset == true else { return }
 
         // Copy the current TempTarget-Preset to not edit the underlying Preset
-        let duplidateId = await copyRunningTempTarget(tempTargetPresetToDuplicate)
+        let duplidateId = await tempTargetStorage.copyRunningTempTarget(tempTargetPresetToDuplicate)
 
         // Cancel the duplicated Temp Target
         /// As we are on the Main Thread already we don't need to cancel via the objectID in this case
@@ -720,45 +674,14 @@ extension OverrideConfig.StateModel {
         }
     }
 
-    // Copy the current Temp Target if it is a RUNNING Preset
-    /// otherwise we would edit the Preset
-    @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
-        let newTempTarget = TempTargetStored(context: viewContext)
-        newTempTarget.date = tempTarget.date
-        newTempTarget.id = tempTarget.id
-        newTempTarget.enabled = tempTarget.enabled
-        newTempTarget.duration = tempTarget.duration
-        newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
-        newTempTarget.name = tempTarget.name
-        newTempTarget.target = tempTarget.target
-        newTempTarget.isPreset = false // no Preset
-
-        await viewContext.perform {
-            do {
-                guard self.viewContext.hasChanges else { return }
-                try self.viewContext.save()
-            } catch let error as NSError {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
-                )
-            }
-        }
-
-        return newTempTarget.objectID
-    }
-
     // Deletion of Temp Targets
     func invokeTempTargetPresetDeletion(_ objectID: NSManagedObjectID) async {
-        await deleteOverridePreset(objectID)
+        await tempTargetStorage.deleteOverridePreset(objectID)
 
         // Update Presets View
         setupTempTargetPresetsArray()
     }
 
-    @MainActor func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
-        await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
-    }
-
     @MainActor func resetTempTargetState() async {
         tempTargetName = ""
         tempTargetTarget = 0

+ 5 - 3
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -436,7 +436,7 @@ extension BaseWatchManager: WCSessionDelegate {
             Task {
                 if var preset = tempTargetsStorage.presets().first(where: { $0.id == tempTargetID }) {
                     preset.createdAt = Date()
-                    tempTargetsStorage.storeTempTargets([preset])
+                    await tempTargetsStorage.storeTempTarget(tempTarget: preset)
                     replyHandler(["confirmation": true])
                 } else if tempTargetID == "cancel" {
                     let entry = TempTarget(
@@ -446,9 +446,11 @@ extension BaseWatchManager: WCSessionDelegate {
                         targetBottom: 0,
                         duration: 0,
                         enteredBy: TempTarget.manual,
-                        reason: TempTarget.cancel
+                        reason: TempTarget.cancel,
+                        isPreset: false,
+                        enabled: false
                     )
-                    tempTargetsStorage.storeTempTargets([entry])
+                    await tempTargetsStorage.storeTempTarget(tempTarget: entry)
                     replyHandler(["confirmation": true])
                 } else {
                     replyHandler(["confirmation": false])

+ 1 - 1
FreeAPS/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -65,7 +65,7 @@ import Foundation
 
             // TODO: enact the temp target
             let tempTarget = try intentRequest.findTempTarget(presetToApply)
-            let finalTempTargetApply = try intentRequest.enactTempTarget(tempTarget)
+            let finalTempTargetApply = try await intentRequest.enactTempTarget(tempTarget)
             let formattedTime = decimalToTimeFormattedString(decimal: finalTempTargetApply.duration)
             let displayDetail: String =
                 "Target '\(finalTempTargetApply.displayName)' applied for \(formattedTime)"

+ 1 - 1
FreeAPS/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift

@@ -16,7 +16,7 @@ import Foundation
 
     @MainActor func perform() async throws -> some ProvidesDialog {
         do {
-            try intentRequest.cancelTempTarget()
+            try await intentRequest.cancelTempTarget()
             return .result(
                 dialog: IntentDialog(stringLiteral: "Temporary Target canceled")
             )

+ 4 - 4
FreeAPS/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -34,10 +34,10 @@ import Foundation
 
     // TODO: - probably broken for now...
 
-    func enactTempTarget(_ presetTarget: TempTarget) throws -> TempTarget {
+    func enactTempTarget(_ presetTarget: TempTarget) async throws -> TempTarget {
         var tempTarget = presetTarget
         tempTarget.createdAt = Date()
-        storage.storeTempTargets([tempTarget])
+        await storage.storeTempTarget(tempTarget: tempTarget)
 
         coredataContext.performAndWait {
             var tempTargetsArray = [TempTargetStored]()
@@ -80,8 +80,8 @@ import Foundation
         return tempTarget
     }
 
-    func cancelTempTarget() throws {
-        storage.storeTempTargets([TempTarget.cancel(at: Date())])
+    func cancelTempTarget() async throws {
+        await storage.storeTempTarget(tempTarget: TempTarget.cancel(at: Date()))
         try coredataContext.performAndWait {
             let saveToCoreData = TempTargetStored(context: self.coredataContext)
             saveToCoreData.enabled = false