ソースを参照

Throwing functions

polscm32 1 年間 前
コミット
40afd01c59
61 ファイル変更1968 行追加1548 行削除
  1. 32 22
      Model/CoreDataStack.swift
  2. 30 18
      Model/Helper/CoreDataError.swift
  3. 194 198
      Trio/Sources/APS/APSManager.swift
  4. 1 1
      Trio/Sources/APS/DeviceDataManager.swift
  5. 69 60
      Trio/Sources/APS/FetchGlucoseManager.swift
  6. 1 1
      Trio/Sources/APS/FetchTreatmentsManager.swift
  7. 26 26
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  8. 17 17
      Trio/Sources/APS/Storage/CarbsStorage.swift
  9. 32 27
      Trio/Sources/APS/Storage/ContactImageStorage.swift
  10. 6 8
      Trio/Sources/APS/Storage/DeterminationStorage.swift
  11. 41 35
      Trio/Sources/APS/Storage/GlucoseStorage.swift
  12. 27 33
      Trio/Sources/APS/Storage/OverrideStorage.swift
  13. 13 13
      Trio/Sources/APS/Storage/PumpHistoryStorage.swift
  14. 51 50
      Trio/Sources/APS/Storage/TempTargetsStorage.swift
  15. 9 2
      Trio/Sources/Application/AppDelegate.swift
  16. 125 84
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift
  17. 55 35
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift
  18. 1 1
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel.swift
  19. 1 1
      Trio/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  20. 1 1
      Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  21. 11 4
      Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  22. 1 1
      Trio/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
  23. 1 1
      Trio/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  24. 3 3
      Trio/Sources/Modules/DataTable/DataTableStateModel.swift
  25. 13 6
      Trio/Sources/Modules/Home/HomeStateModel+Setup/BatterySetup.swift
  26. 20 10
      Trio/Sources/Modules/Home/HomeStateModel+Setup/CarbSetup.swift
  27. 20 14
      Trio/Sources/Modules/Home/HomeStateModel+Setup/DeterminationSetup.swift
  28. 19 10
      Trio/Sources/Modules/Home/HomeStateModel+Setup/ForecastSetup.swift
  29. 13 5
      Trio/Sources/Modules/Home/HomeStateModel+Setup/GlucoseSetup.swift
  30. 24 13
      Trio/Sources/Modules/Home/HomeStateModel+Setup/OverrideSetup.swift
  31. 24 9
      Trio/Sources/Modules/Home/HomeStateModel+Setup/PumpHistorySetup.swift
  32. 26 12
      Trio/Sources/Modules/Home/HomeStateModel+Setup/TempTargetSetup.swift
  33. 1 1
      Trio/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  34. 18 13
      Trio/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  35. 15 0
      Trio/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  36. 30 26
      Trio/Sources/Modules/Stat/StatStateModel.swift
  37. 1 1
      Trio/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift
  38. 140 108
      Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift
  39. 98 65
      Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift
  40. 13 13
      Trio/Sources/Services/Calendar/CalendarManager.swift
  41. 69 64
      Trio/Sources/Services/ContactImage/ContactImageManager.swift
  42. 103 74
      Trio/Sources/Services/HealthKit/HealthKitManager.swift
  43. 6 6
      Trio/Sources/Services/LiveActivity/Data/DataManager.swift
  44. 3 3
      Trio/Sources/Services/LiveActivity/LiveActivityBridge.swift
  45. 64 18
      Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift
  46. 116 96
      Trio/Sources/Services/Network/TidepoolManager.swift
  47. 2 2
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+APNS.swift
  48. 8 7
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift
  49. 2 2
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift
  50. 37 23
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Override.swift
  51. 13 13
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+TempTarget.swift
  52. 4 4
      Trio/Sources/Services/RemoteControl/TrioRemoteControl.swift
  53. 3 3
      Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift
  54. 155 136
      Trio/Sources/Services/WatchManager/AppleWatchManager.swift
  55. 105 90
      Trio/Sources/Services/WatchManager/GarminManager.swift
  56. 1 1
      Trio/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift
  57. 2 2
      Trio/Sources/Shortcuts/Override/OverridePresetEntity.swift
  58. 45 48
      Trio/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift
  59. 2 2
      Trio/Sources/Shortcuts/State/StateIntentRequest.swift
  60. 1 1
      Trio/Sources/Shortcuts/TempPresets/TempPresetIntent.swift
  61. 4 5
      Trio/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

+ 32 - 22
Model/CoreDataStack.swift

@@ -214,7 +214,9 @@ extension CoreDataStack {
         _ objectType: T.Type,
         _ objectType: T.Type,
         dateKey: String,
         dateKey: String,
         days: Int,
         days: Int,
-        isPresetKey: String? = nil
+        isPresetKey: String? = nil,
+        callingFunction: String = #function,
+        callingClass: String = #fileID
     ) async throws {
     ) async throws {
         let taskContext = newTaskContext()
         let taskContext = newTaskContext()
         taskContext.name = "deleteContext"
         taskContext.name = "deleteContext"
@@ -254,14 +256,14 @@ extension CoreDataStack {
                       let success = batchDeleteResult.result as? Bool, success
                       let success = batchDeleteResult.result as? Bool, success
                 else {
                 else {
                     debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
                     debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
-                    throw CoreDataError.batchDeleteError
+                    throw CoreDataError.batchDeleteError(function: callingFunction, file: callingClass)
                 }
                 }
             }
             }
 
 
             debugPrint("Successfully deleted data older than \(days) days. \(DebuggingIdentifiers.succeeded)")
             debugPrint("Successfully deleted data older than \(days) days. \(DebuggingIdentifiers.succeeded)")
         } catch {
         } catch {
             debugPrint("Failed to fetch or delete data: \(error.localizedDescription) \(DebuggingIdentifiers.failed)")
             debugPrint("Failed to fetch or delete data: \(error.localizedDescription) \(DebuggingIdentifiers.failed)")
-            throw CoreDataError.batchDeleteError
+            throw CoreDataError.unexpectedError(error: error, function: callingFunction, file: callingClass)
         }
         }
     }
     }
 
 
@@ -270,7 +272,9 @@ extension CoreDataStack {
         childType: Child.Type,
         childType: Child.Type,
         dateKey: String,
         dateKey: String,
         days: Int,
         days: Int,
-        relationshipKey: String // The key of the Child Entity that links to the parent Entity
+        relationshipKey: String, // The key of the Child Entity that links to the parent Entity
+        callingFunction: String = #function,
+        callingClass: String = #fileID
     ) async throws {
     ) async throws {
         let taskContext = newTaskContext()
         let taskContext = newTaskContext()
         taskContext.name = "deleteContext"
         taskContext.name = "deleteContext"
@@ -316,7 +320,7 @@ extension CoreDataStack {
                       let success = batchDeleteResult.result as? Bool, success
                       let success = batchDeleteResult.result as? Bool, success
                 else {
                 else {
                     debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
                     debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
-                    throw CoreDataError.batchDeleteError
+                    throw CoreDataError.batchDeleteError(function: callingFunction, file: callingClass)
                 }
                 }
             }
             }
 
 
@@ -325,7 +329,7 @@ extension CoreDataStack {
             )
             )
         } catch {
         } catch {
             debugPrint("Failed to fetch or delete data: \(error.localizedDescription) \(DebuggingIdentifiers.failed)")
             debugPrint("Failed to fetch or delete data: \(error.localizedDescription) \(DebuggingIdentifiers.failed)")
-            throw CoreDataError.batchDeleteError
+            throw CoreDataError.unexpectedError(error: error, function: callingFunction, file: callingClass)
         }
         }
     }
     }
 }
 }
@@ -346,7 +350,7 @@ extension CoreDataStack {
         propertiesToFetch: [String]? = nil,
         propertiesToFetch: [String]? = nil,
         callingFunction: String = #function,
         callingFunction: String = #function,
         callingClass: String = #fileID
         callingClass: String = #fileID
-    ) -> [Any] {
+    ) throws -> [Any] {
         let request = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: type))
         let request = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: type))
         request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
         request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
         request.predicate = predicate
         request.predicate = predicate
@@ -367,7 +371,7 @@ extension CoreDataStack {
         context.transactionAuthor = "fetchEntities"
         context.transactionAuthor = "fetchEntities"
 
 
         /// we need to ensure that the fetch immediately returns a value as long as the whole app does not use the async await pattern, otherwise we could perform this asynchronously with backgroundContext.perform and not block the thread
         /// we need to ensure that the fetch immediately returns a value as long as the whole app does not use the async await pattern, otherwise we could perform this asynchronously with backgroundContext.perform and not block the thread
-        return context.performAndWait {
+        return try context.performAndWait {
             do {
             do {
                 if propertiesToFetch != nil {
                 if propertiesToFetch != nil {
                     return try context.fetch(request) as? [[String: Any]] ?? []
                     return try context.fetch(request) as? [[String: Any]] ?? []
@@ -375,11 +379,10 @@ extension CoreDataStack {
                     return try context.fetch(request) as? [T] ?? []
                     return try context.fetch(request) as? [T] ?? []
                 }
                 }
             } catch let error as NSError {
             } catch let error as NSError {
-                debugPrint(
-                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
+                throw CoreDataError.fetchError(
+                    function: callingFunction,
+                    file: callingClass
                 )
                 )
-
-                return []
             }
             }
         }
         }
     }
     }
@@ -397,10 +400,11 @@ extension CoreDataStack {
         relationshipKeyPathsForPrefetching: [String]? = nil,
         relationshipKeyPathsForPrefetching: [String]? = nil,
         callingFunction: String = #function,
         callingFunction: String = #function,
         callingClass: String = #fileID
         callingClass: String = #fileID
-    ) async -> Any {
+    ) async throws -> Any {
         let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: String(describing: type))
         let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: String(describing: type))
         request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
         request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
         request.predicate = predicate
         request.predicate = predicate
+
         if let limit = fetchLimit {
         if let limit = fetchLimit {
             request.fetchLimit = limit
             request.fetchLimit = limit
         }
         }
@@ -420,7 +424,7 @@ extension CoreDataStack {
         context.name = "fetchContext"
         context.name = "fetchContext"
         context.transactionAuthor = "fetchEntities"
         context.transactionAuthor = "fetchEntities"
 
 
-        return await context.perform {
+        return try await context.perform {
             do {
             do {
                 if propertiesToFetch != nil {
                 if propertiesToFetch != nil {
                     return try context.fetch(request) as? [[String: Any]] ?? []
                     return try context.fetch(request) as? [[String: Any]] ?? []
@@ -428,10 +432,11 @@ extension CoreDataStack {
                     return try context.fetch(request) as? [T] ?? []
                     return try context.fetch(request) as? [T] ?? []
                 }
                 }
             } catch let error as NSError {
             } catch let error as NSError {
-                debugPrint(
-                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
+                throw CoreDataError.unexpectedError(
+                    error: error,
+                    function: callingFunction,
+                    file: callingClass
                 )
                 )
-                return []
             }
             }
         }
         }
     }
     }
@@ -439,9 +444,11 @@ extension CoreDataStack {
     // Get NSManagedObject
     // Get NSManagedObject
     func getNSManagedObject<T: NSManagedObject>(
     func getNSManagedObject<T: NSManagedObject>(
         with ids: [NSManagedObjectID],
         with ids: [NSManagedObjectID],
-        context: NSManagedObjectContext
-    ) async -> [T] {
-        await context.perform {
+        context: NSManagedObjectContext,
+        callingFunction: String = #function,
+        callingClass: String = #fileID
+    ) async throws -> [T] {
+        try await context.perform {
             var objects = [T]()
             var objects = [T]()
             do {
             do {
                 for id in ids {
                 for id in ids {
@@ -449,10 +456,13 @@ extension CoreDataStack {
                         objects.append(object)
                         objects.append(object)
                     }
                     }
                 }
                 }
+                return objects
             } catch {
             } catch {
-                debugPrint("Failed to fetch objects: \(error.localizedDescription)")
+                throw CoreDataError.fetchError(
+                    function: callingFunction,
+                    file: callingClass
+                )
             }
             }
-            return objects
         }
         }
     }
     }
 }
 }

+ 30 - 18
Model/Helper/CoreDataError.swift

@@ -1,29 +1,41 @@
 import Foundation
 import Foundation
 
 
 enum CoreDataError: Error {
 enum CoreDataError: Error {
-    case creationError
-    case batchInsertError
-    case batchDeleteError
-    case persistentHistoryChangeError
-    case unexpectedError(error: Error)
-    case fetchError
+    case validationError(function: String, file: String)
+    case creationError(function: String, file: String)
+    case batchInsertError(function: String, file: String)
+    case batchDeleteError(function: String, file: String)
+    case persistentHistoryChangeError(function: String, file: String)
+    case unexpectedError(error: Error, function: String, file: String)
+    case fetchError(function: String, file: String)
 }
 }
 
 
 extension CoreDataError: LocalizedError {
 extension CoreDataError: LocalizedError {
     var errorDescription: String? {
     var errorDescription: String? {
         switch self {
         switch self {
-        case .creationError:
-            return NSLocalizedString("Failed to create a new object.", comment: "")
-        case .batchInsertError:
-            return NSLocalizedString("Failed to execute a batch insert request.", comment: "")
-        case .batchDeleteError:
-            return NSLocalizedString("Failed to execute a batch delete request.", comment: "")
-        case .persistentHistoryChangeError:
-            return NSLocalizedString("Failed to execute a persistent history change request.", comment: "")
-        case let .unexpectedError(error):
-            return NSLocalizedString("Received unexpected error. \(error.localizedDescription)", comment: "")
-        case .fetchError:
-            return NSLocalizedString("Failed to fetch object \(DebuggingIdentifiers.failed).", comment: "")
+        case let .creationError(function, file):
+            return NSLocalizedString("Failed to create a new object in \(function) from \(file).", comment: "")
+        case let .batchInsertError(function, file):
+            return NSLocalizedString("Failed to execute a batch insert request in \(function) from \(file).", comment: "")
+        case let .batchDeleteError(function, file):
+            return NSLocalizedString("Failed to execute a batch delete request in \(function) from \(file).", comment: "")
+        case let .persistentHistoryChangeError(function, file):
+            return NSLocalizedString(
+                "Failed to execute a persistent history change request in \(function) from \(file).",
+                comment: ""
+            )
+        case let .unexpectedError(error, function, file):
+            return NSLocalizedString(
+                "Received unexpected error in \(function) from \(file): \(error.localizedDescription)",
+                comment: ""
+            )
+        case let .fetchError(function, file):
+            return NSLocalizedString(
+                "Failed to fetch object \(DebuggingIdentifiers.failed) in \(function) from \(file).",
+                comment: ""
+            )
+        case let .validationError(function, file):
+            return NSLocalizedString("Failed to validate object in \(function) from \(file).", comment: "")
         }
         }
     }
     }
 }
 }

+ 194 - 198
Trio/Sources/APS/APSManager.swift

@@ -20,7 +20,7 @@ protocol APSManager {
     var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
     var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
     var isManualTempBasal: Bool { get }
     var isManualTempBasal: Bool { get }
     func enactTempBasal(rate: Double, duration: TimeInterval) async
     func enactTempBasal(rate: Double, duration: TimeInterval) async
-    func determineBasal() async -> Bool
+    func determineBasal() async throws -> Bool
     func determineBasalSync() async
     func determineBasalSync() async
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     func roundBolus(amount: Decimal) -> Decimal
@@ -130,7 +130,14 @@ final class BaseAPSManager: APSManager, Injectable {
             let wasParsed = storage.parseOnFileSettingsToMgdL()
             let wasParsed = storage.parseOnFileSettingsToMgdL()
             if wasParsed {
             if wasParsed {
                 Task {
                 Task {
-                    await openAPS.createProfiles()
+                    do {
+                        try await openAPS.createProfiles()
+                    } catch {
+                        debug(
+                            .apsManager,
+                            "\(DebuggingIdentifiers.failed) Error creating profiles: \(error.localizedDescription)"
+                        )
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -242,7 +249,7 @@ final class BaseAPSManager: APSManager, Injectable {
             isLooping.send(true)
             isLooping.send(true)
 
 
             do {
             do {
-                if await !determineBasal() {
+                if try await !determineBasal() {
                     throw APSError.apsError(message: "Determine basal failed")
                     throw APSError.apsError(message: "Determine basal failed")
                 }
                 }
 
 
@@ -346,11 +353,11 @@ final class BaseAPSManager: APSManager, Injectable {
         return false
         return false
     }
     }
 
 
-    func determineBasal() async -> Bool {
+    func determineBasal() async throws -> Bool {
         debug(.apsManager, "Start determine basal")
         debug(.apsManager, "Start determine basal")
 
 
         // Fetch glucose asynchronously
         // Fetch glucose asynchronously
-        let glucose = await fetchGlucose(predicate: NSPredicate.predicateForOneHourAgo, fetchLimit: 6)
+        let glucose = try await fetchGlucose(predicate: NSPredicate.predicateForOneHourAgo, fetchLimit: 6)
 
 
         // Perform the context-related checks and actions
         // Perform the context-related checks and actions
         let isValidGlucoseData = await privateContext.perform {
         let isValidGlucoseData = await privateContext.perform {
@@ -390,7 +397,7 @@ final class BaseAPSManager: APSManager, Injectable {
             async let autosenseResult = autosense()
             async let autosenseResult = autosense()
 
 
             _ = try await autosenseResult
             _ = try await autosenseResult
-            await openAPS.createProfiles()
+            try await openAPS.createProfiles()
             let determination = try await openAPS.determineBasal(currentTemp: await currentTemp, clock: now)
             let determination = try await openAPS.determineBasal(currentTemp: await currentTemp, clock: now)
 
 
             if let determination = determination {
             if let determination = determination {
@@ -410,12 +417,19 @@ final class BaseAPSManager: APSManager, Injectable {
     }
     }
 
 
     func determineBasalSync() async {
     func determineBasalSync() async {
-        _ = await determineBasal()
+        do {
+            _ = try await determineBasal()
+        } catch {
+            debug(
+                .apsManager,
+                "\(DebuggingIdentifiers.failed) Error performing determine basal sync: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination? {
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination? {
         do {
         do {
-            let temp = await fetchCurrentTempBasal(date: Date.now)
+            let temp = try await fetchCurrentTempBasal(date: Date.now)
             return try await openAPS.determineBasal(currentTemp: temp, clock: Date(), carbs: carbs, iob: iob, simulation: true)
             return try await openAPS.determineBasal(currentTemp: temp, clock: Date(), carbs: carbs, iob: iob, simulation: true)
         } catch {
         } catch {
             debugPrint(
             debugPrint(
@@ -522,8 +536,8 @@ final class BaseAPSManager: APSManager, Injectable {
         }
         }
     }
     }
 
 
-    private func fetchCurrentTempBasal(date: Date) async -> TempBasal {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchCurrentTempBasal(date: Date) async throws -> TempBasal {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: privateContext,
             onContext: privateContext,
             predicate: NSPredicate.recentPumpHistory,
             predicate: NSPredicate.recentPumpHistory,
@@ -562,7 +576,7 @@ final class BaseAPSManager: APSManager, Injectable {
     }
     }
 
 
     private func enactDetermination() async throws {
     private func enactDetermination() async throws {
-        guard let determinationID = await determinationStorage
+        guard let determinationID = try await determinationStorage
             .fetchLastDeterminationObjectID(predicate: NSPredicate.predicateFor30MinAgoForDetermination).first
             .fetchLastDeterminationObjectID(predicate: NSPredicate.predicateFor30MinAgoForDetermination).first
         else {
         else {
             throw APSError.apsError(message: "Determination not found")
             throw APSError.apsError(message: "Determination not found")
@@ -617,35 +631,39 @@ final class BaseAPSManager: APSManager, Injectable {
     }
     }
 
 
     private func reportEnacted(wasEnacted: Bool) async {
     private func reportEnacted(wasEnacted: Bool) async {
-        guard let determinationID = await determinationStorage
-            .fetchLastDeterminationObjectID(predicate: NSPredicate.predicateFor30MinAgoForDetermination).first
-        else {
-            return
-        }
-        await privateContext.perform {
-            if let determinationUpdated = self.privateContext.object(with: determinationID) as? OrefDetermination {
+        do {
+            guard let determinationID = try await determinationStorage
+                .fetchLastDeterminationObjectID(predicate: NSPredicate.predicateFor30MinAgoForDetermination).first
+            else {
+                debug(.apsManager, "No determination found to report enacted status")
+                return
+            }
+
+            try await privateContext.perform {
+                guard let determinationUpdated = try self.privateContext
+                    .existingObject(with: determinationID) as? OrefDetermination
+                else {
+                    debug(.apsManager, "Could not find determination object in context")
+                    return
+                }
+
                 determinationUpdated.timestamp = Date()
                 determinationUpdated.timestamp = Date()
                 determinationUpdated.enacted = wasEnacted
                 determinationUpdated.enacted = wasEnacted
                 determinationUpdated.isUploadedToNS = false
                 determinationUpdated.isUploadedToNS = false
 
 
-                do {
-                    guard self.privateContext.hasChanges else { return }
-                    try self.privateContext.save()
-                    debugPrint("Update successful in reportEnacted() \(DebuggingIdentifiers.succeeded)")
-                } catch {
-                    debugPrint(
-                        "Failed  \(DebuggingIdentifiers.succeeded) to save context in reportEnacted(): \(error.localizedDescription)"
-                    )
-                }
-
+                guard self.privateContext.hasChanges else { return }
+                try self.privateContext.save()
                 debug(.apsManager, "Determination enacted. Enacted: \(wasEnacted)")
                 debug(.apsManager, "Determination enacted. Enacted: \(wasEnacted)")
 
 
                 Task.detached(priority: .low) {
                 Task.detached(priority: .low) {
                     await self.statistics()
                     await self.statistics()
                 }
                 }
-            } else {
-                debugPrint("Failed to update OrefDetermination in reportEnacted()")
             }
             }
+        } catch {
+            debug(
+                .apsManager,
+                "\(DebuggingIdentifiers.failed) Error reporting enacted status: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 
 
@@ -805,8 +823,8 @@ final class BaseAPSManager: APSManager, Injectable {
     }
     }
 
 
     // fetch glucose for time interval
     // fetch glucose for time interval
-    func fetchGlucose(predicate: NSPredicate, fetchLimit: Int? = nil, batchSize: Int? = nil) async -> [GlucoseStored] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchGlucose(predicate: NSPredicate, fetchLimit: Int? = nil, batchSize: Int? = nil) async throws -> [GlucoseStored] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: privateContext,
             onContext: privateContext,
             predicate: predicate,
             predicate: predicate,
@@ -843,7 +861,7 @@ final class BaseAPSManager: APSManager, Injectable {
             async let carbTotal = carbsForStats()
             async let carbTotal = carbsForStats()
             async let preferences = settingsManager.preferences
             async let preferences = settingsManager.preferences
 
 
-            let loopStats = await loopStats(oneDayGlucose: await glucoseStats.oneDayGlucose.readings)
+            let loopStats = await loopStats(oneDayGlucose: Double(rawValue: (await glucoseStats?.oneDayGlucose.readings)!) ?? 0.0)
 
 
             // Only save and upload once per day
             // Only save and upload once per day
             guard (-1 * (await lastLoopForStats ?? .distantPast).timeIntervalSinceNow.hours) > 22 else { return }
             guard (-1 * (await lastLoopForStats ?? .distantPast).timeIntervalSinceNow.hours) > 22 else { return }
@@ -913,7 +931,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 scheduled_basal: 0,
                 scheduled_basal: 0,
                 total_average: 0
                 total_average: 0
             )
             )
-            let processedGlucoseStats = await glucoseStats
+            guard let processedGlucoseStats = await glucoseStats else { return }
             let hbA1cDisplayUnit = processedGlucoseStats.hbA1cDisplayUnit
             let hbA1cDisplayUnit = processedGlucoseStats.hbA1cDisplayUnit
 
 
             let dailystat = await Statistics(
             let dailystat = await Statistics(
@@ -932,7 +950,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 insulinType: insulin_type.rawValue,
                 insulinType: insulin_type.rawValue,
                 peakActivityTime: iPa,
                 peakActivityTime: iPa,
                 Carbs_24h: await carbTotal,
                 Carbs_24h: await carbTotal,
-                GlucoseStorage_Days: Decimal(roundDouble(processedGlucoseStats.numberofDays, 1)),
+                GlucoseStorage_Days: Decimal(roundDouble(Double(rawValue: processedGlucoseStats.numberofDays) ?? 0.0, 1)),
                 Statistics: Stats(
                 Statistics: Stats(
                     Distribution: processedGlucoseStats.TimeInRange,
                     Distribution: processedGlucoseStats.TimeInRange,
                     Glucose: processedGlucoseStats.avg,
                     Glucose: processedGlucoseStats.avg,
@@ -1092,175 +1110,153 @@ final class BaseAPSManager: APSManager, Injectable {
         return (currentTDD, tddTotalAverage)
         return (currentTDD, tddTotalAverage)
     }
     }
 
 
-    private func glucoseForStats() async
-        -> (
-            oneDayGlucose: (
-                ifcc: Double,
-                ngsp: Double,
-                average: Double,
-                median: Double,
-                sd: Double,
-                cv: Double,
-                readings: Double
-            ),
-            hbA1cDisplayUnit: HbA1cDisplayUnit,
-            numberofDays: Double,
-            TimeInRange: TIRs,
-            avg: Averages,
-            hbs: Durations,
-            variance: Variance
-        )
-    {
-        // Get the Glucose Values
-        let glucose24h = await fetchGlucose(predicate: NSPredicate.predicateForOneDayAgo, fetchLimit: 288, batchSize: 50)
-        let glucoseOneWeek = await fetchGlucose(
-            predicate: NSPredicate.predicateForOneWeek,
-            fetchLimit: 288 * 7,
-            batchSize: 250
-        )
-        let glucoseOneMonth = await fetchGlucose(
-            predicate: NSPredicate.predicateForOneMonth,
-            fetchLimit: 288 * 7 * 30,
-            batchSize: 500
-        )
-        let glucoseThreeMonths = await fetchGlucose(
-            predicate: NSPredicate.predicateForThreeMonths,
-            fetchLimit: 288 * 7 * 30 * 3,
-            batchSize: 1000
-        )
+    private func glucoseForStats() async -> (
+        oneDayGlucose: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double),
+        hbA1cDisplayUnit: HbA1cDisplayUnit,
+        numberofDays: Double,
+        TimeInRange: TIRs,
+        avg: Averages,
+        hbs: Durations,
+        variance: Variance
+    )? {
+        do {
+            // Get the Glucose Values
+            let glucose24h = try await fetchGlucose(predicate: NSPredicate.predicateForOneDayAgo, fetchLimit: 288, batchSize: 50)
+            let glucoseOneWeek = try await fetchGlucose(
+                predicate: NSPredicate.predicateForOneWeek,
+                fetchLimit: 288 * 7,
+                batchSize: 250
+            )
+            let glucoseOneMonth = try await fetchGlucose(
+                predicate: NSPredicate.predicateForOneMonth,
+                fetchLimit: 288 * 7 * 30,
+                batchSize: 500
+            )
+            let glucoseThreeMonths = try await fetchGlucose(
+                predicate: NSPredicate.predicateForThreeMonths,
+                fetchLimit: 288 * 7 * 30 * 3,
+                batchSize: 1000
+            )
 
 
-        var result: (
-            oneDayGlucose: (
-                ifcc: Double,
-                ngsp: Double,
-                average: Double,
-                median: Double,
-                sd: Double,
-                cv: Double,
-                readings: Double
-            ),
-            hbA1cDisplayUnit: HbA1cDisplayUnit,
-            numberofDays: Double,
-            TimeInRange: TIRs,
-            avg: Averages,
-            hbs: Durations,
-            variance: Variance
-        )?
+            return await privateContext.perform {
+                let units = self.settingsManager.settings.units
+
+                // First date
+                let previous = glucoseThreeMonths.last?.date ?? Date()
+                // Last date (recent)
+                let current = glucoseThreeMonths.first?.date ?? Date()
+                // Total time in days
+                let numberOfDays = (current - previous).timeInterval / 8.64E4
+
+                // Get glucose computations for every case
+                let oneDayGlucose = self.glucoseStats(glucose24h)
+                let sevenDaysGlucose = self.glucoseStats(glucoseOneWeek)
+                let thirtyDaysGlucose = self.glucoseStats(glucoseOneMonth)
+                let totalDaysGlucose = self.glucoseStats(glucoseThreeMonths)
+
+                let median = Durations(
+                    day: self.roundDecimal(Decimal(oneDayGlucose.median), 1),
+                    week: self.roundDecimal(Decimal(sevenDaysGlucose.median), 1),
+                    month: self.roundDecimal(Decimal(thirtyDaysGlucose.median), 1),
+                    total: self.roundDecimal(Decimal(totalDaysGlucose.median), 1)
+                )
 
 
-        await privateContext.perform {
-            let units = self.settingsManager.settings.units
-
-            // First date
-            let previous = glucoseThreeMonths.last?.date ?? Date()
-            // Last date (recent)
-            let current = glucoseThreeMonths.first?.date ?? Date()
-            // Total time in days
-            let numberOfDays = (current - previous).timeInterval / 8.64E4
-
-            // Get glucose computations for every case
-            let oneDayGlucose = self.glucoseStats(glucose24h)
-            let sevenDaysGlucose = self.glucoseStats(glucoseOneWeek)
-            let thirtyDaysGlucose = self.glucoseStats(glucoseOneMonth)
-            let totalDaysGlucose = self.glucoseStats(glucoseThreeMonths)
-
-            let median = Durations(
-                day: self.roundDecimal(Decimal(oneDayGlucose.median), 1),
-                week: self.roundDecimal(Decimal(sevenDaysGlucose.median), 1),
-                month: self.roundDecimal(Decimal(thirtyDaysGlucose.median), 1),
-                total: self.roundDecimal(Decimal(totalDaysGlucose.median), 1)
-            )
+                let hbA1cDisplayUnit = self.settingsManager.settings.hbA1cDisplayUnit
+
+                let hbs = Durations(
+                    day: hbA1cDisplayUnit == .mmolMol ?
+                        self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) :
+                        self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
+                    week: hbA1cDisplayUnit == .mmolMol ?
+                        self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) :
+                        self.roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
+                    month: hbA1cDisplayUnit == .mmolMol ?
+                        self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) :
+                        self.roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
+                    total: hbA1cDisplayUnit == .mmolMol ?
+                        self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) :
+                        self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
+                )
 
 
-            let hbA1cDisplayUnit = self.settingsManager.settings.hbA1cDisplayUnit
-
-            let hbs = Durations(
-                day: hbA1cDisplayUnit == .mmolMol ?
-                    self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) :
-                    self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
-                week: hbA1cDisplayUnit == .mmolMol ?
-                    self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) :
-                    self.roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
-                month: hbA1cDisplayUnit == .mmolMol ?
-                    self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) :
-                    self.roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
-                total: hbA1cDisplayUnit == .mmolMol ?
-                    self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) :
-                    self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
-            )
+                var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var sevenDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                var totalDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
+                // Get TIR computations for every case
+                oneDay_ = self.tir(glucose24h)
+                sevenDays_ = self.tir(glucoseOneWeek)
+                thirtyDays_ = self.tir(glucoseOneMonth)
+                totalDays_ = self.tir(glucoseThreeMonths)
+
+                let tir = Durations(
+                    day: self.roundDecimal(Decimal(oneDay_.TIR), 1),
+                    week: self.roundDecimal(Decimal(sevenDays_.TIR), 1),
+                    month: self.roundDecimal(Decimal(thirtyDays_.TIR), 1),
+                    total: self.roundDecimal(Decimal(totalDays_.TIR), 1)
+                )
+                let hypo = Durations(
+                    day: Decimal(oneDay_.hypos),
+                    week: Decimal(sevenDays_.hypos),
+                    month: Decimal(thirtyDays_.hypos),
+                    total: Decimal(totalDays_.hypos)
+                )
+                let hyper = Durations(
+                    day: Decimal(oneDay_.hypers),
+                    week: Decimal(sevenDays_.hypers),
+                    month: Decimal(thirtyDays_.hypers),
+                    total: Decimal(totalDays_.hypers)
+                )
+                let normal = Durations(
+                    day: Decimal(oneDay_.normal_),
+                    week: Decimal(sevenDays_.normal_),
+                    month: Decimal(thirtyDays_.normal_),
+                    total: Decimal(totalDays_.normal_)
+                )
+                let range = Threshold(
+                    low: units == .mmolL ? self.roundDecimal(self.settingsManager.settings.low.asMmolL, 1) :
+                        self.roundDecimal(self.settingsManager.settings.low, 0),
+                    high: units == .mmolL ? self.roundDecimal(self.settingsManager.settings.high.asMmolL, 1) :
+                        self.roundDecimal(self.settingsManager.settings.high, 0)
+                )
+                let TimeInRange = TIRs(
+                    TIR: tir,
+                    Hypos: hypo,
+                    Hypers: hyper,
+                    Threshold: range,
+                    Euglycemic: normal
+                )
+                let avgs = Durations(
+                    day: self.roundDecimal(Decimal(oneDayGlucose.average), 1),
+                    week: self.roundDecimal(Decimal(sevenDaysGlucose.average), 1),
+                    month: self.roundDecimal(Decimal(thirtyDaysGlucose.average), 1),
+                    total: self.roundDecimal(Decimal(totalDaysGlucose.average), 1)
+                )
+                let avg = Averages(Average: avgs, Median: median)
+                // Standard Deviations
+                let standardDeviations = Durations(
+                    day: self.roundDecimal(Decimal(oneDayGlucose.sd), 1),
+                    week: self.roundDecimal(Decimal(sevenDaysGlucose.sd), 1),
+                    month: self.roundDecimal(Decimal(thirtyDaysGlucose.sd), 1),
+                    total: self.roundDecimal(Decimal(totalDaysGlucose.sd), 1)
+                )
+                // CV = standard deviation / sample mean x 100
+                let cvs = Durations(
+                    day: self.roundDecimal(Decimal(oneDayGlucose.cv), 1),
+                    week: self.roundDecimal(Decimal(sevenDaysGlucose.cv), 1),
+                    month: self.roundDecimal(Decimal(thirtyDaysGlucose.cv), 1),
+                    total: self.roundDecimal(Decimal(totalDaysGlucose.cv), 1)
+                )
+                let variance = Variance(SD: standardDeviations, CV: cvs)
 
 
-            var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-            var sevenDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-            var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-            var totalDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-            // Get TIR computations for every case
-            oneDay_ = self.tir(glucose24h)
-            sevenDays_ = self.tir(glucoseOneWeek)
-            thirtyDays_ = self.tir(glucoseOneMonth)
-            totalDays_ = self.tir(glucoseThreeMonths)
-
-            let tir = Durations(
-                day: self.roundDecimal(Decimal(oneDay_.TIR), 1),
-                week: self.roundDecimal(Decimal(sevenDays_.TIR), 1),
-                month: self.roundDecimal(Decimal(thirtyDays_.TIR), 1),
-                total: self.roundDecimal(Decimal(totalDays_.TIR), 1)
-            )
-            let hypo = Durations(
-                day: Decimal(oneDay_.hypos),
-                week: Decimal(sevenDays_.hypos),
-                month: Decimal(thirtyDays_.hypos),
-                total: Decimal(totalDays_.hypos)
-            )
-            let hyper = Durations(
-                day: Decimal(oneDay_.hypers),
-                week: Decimal(sevenDays_.hypers),
-                month: Decimal(thirtyDays_.hypers),
-                total: Decimal(totalDays_.hypers)
-            )
-            let normal = Durations(
-                day: Decimal(oneDay_.normal_),
-                week: Decimal(sevenDays_.normal_),
-                month: Decimal(thirtyDays_.normal_),
-                total: Decimal(totalDays_.normal_)
-            )
-            let range = Threshold(
-                low: units == .mmolL ? self.roundDecimal(self.settingsManager.settings.low.asMmolL, 1) :
-                    self.roundDecimal(self.settingsManager.settings.low, 0),
-                high: units == .mmolL ? self.roundDecimal(self.settingsManager.settings.high.asMmolL, 1) :
-                    self.roundDecimal(self.settingsManager.settings.high, 0)
-            )
-            let TimeInRange = TIRs(
-                TIR: tir,
-                Hypos: hypo,
-                Hypers: hyper,
-                Threshold: range,
-                Euglycemic: normal
-            )
-            let avgs = Durations(
-                day: self.roundDecimal(Decimal(oneDayGlucose.average), 1),
-                week: self.roundDecimal(Decimal(sevenDaysGlucose.average), 1),
-                month: self.roundDecimal(Decimal(thirtyDaysGlucose.average), 1),
-                total: self.roundDecimal(Decimal(totalDaysGlucose.average), 1)
-            )
-            let avg = Averages(Average: avgs, Median: median)
-            // Standard Deviations
-            let standardDeviations = Durations(
-                day: self.roundDecimal(Decimal(oneDayGlucose.sd), 1),
-                week: self.roundDecimal(Decimal(sevenDaysGlucose.sd), 1),
-                month: self.roundDecimal(Decimal(thirtyDaysGlucose.sd), 1),
-                total: self.roundDecimal(Decimal(totalDaysGlucose.sd), 1)
-            )
-            // CV = standard deviation / sample mean x 100
-            let cvs = Durations(
-                day: self.roundDecimal(Decimal(oneDayGlucose.cv), 1),
-                week: self.roundDecimal(Decimal(sevenDaysGlucose.cv), 1),
-                month: self.roundDecimal(Decimal(thirtyDaysGlucose.cv), 1),
-                total: self.roundDecimal(Decimal(totalDaysGlucose.cv), 1)
+                return (oneDayGlucose, hbA1cDisplayUnit, numberOfDays, TimeInRange, avg, hbs, variance)
+            }
+        } catch {
+            debug(
+                .apsManager,
+                "\(DebuggingIdentifiers.failed) Error fetching glucose for stats: \(error.localizedDescription)"
             )
             )
-            let variance = Variance(SD: standardDeviations, CV: cvs)
-
-            result = (oneDayGlucose, hbA1cDisplayUnit, numberOfDays, TimeInRange, avg, hbs, variance)
+            return nil
         }
         }
-
-        return result!
     }
     }
 
 
     private func loopStats(loopStatRecord: LoopStats) {
     private func loopStats(loopStatRecord: LoopStats) {

+ 1 - 1
Trio/Sources/APS/DeviceDataManager.swift

@@ -523,7 +523,7 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
                 guard let type = $0.type, type == .tempBasal else { return true }
                 guard let type = $0.type, type == .tempBasal else { return true }
                 return $0.dose?.unitsPerHour ?? 0 <= Double(settingsManager.pumpSettings.maxBasal)
                 return $0.dose?.unitsPerHour ?? 0 <= Double(settingsManager.pumpSettings.maxBasal)
             }
             }
-            await pumpHistoryStorage.storePumpEvents(events)
+            try await pumpHistoryStorage.storePumpEvents(events)
             lastEventDate = events.last?.date
             lastEventDate = events.last?.date
             completion(nil)
             completion(nil)
         }
         }

+ 69 - 60
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -8,6 +8,7 @@ import Swinject
 import UIKit
 import UIKit
 
 
 protocol FetchGlucoseManager: SourceInfoProvider {
 protocol FetchGlucoseManager: SourceInfoProvider {
+    func updateGlucoseStore(newBloodGlucose: [BloodGlucose])
     func updateGlucoseSource(cgmGlucoseSourceType: CGMType, cgmGlucosePluginId: String, newManager: CGMManagerUI?)
     func updateGlucoseSource(cgmGlucoseSourceType: CGMType, cgmGlucosePluginId: String, newManager: CGMManagerUI?)
     func deleteGlucoseSource()
     func deleteGlucoseSource()
     func removeCalibrations()
     func removeCalibrations()
@@ -75,6 +76,44 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         subscribe()
         subscribe()
     }
     }
 
 
+    /// The function used to start the timer sync - Function of the variable defined in config
+    private func subscribe() {
+        timer.publisher
+            .receive(on: processQueue)
+            .flatMap { [self] _ -> AnyPublisher<[BloodGlucose], Never> in
+                debug(.nightscout, "FetchGlucoseManager timer heartbeat")
+                if let glucoseSource = self.glucoseSource {
+                    return glucoseSource.fetch(self.timer).eraseToAnyPublisher()
+                } else {
+                    return Empty(completeImmediately: false).eraseToAnyPublisher()
+                }
+            }
+            .sink { glucose in
+                debug(.nightscout, "FetchGlucoseManager callback sensor")
+                Publishers.CombineLatest(
+                    Just(glucose),
+                    Just(self.glucoseStorage.syncDate())
+                )
+                .eraseToAnyPublisher()
+                .sink { newGlucose, syncDate in
+                    Task {
+                        do {
+                            try await self.glucoseStoreAndHeartDecision(
+                                syncDate: syncDate,
+                                glucose: newGlucose
+                            )
+                        } catch {
+                            debug(.deviceManager, "Failed to store glucose: \(error.localizedDescription)")
+                        }
+                    }
+                }
+                .store(in: &self.lifetime)
+            }
+            .store(in: &lifetime)
+        timer.fire()
+        timer.resume()
+    }
+
     var glucoseSource: GlucoseSource!
     var glucoseSource: GlucoseSource!
 
 
     func removeCalibrations() {
     func removeCalibrations() {
@@ -164,8 +203,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         return Manager.init(rawState: rawState)
         return Manager.init(rawState: rawState)
     }
     }
 
 
-    private func fetchGlucose() async -> [GlucoseStored]? {
-        await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [GlucoseStored]? {
+        try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateFor30MinAgo,
             predicate: NSPredicate.predicateFor30MinAgo,
@@ -175,10 +214,12 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         ) as? [GlucoseStored]
         ) as? [GlucoseStored]
     }
     }
 
 
-    private func processGlucose() async -> [BloodGlucose] {
-        guard let results = await fetchGlucose() else { return [] }
+    private func processGlucose() async throws -> [BloodGlucose] {
+        let results = try await fetchGlucose()
+        guard let results else { return [] }
+
         return await context.perform {
         return await context.perform {
-            return results.map { result in
+            results.map { result in
                 BloodGlucose(
                 BloodGlucose(
                     sgv: Int(result.glucose),
                     sgv: Int(result.glucose),
                     direction: BloodGlucose.Direction(from: result.direction ?? ""),
                     direction: BloodGlucose.Direction(from: result.direction ?? ""),
@@ -194,7 +235,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         }
         }
     }
     }
 
 
-    private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose]) async {
+    private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose]) async throws {
         // calibration add if required only for sensor
         // calibration add if required only for sensor
         let newGlucose = overcalibrate(entries: glucose)
         let newGlucose = overcalibrate(entries: glucose)
 
 
@@ -209,31 +250,27 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
             backGroundFetchBGTaskID = .invalid
             backGroundFetchBGTaskID = .invalid
         }
         }
 
 
-        guard newGlucose.isNotEmpty else {
+        defer {
             if let backgroundTask = backGroundFetchBGTaskID {
             if let backgroundTask = backGroundFetchBGTaskID {
-                await UIApplication.shared.endBackgroundTask(backgroundTask)
+                Task {
+                    await UIApplication.shared.endBackgroundTask(backgroundTask)
+                }
                 backGroundFetchBGTaskID = .invalid
                 backGroundFetchBGTaskID = .invalid
             }
             }
-            return
         }
         }
 
 
+        guard newGlucose.isNotEmpty else { return }
+
         filteredByDate = newGlucose.filter { $0.dateString > syncDate }
         filteredByDate = newGlucose.filter { $0.dateString > syncDate }
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
 
 
-        guard filtered.isNotEmpty else {
-            // end of the Background tasks
-            if let backgroundTask = backGroundFetchBGTaskID {
-                await UIApplication.shared.endBackgroundTask(backgroundTask)
-                backGroundFetchBGTaskID = .invalid
-            }
-            return
-        }
+        guard filtered.isNotEmpty else { return }
         debug(.deviceManager, "New glucose found")
         debug(.deviceManager, "New glucose found")
 
 
         // filter the data if it is the case
         // filter the data if it is the case
         if settingsManager.settings.smoothGlucose {
         if settingsManager.settings.smoothGlucose {
             // limited to 30 min of old glucose data
             // limited to 30 min of old glucose data
-            let oldGlucoseValues = await processGlucose()
+            let oldGlucoseValues = try await processGlucose()
 
 
             var smoothedValues = oldGlucoseValues + filtered
             var smoothedValues = oldGlucoseValues + filtered
             // smooth with 3 repeats
             // smooth with 3 repeats
@@ -244,49 +281,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
             filtered = smoothedValues.filter { $0.dateString > syncDate }
             filtered = smoothedValues.filter { $0.dateString > syncDate }
         }
         }
 
 
-        await glucoseStorage.storeGlucose(filtered)
-
+        try await glucoseStorage.storeGlucose(filtered)
         deviceDataManager.heartbeat(date: Date())
         deviceDataManager.heartbeat(date: Date())
-
-        // End of the Background tasks
-        if let backgroundTask = backGroundFetchBGTaskID {
-            await UIApplication.shared.endBackgroundTask(backgroundTask)
-            backGroundFetchBGTaskID = .invalid
-        }
-    }
-
-    /// The function used to start the timer sync - Function of the variable defined in config
-    private func subscribe() {
-        timer.publisher
-            .receive(on: processQueue)
-            .flatMap { [self] _ -> AnyPublisher<[BloodGlucose], Never> in
-                debug(.nightscout, "FetchGlucoseManager timer heartbeat")
-                if let glucoseSource = self.glucoseSource {
-                    return glucoseSource.fetch(self.timer).eraseToAnyPublisher()
-                } else {
-                    return Empty(completeImmediately: false).eraseToAnyPublisher()
-                }
-            }
-            .sink { glucose in
-                debug(.nightscout, "FetchGlucoseManager callback sensor")
-                Publishers.CombineLatest(
-                    Just(glucose),
-                    Just(self.glucoseStorage.syncDate())
-                )
-                .eraseToAnyPublisher()
-                .sink { newGlucose, syncDate in
-                    Task {
-                        await self.glucoseStoreAndHeartDecision(
-                            syncDate: syncDate,
-                            glucose: newGlucose
-                        )
-                    }
-                }
-                .store(in: &self.lifetime)
-            }
-            .store(in: &lifetime)
-        timer.fire()
-        timer.resume()
     }
     }
 
 
     func sourceInfo() -> [String: Any]? {
     func sourceInfo() -> [String: Any]? {
@@ -312,6 +308,19 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
             return entries
             return entries
         }
         }
     }
     }
+
+    /// function called when a callback is fired by CGM BLE
+    public func updateGlucoseStore(newBloodGlucose: [BloodGlucose]) {
+        Task {
+            let syncDate = glucoseStorage.syncDate()
+            debug(.deviceManager, "CGM BLE FETCHGLUCOSE  : SyncDate is \(syncDate)")
+            do {
+                try await glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: newBloodGlucose)
+            } catch {
+                debug(.deviceManager, "Failed to store glucose: \(error.localizedDescription)")
+            }
+        }
+    }
 }
 }
 
 
 extension CGMManager {
 extension CGMManager {

+ 1 - 1
Trio/Sources/APS/FetchTreatmentsManager.swift

@@ -37,7 +37,7 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
                     // Filter and store if not from "Trio"
                     // Filter and store if not from "Trio"
                     let filteredCarbs = await carbs.filter { $0.enteredBy != CarbsEntry.local }
                     let filteredCarbs = await carbs.filter { $0.enteredBy != CarbsEntry.local }
                     if filteredCarbs.isNotEmpty {
                     if filteredCarbs.isNotEmpty {
-                        await self.carbsStorage.storeCarbs(filteredCarbs, areFetchedFromRemote: true)
+                        try await self.carbsStorage.storeCarbs(filteredCarbs, areFetchedFromRemote: true)
                     }
                     }
 
 
                     // Filter and store if not from Trio
                     // Filter and store if not from Trio

+ 26 - 26
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -100,8 +100,8 @@ final class OpenAPS {
     }
     }
 
 
     // fetch glucose to pass it to the meal function and to determine basal
     // fetch glucose to pass it to the meal function and to determine basal
-    private func fetchAndProcessGlucose() async -> String {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchAndProcessGlucose() async throws -> String {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgoInMinutes,
             predicate: NSPredicate.predicateForOneDayAgoInMinutes,
@@ -121,8 +121,8 @@ final class OpenAPS {
         }
         }
     }
     }
 
 
-    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil) async -> String {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil) async throws -> String {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgo,
             predicate: NSPredicate.predicateForOneDayAgo,
@@ -170,8 +170,8 @@ final class OpenAPS {
         return json
         return json
     }
     }
 
 
-    private func fetchPumpHistoryObjectIDs() async -> [NSManagedObjectID]? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchPumpHistoryObjectIDs() async throws -> [NSManagedObjectID]? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpHistoryLast1440Minutes,
             predicate: NSPredicate.pumpHistoryLast1440Minutes,
@@ -287,10 +287,10 @@ final class OpenAPS {
             reservoir,
             reservoir,
             preferences
             preferences
         ) = await (
         ) = await (
-            parsePumpHistory(await pumpHistoryObjectIDs, iob: iob),
-            carbs,
-            glucose,
-            oref2,
+            try parsePumpHistory(await pumpHistoryObjectIDs, iob: iob),
+            try carbs,
+            try glucose,
+            try oref2,
             profileAsync,
             profileAsync,
             basalAsync,
             basalAsync,
             autosenseAsync,
             autosenseAsync,
@@ -355,8 +355,8 @@ final class OpenAPS {
         }
         }
     }
     }
 
 
-    func oref2() async -> RawJSON {
-        await context.perform {
+    func oref2() async throws -> RawJSON {
+        try await context.perform {
             // Retrieve user preferences
             // Retrieve user preferences
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
             let weightPercentage = userPreferences?.weightPercentage ?? 1.0
             let weightPercentage = userPreferences?.weightPercentage ?? 1.0
@@ -366,10 +366,10 @@ final class OpenAPS {
             // Fetch historical events for Total Daily Dose (TDD) calculation
             // Fetch historical events for Total Daily Dose (TDD) calculation
             let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
             let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
-            let historicalTDDData = self.fetchHistoricalTDDData(from: tenDaysAgo)
+            let historicalTDDData = try self.fetchHistoricalTDDData(from: tenDaysAgo)
 
 
             // Fetch the last active Override
             // Fetch the last active Override
-            let activeOverrides = self.fetchActiveOverrides()
+            let activeOverrides = try self.fetchActiveOverrides()
             let isOverrideActive = activeOverrides.first?.enabled ?? false
             let isOverrideActive = activeOverrides.first?.enabled ?? false
             let overridePercentage = Decimal(activeOverrides.first?.percentage ?? 100)
             let overridePercentage = Decimal(activeOverrides.first?.percentage ?? 100)
             let isOverrideIndefinite = activeOverrides.first?.indefinite ?? true
             let isOverrideIndefinite = activeOverrides.first?.indefinite ?? true
@@ -433,9 +433,9 @@ final class OpenAPS {
 
 
         // Await the results of asynchronous tasks
         // Await the results of asynchronous tasks
         let (pumpHistoryJSON, carbsAsJSON, glucoseAsJSON, profile, basalProfile, tempTargets) = await (
         let (pumpHistoryJSON, carbsAsJSON, glucoseAsJSON, profile, basalProfile, tempTargets) = await (
-            parsePumpHistory(await pumpHistoryObjectIDs),
-            carbs,
-            glucose,
+            try parsePumpHistory(await pumpHistoryObjectIDs),
+            try carbs,
+            try glucose,
             getProfile,
             getProfile,
             getBasalProfile,
             getBasalProfile,
             getTempTargets
             getTempTargets
@@ -462,7 +462,7 @@ final class OpenAPS {
         }
         }
     }
     }
 
 
-    func createProfiles() async {
+    func createProfiles() async throws {
         debug(.openAPS, "Start creating pump profile and user profile")
         debug(.openAPS, "Start creating pump profile and user profile")
 
 
         // Load required settings and profiles asynchronously
         // Load required settings and profiles asynchronously
@@ -492,9 +492,9 @@ final class OpenAPS {
         var adjustedPreferences = preferences
         var adjustedPreferences = preferences
 
 
         // Check for active Temp Targets and adjust HBT if necessary
         // Check for active Temp Targets and adjust HBT if necessary
-        await context.perform {
+        try await context.perform {
             // Check if a Temp Target is active and if its HBT differs from user preferences
             // Check if a Temp Target is active and if its HBT differs from user preferences
-            if let activeTempTarget = self.fetchActiveTempTargets().first,
+            if let activeTempTarget = try self.fetchActiveTempTargets().first,
                activeTempTarget.enabled,
                activeTempTarget.enabled,
                let activeHBT = activeTempTarget.halfBasalTarget?.decimalValue,
                let activeHBT = activeTempTarget.halfBasalTarget?.decimalValue,
                activeHBT != defaultHalfBasalTarget
                activeHBT != defaultHalfBasalTarget
@@ -786,8 +786,8 @@ final class OpenAPS {
 
 
 // Non-Async fetch methods for oref2
 // Non-Async fetch methods for oref2
 extension OpenAPS {
 extension OpenAPS {
-    func fetchActiveTempTargets() -> [TempTargetStored] {
-        CoreDataStack.shared.fetchEntities(
+    func fetchActiveTempTargets() throws -> [TempTargetStored] {
+        try CoreDataStack.shared.fetchEntities(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.lastActiveTempTarget,
             predicate: NSPredicate.lastActiveTempTarget,
@@ -797,8 +797,8 @@ extension OpenAPS {
         ) as? [TempTargetStored] ?? []
         ) as? [TempTargetStored] ?? []
     }
     }
 
 
-    func fetchActiveOverrides() -> [OverrideStored] {
-        CoreDataStack.shared.fetchEntities(
+    func fetchActiveOverrides() throws -> [OverrideStored] {
+        try CoreDataStack.shared.fetchEntities(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.lastActiveOverride,
             predicate: NSPredicate.lastActiveOverride,
@@ -808,8 +808,8 @@ extension OpenAPS {
         ) as? [OverrideStored] ?? []
         ) as? [OverrideStored] ?? []
     }
     }
 
 
-    func fetchHistoricalTDDData(from date: Date) -> [[String: Any]] {
-        CoreDataStack.shared.fetchEntities(
+    func fetchHistoricalTDDData(from date: Date) throws -> [[String: Any]] {
+        try CoreDataStack.shared.fetchEntities(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", date as NSDate),
             predicate: NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", date as NSDate),

+ 17 - 17
Trio/Sources/APS/Storage/CarbsStorage.swift

@@ -10,14 +10,14 @@ protocol CarbsObserver {
 
 
 protocol CarbsStorage {
 protocol CarbsStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
     var updatePublisher: AnyPublisher<Void, Never> { get }
-    func storeCarbs(_ carbs: [CarbsEntry], areFetchedFromRemote: Bool) async
+    func storeCarbs(_ carbs: [CarbsEntry], areFetchedFromRemote: Bool) async throws
     func deleteCarbsEntryStored(_ treatmentObjectID: NSManagedObjectID) async
     func deleteCarbsEntryStored(_ treatmentObjectID: NSManagedObjectID) async
     func syncDate() -> Date
     func syncDate() -> Date
     func recent() -> [CarbsEntry]
     func recent() -> [CarbsEntry]
-    func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getFPUsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getCarbsNotYetUploadedToHealth() async -> [CarbsEntry]
-    func getCarbsNotYetUploadedToTidepool() async -> [CarbsEntry]
+    func getCarbsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getFPUsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getCarbsNotYetUploadedToHealth() async throws -> [CarbsEntry]
+    func getCarbsNotYetUploadedToTidepool() async throws -> [CarbsEntry]
 }
 }
 
 
 final class BaseCarbsStorage: CarbsStorage, Injectable {
 final class BaseCarbsStorage: CarbsStorage, Injectable {
@@ -38,11 +38,11 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         injectServices(resolver)
         injectServices(resolver)
     }
     }
 
 
-    func storeCarbs(_ entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
+    func storeCarbs(_ entries: [CarbsEntry], areFetchedFromRemote: Bool) async throws {
         var entriesToStore = entries
         var entriesToStore = entries
 
 
         if areFetchedFromRemote {
         if areFetchedFromRemote {
-            entriesToStore = await filterRemoteEntries(entries: entriesToStore)
+            entriesToStore = try await filterRemoteEntries(entries: entriesToStore)
         }
         }
 
 
         // Check for FPU-only entries (fat/protein without carbs)
         // Check for FPU-only entries (fat/protein without carbs)
@@ -71,9 +71,9 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
     }
     }
 
 
-    private func filterRemoteEntries(entries: [CarbsEntry]) async -> [CarbsEntry] {
+    private func filterRemoteEntries(entries: [CarbsEntry]) async throws -> [CarbsEntry] {
         // Fetch only the date property from Core Data
         // Fetch only the date property from Core Data
-        guard let existing24hCarbEntries = await CoreDataStack.shared.fetchEntitiesAsync(
+        guard let existing24hCarbEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.predicateForOneDayAgo,
             predicate: NSPredicate.predicateForOneDayAgo,
@@ -339,8 +339,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getCarbsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.carbsNotYetUploadedToNightscout,
             predicate: NSPredicate.carbsNotYetUploadedToNightscout,
@@ -378,8 +378,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getFPUsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getFPUsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.fpusNotYetUploadedToNightscout,
             predicate: NSPredicate.fpusNotYetUploadedToNightscout,
@@ -415,8 +415,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getCarbsNotYetUploadedToHealth() async -> [CarbsEntry] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getCarbsNotYetUploadedToHealth() async throws -> [CarbsEntry] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.carbsNotYetUploadedToHealth,
             predicate: NSPredicate.carbsNotYetUploadedToHealth,
@@ -446,8 +446,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getCarbsNotYetUploadedToTidepool() async -> [CarbsEntry] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getCarbsNotYetUploadedToTidepool() async throws -> [CarbsEntry] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.carbsNotYetUploadedToTidepool,
             predicate: NSPredicate.carbsNotYetUploadedToTidepool,

+ 32 - 27
Trio/Sources/APS/Storage/ContactImageStorage.swift

@@ -26,36 +26,41 @@ final class BaseContactImageStorage: ContactImageStorage, Injectable {
     ///
     ///
     /// - Returns: An array of `ContactImageEntry` objects.
     /// - Returns: An array of `ContactImageEntry` objects.
     func fetchContactImageEntries() async -> [ContactImageEntry] {
     func fetchContactImageEntries() async -> [ContactImageEntry] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: ContactImageEntryStored.self,
-            onContext: backgroundContext,
-            predicate: NSPredicate.all,
-            key: "hasHighContrast",
-            ascending: false
-        )
+        do {
+            let results = try await CoreDataStack.shared.fetchEntitiesAsync(
+                ofType: ContactImageEntryStored.self,
+                onContext: backgroundContext,
+                predicate: NSPredicate.all,
+                key: "hasHighContrast",
+                ascending: false
+            )
 
 
-        return await backgroundContext.perform {
-            guard let fetchedContactImageEntries = results as? [ContactImageEntryStored] else { return [] }
+            return await backgroundContext.perform {
+                guard let fetchedContactImageEntries = results as? [ContactImageEntryStored] else { return [] }
 
 
-            return fetchedContactImageEntries.compactMap { entry in
-                ContactImageEntry(
-                    name: entry.name ?? "No name provided",
-                    layout: ContactImageLayout(rawValue: entry.layout ?? "Default") ?? .default,
-                    ring: ContactImageLargeRing(rawValue: entry.ring ?? "Hidden") ?? .none,
-                    primary: ContactImageValue(rawValue: entry.primary ?? "Glucose Reading") ?? .glucose,
-                    top: ContactImageValue(rawValue: entry.top ?? "None") ?? .none,
-                    bottom: ContactImageValue(rawValue: entry.bottom ?? "None") ?? .none,
-                    contactId: entry.contactId?.string,
-                    hasHighContrast: entry.hasHighContrast,
-                    ringWidth: ContactImageEntry.RingWidth(rawValue: Int(entry.ringWidth)) ?? .regular,
-                    ringGap: ContactImageEntry.RingGap(rawValue: Int(entry.ringGap)) ?? .small,
-                    fontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSize)) ?? .regular,
-                    secondaryFontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSizeSecondary)) ?? .small,
-                    fontWeight: Font.Weight.fromString(entry.fontWeight ?? "regular"),
-                    fontWidth: Font.Width.fromString(entry.fontWidth ?? "standard"),
-                    managedObjectID: entry.objectID
-                )
+                return fetchedContactImageEntries.compactMap { entry in
+                    ContactImageEntry(
+                        name: entry.name ?? "No name provided",
+                        layout: ContactImageLayout(rawValue: entry.layout ?? "Default") ?? .default,
+                        ring: ContactImageLargeRing(rawValue: entry.ring ?? "Hidden") ?? .none,
+                        primary: ContactImageValue(rawValue: entry.primary ?? "Glucose Reading") ?? .glucose,
+                        top: ContactImageValue(rawValue: entry.top ?? "None") ?? .none,
+                        bottom: ContactImageValue(rawValue: entry.bottom ?? "None") ?? .none,
+                        contactId: entry.contactId?.string,
+                        hasHighContrast: entry.hasHighContrast,
+                        ringWidth: ContactImageEntry.RingWidth(rawValue: Int(entry.ringWidth)) ?? .regular,
+                        ringGap: ContactImageEntry.RingGap(rawValue: Int(entry.ringGap)) ?? .small,
+                        fontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSize)) ?? .regular,
+                        secondaryFontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSizeSecondary)) ?? .small,
+                        fontWeight: Font.Weight.fromString(entry.fontWeight ?? "regular"),
+                        fontWidth: Font.Width.fromString(entry.fontWidth ?? "standard"),
+                        managedObjectID: entry.objectID
+                    )
+                }
             }
             }
+        } catch {
+            debug(.default, "\(DebuggingIdentifiers.failed) Error fetching contact image entries: \(error.localizedDescription)")
+            return []
         }
         }
     }
     }
 
 

+ 6 - 8
Trio/Sources/APS/Storage/DeterminationStorage.swift

@@ -4,7 +4,7 @@ import Foundation
 import Swinject
 import Swinject
 
 
 protocol DeterminationStorage {
 protocol DeterminationStorage {
-    func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID]
+    func fetchLastDeterminationObjectID(predicate: NSPredicate) async throws -> [NSManagedObjectID]
     func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
     func getForecastIDs(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
     func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
     func getForecastValueIDs(for forecastID: NSManagedObjectID, in context: NSManagedObjectContext) async -> [NSManagedObjectID]
     func fetchForecastObjects(
     func fetchForecastObjects(
@@ -13,7 +13,7 @@ protocol DeterminationStorage {
     ) async -> (UUID, Forecast?, [ForecastValue])
     ) async -> (UUID, Forecast?, [ForecastValue])
     func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination?
     func getOrefDeterminationNotYetUploadedToNightscout(_ determinationIds: [NSManagedObjectID]) async -> Determination?
     func fetchForecastHierarchy(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext)
     func fetchForecastHierarchy(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext)
-    async -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])]
+    async throws -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])]
 }
 }
 
 
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
@@ -25,8 +25,8 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
         injectServices(resolver)
         injectServices(resolver)
     }
     }
 
 
-    func fetchLastDeterminationObjectID(predicate: NSPredicate) async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchLastDeterminationObjectID(predicate: NSPredicate) async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: context,
             onContext: context,
             predicate: predicate,
             predicate: predicate,
@@ -37,7 +37,6 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
 
 
         return await context.perform {
         return await context.perform {
             guard let fetchedResults = results as? [OrefDetermination] else { return [] }
             guard let fetchedResults = results as? [OrefDetermination] else { return [] }
-
             return fetchedResults.map(\.objectID)
             return fetchedResults.map(\.objectID)
         }
         }
     }
     }
@@ -206,10 +205,9 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
     }
     }
 
 
     func fetchForecastHierarchy(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext)
     func fetchForecastHierarchy(for determinationID: NSManagedObjectID, in context: NSManagedObjectContext)
-    async -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])]
+    async throws -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])]
     {
     {
-        // Fetch forecasts with prefetched values for the given determination
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: Forecast.self,
             ofType: Forecast.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate(format: "orefDetermination = %@", determinationID),
             predicate: NSPredicate(format: "orefDetermination = %@", determinationID),

+ 41 - 35
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -9,20 +9,20 @@ import Swinject
 
 
 protocol GlucoseStorage {
 protocol GlucoseStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
     var updatePublisher: AnyPublisher<Void, Never> { get }
-    func storeGlucose(_ glucose: [BloodGlucose]) async
+    func storeGlucose(_ glucose: [BloodGlucose]) async throws
     func addManualGlucose(glucose: Int)
     func addManualGlucose(glucose: Int)
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool
     func syncDate() -> Date
     func syncDate() -> Date
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func lastGlucoseDate() -> Date
     func lastGlucoseDate() -> Date
     func isGlucoseFresh() -> Bool
     func isGlucoseFresh() -> Bool
-    func getGlucoseNotYetUploadedToNightscout() async -> [BloodGlucose]
-    func getCGMStateNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getManualGlucoseNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getGlucoseNotYetUploadedToHealth() async -> [BloodGlucose]
-    func getManualGlucoseNotYetUploadedToHealth() async -> [BloodGlucose]
-    func getGlucoseNotYetUploadedToTidepool() async -> [StoredGlucoseSample]
-    func getManualGlucoseNotYetUploadedToTidepool() async -> [StoredGlucoseSample]
+    func getGlucoseNotYetUploadedToNightscout() async throws -> [BloodGlucose]
+    func getCGMStateNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getManualGlucoseNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
+    func getManualGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
+    func getGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample]
+    func getManualGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample]
     var alarm: GlucoseAlarm? { get }
     var alarm: GlucoseAlarm? { get }
     func deleteGlucose(_ treatmentObjectID: NSManagedObjectID) async
     func deleteGlucose(_ treatmentObjectID: NSManagedObjectID) async
 }
 }
@@ -60,8 +60,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return formatter
         return formatter
     }
     }
 
 
-    func storeGlucose(_ glucose: [BloodGlucose]) async {
-        await coredataContext.perform {
+    func storeGlucose(_ glucose: [BloodGlucose]) async throws {
+        try await coredataContext.perform {
             // Get new glucose values that don't exist yet
             // Get new glucose values that don't exist yet
             let newGlucose = self.filterNewGlucoseValues(glucose)
             let newGlucose = self.filterNewGlucoseValues(glucose)
             guard !newGlucose.isEmpty else { return }
             guard !newGlucose.isEmpty else { return }
@@ -70,8 +70,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                 // Store glucose values in Core Data
                 // Store glucose values in Core Data
                 try self.storeGlucoseInCoreData(newGlucose)
                 try self.storeGlucoseInCoreData(newGlucose)
             } catch {
             } catch {
-                debugPrint(
-                    "Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to store glucose: \(error)"
+                throw CoreDataError.creationError(
+                    function: #function,
+                    file: #fileID
                 )
                 )
             }
             }
 
 
@@ -312,9 +313,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return filtered
         return filtered
     }
     }
 
 
-    func fetchLatestGlucose() -> GlucoseStored? {
+    func fetchLatestGlucose() throws -> GlucoseStored? {
         let predicate = NSPredicate.predicateFor20MinAgo
         let predicate = NSPredicate.predicateFor20MinAgo
-        return (CoreDataStack.shared.fetchEntities(
+        return (try CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: predicate,
             predicate: predicate,
@@ -326,8 +327,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch glucose that is not uploaded to Nightscout yet
     // Fetch glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
-    func getGlucoseNotYetUploadedToNightscout() async -> [BloodGlucose] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getGlucoseNotYetUploadedToNightscout() async throws -> [BloodGlucose] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.glucoseNotYetUploadedToNightscout,
             predicate: NSPredicate.glucoseNotYetUploadedToNightscout,
@@ -357,8 +358,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch manual glucose that is not uploaded to Nightscout yet
     // Fetch manual glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
-    func getManualGlucoseNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getManualGlucoseNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToNightscout,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToNightscout,
@@ -411,8 +412,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch glucose that is not uploaded to Nightscout yet
     // Fetch glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
-    func getGlucoseNotYetUploadedToHealth() async -> [BloodGlucose] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.glucoseNotYetUploadedToHealth,
             predicate: NSPredicate.glucoseNotYetUploadedToHealth,
@@ -441,8 +442,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch manual glucose that is not uploaded to Nightscout yet
     // Fetch manual glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
-    func getManualGlucoseNotYetUploadedToHealth() async -> [BloodGlucose] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getManualGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToHealth,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToHealth,
@@ -471,8 +472,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch glucose that is not uploaded to Tidepool yet
     // Fetch glucose that is not uploaded to Tidepool yet
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for Tidepool upload
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for Tidepool upload
-    func getGlucoseNotYetUploadedToTidepool() async -> [StoredGlucoseSample] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.glucoseNotYetUploadedToTidepool,
             predicate: NSPredicate.glucoseNotYetUploadedToTidepool,
@@ -502,8 +503,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     // Fetch manual glucose that is not uploaded to Tidepool yet
     // Fetch manual glucose that is not uploaded to Tidepool yet
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for the Tidepool upload
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for the Tidepool upload
-    func getManualGlucoseNotYetUploadedToTidepool() async -> [StoredGlucoseSample] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getManualGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToTidepool,
             predicate: NSPredicate.manualGlucoseNotYetUploadedToTidepool,
@@ -560,19 +561,24 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     var alarm: GlucoseAlarm? {
     var alarm: GlucoseAlarm? {
         /// glucose can not be older than 20 minutes due to the predicate in the fetch request
         /// glucose can not be older than 20 minutes due to the predicate in the fetch request
         coredataContext.performAndWait {
         coredataContext.performAndWait {
-            guard let glucose = fetchLatestGlucose() else { return nil }
+            do {
+                guard let glucose = try fetchLatestGlucose() else { return nil }
 
 
-            let glucoseValue = glucose.glucose
+                let glucoseValue = glucose.glucose
 
 
-            if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
-                return .low
-            }
+                if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
+                    return .low
+                }
 
 
-            if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
-                return .high
-            }
+                if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
+                    return .high
+                }
 
 
-            return nil
+                return nil
+            } catch {
+                debugPrint("Error fetching latest glucose: \(error)")
+                return nil
+            }
         }
         }
     }
     }
 }
 }

+ 27 - 33
Trio/Sources/APS/Storage/OverrideStorage.swift

@@ -3,17 +3,17 @@ import Foundation
 import Swinject
 import Swinject
 
 
 protocol OverrideStorage {
 protocol OverrideStorage {
-    func fetchLastCreatedOverride() async -> [NSManagedObjectID]
-    func loadLatestOverrideConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
-    func fetchForOverridePresets() async -> [NSManagedObjectID]
+    func fetchLastCreatedOverride() async throws -> [NSManagedObjectID]
+    func loadLatestOverrideConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID]
+    func fetchForOverridePresets() async throws -> [NSManagedObjectID]
     func calculateTarget(override: OverrideStored) -> Decimal
     func calculateTarget(override: OverrideStored) -> Decimal
-    func storeOverride(override: Override) async
+    func storeOverride(override: Override) async throws
     func copyRunningOverride(_ override: OverrideStored) async -> NSManagedObjectID
     func copyRunningOverride(_ override: OverrideStored) async -> NSManagedObjectID
     func deleteOverridePreset(_ objectID: NSManagedObjectID) async
     func deleteOverridePreset(_ objectID: NSManagedObjectID) async
-    func getOverridesNotYetUploadedToNightscout() async -> [NightscoutExercise]
-    func getOverrideRunsNotYetUploadedToNightscout() async -> [NightscoutExercise]
-    func getPresetOverridesForNightscout() async -> [NightscoutPresetOverride]
-    func fetchLatestActiveOverride() async -> NSManagedObjectID?
+    func getOverridesNotYetUploadedToNightscout() async throws -> [NightscoutExercise]
+    func getOverrideRunsNotYetUploadedToNightscout() async throws -> [NightscoutExercise]
+    func getPresetOverridesForNightscout() async throws -> [NightscoutPresetOverride]
+    func fetchLatestActiveOverride() async throws -> NSManagedObjectID?
 }
 }
 
 
 final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
 final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
@@ -34,8 +34,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         return dateFormatter
         return dateFormatter
     }
     }
 
 
-    func fetchLastCreatedOverride() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchLastCreatedOverride() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate(
             predicate: NSPredicate(
@@ -54,8 +54,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         }
         }
     }
     }
 
 
-    func loadLatestOverrideConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func loadLatestOverrideConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastActiveOverride,
             predicate: NSPredicate.lastActiveOverride,
@@ -72,8 +72,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
     }
 
 
     /// Returns the NSManagedObjectID of the Override Presets
     /// Returns the NSManagedObjectID of the Override Presets
-    func fetchForOverridePresets() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchForOverridePresets() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.allOverridePresets,
             predicate: NSPredicate.allOverridePresets,
@@ -95,14 +95,14 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         return overrideTarget.decimalValue
         return overrideTarget.decimalValue
     }
     }
 
 
-    func storeOverride(override: Override) async {
+    func storeOverride(override: Override) async throws {
         var presetCount = -1
         var presetCount = -1
         if override.isPreset {
         if override.isPreset {
-            let presets = await fetchForOverridePresets()
+            let presets = try await fetchForOverridePresets()
             presetCount = presets.count
             presetCount = presets.count
         }
         }
 
 
-        await backgroundContext.perform {
+        try await backgroundContext.perform {
             let newOverride = OverrideStored(context: self.backgroundContext)
             let newOverride = OverrideStored(context: self.backgroundContext)
 
 
             // override key meta data
             // override key meta data
@@ -151,14 +151,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
                 newOverride.smbIsScheduledOff = false
                 newOverride.smbIsScheduledOff = 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 Override Preset to Core Data with error: \(error.userInfo)"
-                )
-            }
+            guard self.backgroundContext.hasChanges else { return }
+            try self.backgroundContext.save()
         }
         }
     }
     }
 
 
@@ -206,8 +200,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
         await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
     }
     }
 
 
-    func getOverridesNotYetUploadedToNightscout() async -> [NightscoutExercise] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getOverridesNotYetUploadedToNightscout() async throws -> [NightscoutExercise] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout,
             predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout,
@@ -232,8 +226,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getOverrideRunsNotYetUploadedToNightscout() async -> [NightscoutExercise] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getOverrideRunsNotYetUploadedToNightscout() async throws -> [NightscoutExercise] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideRunStored.self,
             ofType: OverrideRunStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate(
             predicate: NSPredicate(
@@ -263,8 +257,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getPresetOverridesForNightscout() async -> [NightscoutPresetOverride] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getPresetOverridesForNightscout() async throws -> [NightscoutPresetOverride] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.allOverridePresets,
             predicate: NSPredicate.allOverridePresets,
@@ -290,8 +284,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
         }
         }
     }
     }
 
 
-    func fetchLatestActiveOverride() async -> NSManagedObjectID? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchLatestActiveOverride() async throws -> NSManagedObjectID? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastActiveOverride,
             predicate: NSPredicate.lastActiveOverride,

+ 13 - 13
Trio/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -11,11 +11,11 @@ protocol PumpHistoryObserver {
 
 
 protocol PumpHistoryStorage {
 protocol PumpHistoryStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
     var updatePublisher: AnyPublisher<Void, Never> { get }
-    func storePumpEvents(_ events: [NewPumpEvent]) async
+    func storePumpEvents(_ events: [NewPumpEvent]) async throws
     func storeExternalInsulinEvent(amount: Decimal, timestamp: Date) async
     func storeExternalInsulinEvent(amount: Decimal, timestamp: Date) async
-    func getPumpHistoryNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getPumpHistoryNotYetUploadedToHealth() async -> [PumpHistoryEvent]
-    func getPumpHistoryNotYetUploadedToTidepool() async -> [PumpHistoryEvent]
+    func getPumpHistoryNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getPumpHistoryNotYetUploadedToHealth() async throws -> [PumpHistoryEvent]
+    func getPumpHistoryNotYetUploadedToTidepool() async throws -> [PumpHistoryEvent]
 }
 }
 
 
 final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
 final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
@@ -44,10 +44,10 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         return Decimal(roundedValue)
         return Decimal(roundedValue)
     }
     }
 
 
-    func storePumpEvents(_ events: [NewPumpEvent]) async {
-        await context.perform {
+    func storePumpEvents(_ events: [NewPumpEvent]) async throws {
+        try await context.perform {
             for event in events {
             for event in events {
-                let existingEvents: [PumpEventStored] = CoreDataStack.shared.fetchEntities(
+                let existingEvents: [PumpEventStored] = try CoreDataStack.shared.fetchEntities(
                     ofType: PumpEventStored.self,
                     ofType: PumpEventStored.self,
                     onContext: self.context,
                     onContext: self.context,
                     predicate: NSPredicate.duplicateInLastHour(event.date),
                     predicate: NSPredicate.duplicateInLastHour(event.date),
@@ -259,8 +259,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         return PumpEventStored.EventType(rawValue: event.type!) ?? PumpEventStored.EventType.bolus
         return PumpEventStored.EventType(rawValue: event.type!) ?? PumpEventStored.EventType.bolus
     }
     }
 
 
-    func getPumpHistoryNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getPumpHistoryNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToNightscout,
             predicate: NSPredicate.pumpEventsNotYetUploadedToNightscout,
@@ -418,8 +418,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getPumpHistoryNotYetUploadedToHealth() async -> [PumpHistoryEvent] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getPumpHistoryNotYetUploadedToHealth() async throws -> [PumpHistoryEvent] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToHealth,
             predicate: NSPredicate.pumpEventsNotYetUploadedToHealth,
@@ -460,8 +460,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getPumpHistoryNotYetUploadedToTidepool() async -> [PumpHistoryEvent] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getPumpHistoryNotYetUploadedToTidepool() async throws -> [PumpHistoryEvent] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToTidepool,
             predicate: NSPredicate.pumpEventsNotYetUploadedToTidepool,

+ 51 - 50
Trio/Sources/APS/Storage/TempTargetsStorage.swift

@@ -10,16 +10,16 @@ protocol TempTargetsObserver {
 protocol TempTargetsStorage {
 protocol TempTargetsStorage {
     func storeTempTarget(tempTarget: TempTarget) async
     func storeTempTarget(tempTarget: TempTarget) async
     func saveTempTargetsToStorage(_ targets: [TempTarget])
     func saveTempTargetsToStorage(_ targets: [TempTarget])
-    func fetchForTempTargetPresets() async -> [NSManagedObjectID]
-    func fetchScheduledTempTargets() async -> [NSManagedObjectID]
-    func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID]
+    func fetchForTempTargetPresets() async throws -> [NSManagedObjectID]
+    func fetchScheduledTempTargets() async throws -> [NSManagedObjectID]
+    func fetchScheduledTempTarget(for targetDate: Date) async throws -> [NSManagedObjectID]
     func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
     func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
     func deleteOverridePreset(_ objectID: NSManagedObjectID) async
     func deleteOverridePreset(_ objectID: NSManagedObjectID) async
-    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID]
     func syncDate() -> Date
     func syncDate() -> Date
     func recent() -> [TempTarget]
     func recent() -> [TempTarget]
-    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
+    func getTempTargetsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
+    func getTempTargetRunsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
     func presets() -> [TempTarget]
     func presets() -> [TempTarget]
     func current() -> TempTarget?
     func current() -> TempTarget?
     func existsTempTarget(with date: Date) async -> Bool
     func existsTempTarget(with date: Date) async -> Bool
@@ -38,8 +38,8 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         injectServices(resolver)
         injectServices(resolver)
     }
     }
 
 
-    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastActiveTempTarget,
             predicate: NSPredicate.lastActiveTempTarget,
@@ -56,8 +56,8 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
     }
 
 
     /// Returns the NSManagedObjectID of the Temp Target Presets
     /// Returns the NSManagedObjectID of the Temp Target Presets
-    func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchForTempTargetPresets() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.allTempTargetPresets,
             predicate: NSPredicate.allTempTargetPresets,
@@ -72,10 +72,10 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func fetchScheduledTempTargets() async -> [NSManagedObjectID] {
+    func fetchScheduledTempTargets() async throws -> [NSManagedObjectID] {
         let scheduledTempTargets = NSPredicate(format: "date > %@", Date() as NSDate)
         let scheduledTempTargets = NSPredicate(format: "date > %@", Date() as NSDate)
 
 
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: scheduledTempTargets,
             predicate: scheduledTempTargets,
@@ -90,10 +90,10 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID] {
+    func fetchScheduledTempTarget(for targetDate: Date) async throws -> [NSManagedObjectID] {
         let predicate = NSPredicate(format: "date == %@", targetDate as NSDate)
         let predicate = NSPredicate(format: "date == %@", targetDate as NSDate)
 
 
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: predicate,
             predicate: predicate,
@@ -110,44 +110,45 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
     }
 
 
     func storeTempTarget(tempTarget: TempTarget) async {
     func storeTempTarget(tempTarget: TempTarget) async {
-        var presetCount = -1
-        if tempTarget.isPreset == true {
-            let presets = await fetchForTempTargetPresets()
-            presetCount = presets.count
-        }
-
-        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
-
-            // Nullify half basal target to ensure the latest HBT is used via OpenAPS Manager when sending TT data to oref
-            newTempTarget.halfBasalTarget = nil
-
-            if let halfBasalTarget = tempTarget.halfBasalTarget,
-               halfBasalTarget != self.settingsManager.preferences.halfBasalExerciseTarget
-            {
-                newTempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalTarget)
+        do {
+            var presetCount = -1
+            if tempTarget.isPreset == true {
+                let presets = try await fetchForTempTargetPresets()
+                presetCount = presets.count
             }
             }
 
 
-            if tempTarget.isPreset == true, presetCount > -1 {
-                newTempTarget.orderPosition = Int16(presetCount + 1)
-            }
+            try 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
+
+                // Nullify half basal target to ensure the latest HBT is used via OpenAPS Manager when sending TT data to oref
+                newTempTarget.halfBasalTarget = nil
+
+                if let halfBasalTarget = tempTarget.halfBasalTarget,
+                   halfBasalTarget != self.settingsManager.preferences.halfBasalExerciseTarget
+                {
+                    newTempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalTarget)
+                }
+
+                if tempTarget.isPreset == true, presetCount > -1 {
+                    newTempTarget.orderPosition = Int16(presetCount + 1)
+                }
 
 
-            do {
                 guard self.backgroundContext.hasChanges else { return }
                 guard self.backgroundContext.hasChanges else { return }
                 try self.backgroundContext.save()
                 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)"
-                )
             }
             }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to store temp target: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 
 
@@ -242,8 +243,8 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         return last
         return last
     }
     }
 
 
-    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getTempTargetsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout, // TODO: create adjustment predicate (OR+TT)
             predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout, // TODO: create adjustment predicate (OR+TT)
@@ -277,8 +278,8 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func getTempTargetRunsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetRunStored.self,
             ofType: TempTargetRunStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate(
             predicate: NSPredicate(

+ 9 - 2
Trio/Sources/Application/AppDelegate.swift

@@ -23,7 +23,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, UNUserNoti
             let pushMessage = try JSONDecoder().decode(PushMessage.self, from: jsonData)
             let pushMessage = try JSONDecoder().decode(PushMessage.self, from: jsonData)
 
 
             Task {
             Task {
-                await TrioRemoteControl.shared.handleRemoteNotification(pushMessage: pushMessage)
+                try await TrioRemoteControl.shared.handleRemoteNotification(pushMessage: pushMessage)
                 completionHandler(.newData)
                 completionHandler(.newData)
             }
             }
         } catch {
         } catch {
@@ -40,7 +40,14 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, UNUserNoti
         let token = tokenParts.joined()
         let token = tokenParts.joined()
 
 
         Task {
         Task {
-            await TrioRemoteControl.shared.handleAPNSChanges(deviceToken: token)
+            do {
+                try await TrioRemoteControl.shared.handleAPNSChanges(deviceToken: token)
+            } catch {
+                debug(
+                    .remoteControl,
+                    "\(DebuggingIdentifiers.failed) failed to register for remote notifications: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 

+ 125 - 84
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift

@@ -29,19 +29,22 @@ extension Adjustments.StateModel {
     // MARK: - Disable Overrides
     // MARK: - Disable Overrides
 
 
     /// Disables all active Overrides, optionally creating a run entry.
     /// Disables all active Overrides, optionally creating a run entry.
-    @MainActor func disableAllActiveOverrides(except overrideID: NSManagedObjectID? = nil, createOverrideRunEntry: Bool) async {
-        // Get ALL NSManagedObject IDs of ALL active Override to cancel every single Override
-        let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0)
+    @MainActor func disableAllActiveOverrides(
+        except overrideID: NSManagedObjectID? = nil,
+        createOverrideRunEntry: Bool
+    ) async {
+        do {
+            // Get ALL NSManagedObject IDs of ALL active Override to cancel every single Override
+            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0)
 
 
-        await viewContext.perform {
-            do {
+            try await viewContext.perform {
                 // Fetch the existing OverrideStored objects from the context
                 // Fetch the existing OverrideStored objects from the context
                 let results = try ids.compactMap { id in
                 let results = try ids.compactMap { id in
                     try self.viewContext.existingObject(with: id) as? OverrideStored
                     try self.viewContext.existingObject(with: id) as? OverrideStored
                 }
                 }
                 guard !results.isEmpty else { return }
                 guard !results.isEmpty else { return }
 
 
-                // Check if we also need to create a corresponding OverrideRunStored entry, i.e. when the User uses the Cancel Button in Override View
+                // Check if we also need to create a corresponding OverrideRunStored entry
                 if createOverrideRunEntry {
                 if createOverrideRunEntry {
                     // Use the first override to create a new OverrideRunStored entry
                     // Use the first override to create a new OverrideRunStored entry
                     if let canceledOverride = results.first {
                     if let canceledOverride = results.first {
@@ -50,8 +53,9 @@ extension Adjustments.StateModel {
                         newOverrideRunStored.name = canceledOverride.name
                         newOverrideRunStored.name = canceledOverride.name
                         newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
                         newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
                         newOverrideRunStored.endDate = Date()
                         newOverrideRunStored.endDate = Date()
-                        newOverrideRunStored
-                            .target = NSDecimalNumber(decimal: self.overrideStorage.calculateTarget(override: canceledOverride))
+                        newOverrideRunStored.target = NSDecimalNumber(
+                            decimal: self.overrideStorage.calculateTarget(override: canceledOverride)
+                        )
                         newOverrideRunStored.override = canceledOverride
                         newOverrideRunStored.override = canceledOverride
                         newOverrideRunStored.isUploadedToNS = false
                         newOverrideRunStored.isUploadedToNS = false
                     }
                     }
@@ -67,11 +71,12 @@ extension Adjustments.StateModel {
                     try self.viewContext.save()
                     try self.viewContext.save()
                     self.updateLatestOverrideConfiguration()
                     self.updateLatestOverrideConfiguration()
                 }
                 }
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides: \(error.localizedDescription)"
-                )
             }
             }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to disable active overrides: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 
 
@@ -79,74 +84,89 @@ extension Adjustments.StateModel {
 
 
     /// Saves a custom Override and activates it.
     /// Saves a custom Override and activates it.
     func saveCustomOverride() async {
     func saveCustomOverride() async {
-        let override = Override(
-            name: overrideName,
-            enabled: true,
-            date: Date(),
-            duration: overrideDuration,
-            indefinite: indefinite,
-            percentage: overridePercentage,
-            smbIsOff: smbIsOff,
-            isPreset: isPreset,
-            id: id,
-            overrideTarget: shouldOverrideTarget,
-            target: target,
-            advancedSettings: advancedSettings,
-            isfAndCr: isfAndCr,
-            isf: isf,
-            cr: cr,
-            smbIsScheduledOff: smbIsScheduledOff,
-            start: start,
-            end: end,
-            smbMinutes: smbMinutes,
-            uamMinutes: uamMinutes
-        )
-
-        // First disable all Overrides
-        await disableAllActiveOverrides(createOverrideRunEntry: true)
-
-        // Then save and activate a new custom Override
-        await overrideStorage.storeOverride(override: override)
-
-        // Reset State variables
-        await resetStateVariables()
-
-        // Update View
-        updateLatestOverrideConfiguration()
+        do {
+            let override = Override(
+                name: overrideName,
+                enabled: true,
+                date: Date(),
+                duration: overrideDuration,
+                indefinite: indefinite,
+                percentage: overridePercentage,
+                smbIsOff: smbIsOff,
+                isPreset: isPreset,
+                id: id,
+                overrideTarget: shouldOverrideTarget,
+                target: target,
+                advancedSettings: advancedSettings,
+                isfAndCr: isfAndCr,
+                isf: isf,
+                cr: cr,
+                smbIsScheduledOff: smbIsScheduledOff,
+                start: start,
+                end: end,
+                smbMinutes: smbMinutes,
+                uamMinutes: uamMinutes
+            )
+
+            // First disable all Overrides
+            await disableAllActiveOverrides(createOverrideRunEntry: true)
+
+            // Then save and activate a new custom Override
+            try await overrideStorage.storeOverride(override: override)
+
+            // Reset State variables
+            await resetStateVariables()
+
+            // Update View
+            updateLatestOverrideConfiguration()
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to save custom override: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     /// Saves an Override Preset without activating it.
     /// Saves an Override Preset without activating it.
     /// `enabled` has to be false
     /// `enabled` has to be false
     /// `isPreset` has to be true
     /// `isPreset` has to be true
     func saveOverridePreset() async {
     func saveOverridePreset() async {
-        let preset = Override(
-            name: overrideName,
-            enabled: false,
-            date: Date(),
-            duration: overrideDuration,
-            indefinite: indefinite,
-            percentage: overridePercentage,
-            smbIsOff: smbIsOff,
-            isPreset: true,
-            id: id,
-            overrideTarget: shouldOverrideTarget,
-            target: target,
-            advancedSettings: advancedSettings,
-            isfAndCr: isfAndCr,
-            isf: isf,
-            cr: cr,
-            smbIsScheduledOff: smbIsScheduledOff,
-            start: start,
-            end: end,
-            smbMinutes: smbMinutes,
-            uamMinutes: uamMinutes
-        )
-
-        async let storeOverride: () = overrideStorage.storeOverride(override: preset)
-        async let resetState: () = resetStateVariables()
-        _ = await (storeOverride, resetState)
-        setupOverridePresetsArray()
-        await nightscoutManager.uploadProfiles()
+        do {
+            let preset = Override(
+                name: overrideName,
+                enabled: false,
+                date: Date(),
+                duration: overrideDuration,
+                indefinite: indefinite,
+                percentage: overridePercentage,
+                smbIsOff: smbIsOff,
+                isPreset: true,
+                id: id,
+                overrideTarget: shouldOverrideTarget,
+                target: target,
+                advancedSettings: advancedSettings,
+                isfAndCr: isfAndCr,
+                isf: isf,
+                cr: cr,
+                smbIsScheduledOff: smbIsScheduledOff,
+                start: start,
+                end: end,
+                smbMinutes: smbMinutes,
+                uamMinutes: uamMinutes
+            )
+
+            async let storeOverride: () = overrideStorage.storeOverride(override: preset)
+            async let resetState: () = resetStateVariables()
+            _ = try await (storeOverride, resetState)
+
+            setupOverridePresetsArray()
+            try await nightscoutManager.uploadProfiles()
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to save override preset: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     // MARK: - Override Preset Management
     // MARK: - Override Preset Management
@@ -154,8 +174,15 @@ extension Adjustments.StateModel {
     /// Sets up the array of Override Presets for UI display.
     /// Sets up the array of Override Presets for UI display.
     func setupOverridePresetsArray() {
     func setupOverridePresetsArray() {
         Task {
         Task {
-            let ids = await overrideStorage.fetchForOverridePresets()
-            await updateOverridePresetsArray(with: ids)
+            do {
+                let ids = try await overrideStorage.fetchForOverridePresets()
+                await updateOverridePresetsArray(with: ids)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Failed to setup override presets: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
@@ -175,9 +202,16 @@ extension Adjustments.StateModel {
 
 
     /// Deletes an Override Preset and updates the view.
     /// Deletes an Override Preset and updates the view.
     func invokeOverridePresetDeletion(_ objectID: NSManagedObjectID) async {
     func invokeOverridePresetDeletion(_ objectID: NSManagedObjectID) async {
-        await overrideStorage.deleteOverridePreset(objectID)
-        setupOverridePresetsArray()
-        await nightscoutManager.uploadProfiles()
+        do {
+            await overrideStorage.deleteOverridePreset(objectID)
+            setupOverridePresetsArray()
+            try await nightscoutManager.uploadProfiles()
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to delete override preset: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     // MARK: - Update Latest Override Configuration
     // MARK: - Update Latest Override Configuration
@@ -188,13 +222,20 @@ extension Adjustments.StateModel {
     /// This also needs to be called when we cancel an Override via the Home View to update the State of the Button for this case
     /// This also needs to be called when we cancel an Override via the Home View to update the State of the Button for this case
     func updateLatestOverrideConfiguration() {
     func updateLatestOverrideConfiguration() {
         Task { [weak self] in
         Task { [weak self] in
-            guard let self = self else { return }
+            do {
+                guard let self = self else { return }
 
 
-            let id = await self.overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
+                let id = try await self.overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
 
 
-            // execute sequentially instead of concurrently
-            await self.updateLatestOverrideConfigurationOfState(from: id)
-            await self.setCurrentOverride(from: id)
+                // execute sequentially instead of concurrently
+                await self.updateLatestOverrideConfigurationOfState(from: id)
+                await self.setCurrentOverride(from: id)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Failed to update override configuration: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 

+ 55 - 35
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift

@@ -11,7 +11,7 @@ extension Adjustments.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
     /// 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() {
     func updateLatestTempTargetConfiguration() {
         Task {
         Task {
-            let id = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1)
+            let id = try await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1)
             async let updateState: () = updateLatestTempTargetConfigurationOfState(from: id)
             async let updateState: () = updateLatestTempTargetConfigurationOfState(from: id)
             async let setTempTarget: () = setCurrentTempTarget(from: id)
             async let setTempTarget: () = setCurrentTempTarget(from: id)
             _ = await (updateState, setTempTarget)
             _ = await (updateState, setTempTarget)
@@ -58,13 +58,20 @@ extension Adjustments.StateModel {
 
 
     /// Sets up Temp Targets using fetch and update functions.
     /// Sets up Temp Targets using fetch and update functions.
     func setupTempTargets(
     func setupTempTargets(
-        fetchFunction: @escaping () async -> [NSManagedObjectID],
+        fetchFunction: @escaping () async throws -> [NSManagedObjectID],
         updateFunction: @escaping @MainActor([TempTargetStored]) -> Void
         updateFunction: @escaping @MainActor([TempTargetStored]) -> Void
     ) {
     ) {
         Task {
         Task {
-            let ids = await fetchFunction()
-            let tempTargetObjects = await fetchTempTargetObjects(for: ids)
-            await updateFunction(tempTargetObjects)
+            do {
+                let ids = try await fetchFunction()
+                let tempTargetObjects = await fetchTempTargetObjects(for: ids)
+                await updateFunction(tempTargetObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Failed to setup temp targets: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
@@ -83,7 +90,7 @@ extension Adjustments.StateModel {
     /// Sets up the Temp Target presets array for the view.
     /// Sets up the Temp Target presets array for the view.
     func setupTempTargetPresetsArray() {
     func setupTempTargetPresetsArray() {
         setupTempTargets(
         setupTempTargets(
-            fetchFunction: tempTargetStorage.fetchForTempTargetPresets,
+            fetchFunction: { try await self.tempTargetStorage.fetchForTempTargetPresets() },
             updateFunction: { tempTargets in
             updateFunction: { tempTargets in
                 self.tempTargetPresets = tempTargets
                 self.tempTargetPresets = tempTargets
             }
             }
@@ -93,7 +100,7 @@ extension Adjustments.StateModel {
     /// Sets up the scheduled Temp Targets array for the view.
     /// Sets up the scheduled Temp Targets array for the view.
     func setupScheduledTempTargetsArray() {
     func setupScheduledTempTargetsArray() {
         setupTempTargets(
         setupTempTargets(
-            fetchFunction: tempTargetStorage.fetchScheduledTempTargets,
+            fetchFunction: { try await self.tempTargetStorage.fetchScheduledTempTargets() },
             updateFunction: { tempTargets in
             updateFunction: { tempTargets in
                 self.scheduledTempTargets = tempTargets
                 self.scheduledTempTargets = tempTargets
             }
             }
@@ -146,25 +153,35 @@ extension Adjustments.StateModel {
 
 
     /// Enables a scheduled Temp Target for a specific date.
     /// Enables a scheduled Temp Target for a specific date.
     func enableScheduledTempTarget(for date: Date) async {
     func enableScheduledTempTarget(for date: Date) async {
-        let ids = await tempTargetStorage.fetchScheduledTempTarget(for: date)
-        guard let firstID = ids.first else {
-            debugPrint("No Temp Target found for the specified date.")
-            return
-        }
-        await setCurrentTempTarget(from: ids)
-
-        await MainActor.run {
-            do {
-                if let tempTarget = try viewContext.existingObject(with: firstID) as? TempTargetStored {
-                    tempTarget.enabled = true
-                    try viewContext.save()
-                    isTempTargetEnabled = true
+        do {
+            let ids = try await tempTargetStorage.fetchScheduledTempTarget(for: date)
+            guard let firstID = ids.first else {
+                debug(.default, "No Temp Target found for the specified date.")
+                return
+            }
+            await setCurrentTempTarget(from: ids)
+
+            try await MainActor.run {
+                guard let tempTarget = try viewContext.existingObject(with: firstID) as? TempTargetStored else {
+                    throw NSError(
+                        domain: "TempTarget",
+                        code: -1,
+                        userInfo: [NSLocalizedDescriptionKey: "Failed to find temp target"]
+                    )
                 }
                 }
-            } catch {
-                debugPrint("Failed to enable the Temp Target: \(error.localizedDescription)")
+
+                tempTarget.enabled = true
+                try viewContext.save()
+                isTempTargetEnabled = true
             }
             }
+
+            setupScheduledTempTargetsArray()
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to enable scheduled temp target: \(error.localizedDescription)"
+            )
         }
         }
-        setupScheduledTempTargetsArray()
     }
     }
 
 
     /// Waits until a target date before proceeding.
     /// Waits until a target date before proceeding.
@@ -259,12 +276,15 @@ extension Adjustments.StateModel {
     }
     }
 
 
     /// Disables all active Temp Targets.
     /// Disables all active Temp Targets.
-    @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 tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 0) // 0 = no fetch limit
+    @MainActor func disableAllActiveTempTargets(
+        except id: NSManagedObjectID? = nil,
+        createTempTargetRunEntry: Bool
+    ) async {
+        do {
+            // Get ALL NSManagedObject IDs of ALL active Temp Targets to cancel every single Temp Target
+            let ids = try await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 0) // 0 = no fetch limit
 
 
-        await viewContext.perform {
-            do {
+            try await viewContext.perform {
                 // Fetch the existing TempTargetStored objects from the context
                 // Fetch the existing TempTargetStored objects from the context
                 let results = try ids.compactMap { id in
                 let results = try ids.compactMap { id in
                     try self.viewContext.existingObject(with: id) as? TempTargetStored
                     try self.viewContext.existingObject(with: id) as? TempTargetStored
@@ -273,7 +293,7 @@ extension Adjustments.StateModel {
                 // If there are no results, return early
                 // If there are no results, return early
                 guard !results.isEmpty else { return }
                 guard !results.isEmpty else { return }
 
 
-                // Check if we also need to create a corresponding TempTargetRunStored entry, i.e. when the User uses the Cancel Button in Temp Target View
+                // Check if we also need to create a corresponding TempTargetRunStored entry
                 if createTempTargetRunEntry {
                 if createTempTargetRunEntry {
                     // Use the first temp target to create a new TempTargetRunStored entry
                     // Use the first temp target to create a new TempTargetRunStored entry
                     if let canceledTempTarget = results.first {
                     if let canceledTempTarget = results.first {
@@ -282,8 +302,7 @@ extension Adjustments.StateModel {
                         newTempTargetRunStored.name = canceledTempTarget.name
                         newTempTargetRunStored.name = canceledTempTarget.name
                         newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
                         newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
                         newTempTargetRunStored.endDate = Date()
                         newTempTargetRunStored.endDate = Date()
-                        newTempTargetRunStored
-                            .target = canceledTempTarget.target ?? 0
+                        newTempTargetRunStored.target = canceledTempTarget.target ?? 0
                         newTempTargetRunStored.tempTarget = canceledTempTarget
                         newTempTargetRunStored.tempTarget = canceledTempTarget
                         newTempTargetRunStored.isUploadedToNS = false
                         newTempTargetRunStored.isUploadedToNS = false
                     }
                     }
@@ -303,11 +322,12 @@ extension Adjustments.StateModel {
                     // Update the storage
                     // Update the storage
                     self.tempTargetStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
                     self.tempTargetStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
                 }
                 }
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active TempTargets with error: \(error.localizedDescription)"
-                )
             }
             }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to disable active temp targets: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 
 

+ 1 - 1
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel.swift

@@ -174,7 +174,7 @@ extension Adjustments {
                 guard viewContext.hasChanges else { return }
                 guard viewContext.hasChanges else { return }
                 try viewContext.save()
                 try viewContext.save()
                 setupOverridePresetsArray()
                 setupOverridePresetsArray()
-                Task { await nightscoutManager.uploadProfiles() }
+                Task { try await nightscoutManager.uploadProfiles() }
             } catch {
             } catch {
                 debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Override Presets order")
                 debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Override Presets order")
             }
             }

+ 1 - 1
Trio/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift

@@ -518,7 +518,7 @@ struct EditOverrideForm: View {
                         guard moc.hasChanges else { return }
                         guard moc.hasChanges else { return }
                         try moc.save()
                         try moc.save()
                         Task {
                         Task {
-                            await state.nightscoutManager.uploadProfiles()
+                            try await state.nightscoutManager.uploadProfiles()
                         }
                         }
                         // Disable previous active Override
                         // Disable previous active Override
                         if let currentActiveOverride = state.currentActiveOverride {
                         if let currentActiveOverride = state.currentActiveOverride {

+ 1 - 1
Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -67,7 +67,7 @@ extension AlgorithmAdvancedSettings {
 
 
                         Task.detached(priority: .low) {
                         Task.detached(priority: .low) {
                             debug(.nightscout, "Attempting to upload DIA to Nightscout")
                             debug(.nightscout, "Attempting to upload DIA to Nightscout")
-                            await self.nightscout.uploadProfiles()
+                            try await self.nightscout.uploadProfiles()
                         }
                         }
                     } receiveValue: {}
                     } receiveValue: {}
                     .store(in: &lifetime)
                     .store(in: &lifetime)

+ 11 - 4
Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -41,10 +41,17 @@ extension AutosensSettings {
 
 
         private func setupDeterminationsArray() {
         private func setupDeterminationsArray() {
             Task {
             Task {
-                let ids = await determinationStorage.fetchLastDeterminationObjectID(
-                    predicate: NSPredicate.enactedDetermination
-                )
-                await updateDeterminationsArray(with: ids)
+                do {
+                    let ids = try await determinationStorage.fetchLastDeterminationObjectID(
+                        predicate: NSPredicate.enactedDetermination
+                    )
+                    await updateDeterminationsArray(with: ids)
+                } catch {
+                    debug(
+                        .default,
+                        "\(DebuggingIdentifiers.failed) Error fetching determination IDs: \(error.localizedDescription)"
+                    )
+                }
             }
             }
         }
         }
 
 

+ 1 - 1
Trio/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -95,7 +95,7 @@ extension BasalProfileEditor {
 
 
                         Task.detached(priority: .low) {
                         Task.detached(priority: .low) {
                             debug(.nightscout, "Attempting to upload basal rates to Nightscout")
                             debug(.nightscout, "Attempting to upload basal rates to Nightscout")
-                            await self.nightscout.uploadProfiles()
+                            try await self.nightscout.uploadProfiles()
                         }
                         }
                     case .failure:
                     case .failure:
                         // Handle the error, show error message
                         // Handle the error, show error message

+ 1 - 1
Trio/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift

@@ -71,7 +71,7 @@ extension CarbRatioEditor {
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
             Task.detached(priority: .low) {
             Task.detached(priority: .low) {
                 debug(.nightscout, "Attempting to upload CRs to Nightscout")
                 debug(.nightscout, "Attempting to upload CRs to Nightscout")
-                await self.nightscout.uploadProfiles()
+                try await self.nightscout.uploadProfiles()
             }
             }
         }
         }
 
 

+ 3 - 3
Trio/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -304,7 +304,7 @@ extension DataTable {
                     newNote: newNote
                     newNote: newNote
                 )
                 )
 
 
-                await createNewEntries(
+                try await createNewEntries(
                     originalDate: newDate,
                     originalDate: newDate,
                     newCarbs: newCarbs,
                     newCarbs: newCarbs,
                     newFat: newFat,
                     newFat: newFat,
@@ -322,7 +322,7 @@ extension DataTable {
             newFat: Decimal,
             newFat: Decimal,
             newProtein: Decimal,
             newProtein: Decimal,
             newNote: String
             newNote: String
-        ) async {
+        ) async throws {
             let newEntry = CarbsEntry(
             let newEntry = CarbsEntry(
                 id: UUID().uuidString,
                 id: UUID().uuidString,
                 createdAt: Date(),
                 createdAt: Date(),
@@ -337,7 +337,7 @@ extension DataTable {
             )
             )
 
 
             // Handles internally whether to create fake carbs or not based on whether fat > 0 or protein > 0
             // Handles internally whether to create fake carbs or not based on whether fat > 0 or protein > 0
-            await carbsStorage.storeCarbs([newEntry], areFetchedFromRemote: false)
+            try await carbsStorage.storeCarbs([newEntry], areFetchedFromRemote: false)
         }
         }
 
 
         /// Deletes the old carb/ FPU entries and creates new ones with updated values
         /// Deletes the old carb/ FPU entries and creates new ones with updated values

+ 13 - 6
Trio/Sources/Modules/Home/HomeStateModel+Setup/BatterySetup.swift

@@ -4,14 +4,22 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupBatteryArray() {
     func setupBatteryArray() {
         Task {
         Task {
-            let ids = await self.fetchBattery()
-            let batteryObjects: [OpenAPS_Battery] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateBatteryArray(with: batteryObjects)
+            do {
+                let ids = try await self.fetchBattery()
+                let batteryObjects: [OpenAPS_Battery] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateBatteryArray(with: batteryObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up battery array: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchBattery() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchBattery() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OpenAPS_Battery.self,
             ofType: OpenAPS_Battery.self,
             onContext: batteryFetchContext,
             onContext: batteryFetchContext,
             predicate: NSPredicate.predicateFor30MinAgo,
             predicate: NSPredicate.predicateFor30MinAgo,
@@ -21,7 +29,6 @@ extension Home.StateModel {
 
 
         return await batteryFetchContext.perform {
         return await batteryFetchContext.perform {
             guard let fetchedResults = results as? [OpenAPS_Battery] else { return [] }
             guard let fetchedResults = results as? [OpenAPS_Battery] else { return [] }
-
             return fetchedResults.map(\.objectID)
             return fetchedResults.map(\.objectID)
         }
         }
     }
     }

+ 20 - 10
Trio/Sources/Modules/Home/HomeStateModel+Setup/CarbSetup.swift

@@ -4,14 +4,19 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupCarbsArray() {
     func setupCarbsArray() {
         Task {
         Task {
-            let ids = await self.fetchCarbs()
-            let carbObjects: [CarbEntryStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateCarbsArray(with: carbObjects)
+            do {
+                let ids = try await self.fetchCarbs()
+                let carbObjects: [CarbEntryStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateCarbsArray(with: carbObjects)
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) Error fetching carb objects: \(error) in \(#file):\(#line)")
+            }
         }
         }
     }
     }
 
 
-    private func fetchCarbs() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchCarbs() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: carbsFetchContext,
             onContext: carbsFetchContext,
             predicate: NSPredicate.carbsForChart,
             predicate: NSPredicate.carbsForChart,
@@ -33,14 +38,19 @@ extension Home.StateModel {
 
 
     func setupFPUsArray() {
     func setupFPUsArray() {
         Task {
         Task {
-            let ids = await self.fetchFPUs()
-            let fpuObjects: [CarbEntryStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateFPUsArray(with: fpuObjects)
+            do {
+                let ids = try await self.fetchFPUs()
+                let fpuObjects: [CarbEntryStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateFPUsArray(with: fpuObjects)
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) Error fetching FPU objects: \(error) in \(#file):\(#line)")
+            }
         }
         }
     }
     }
 
 
-    private func fetchFPUs() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchFPUs() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,
             onContext: fpuFetchContext,
             onContext: fpuFetchContext,
             predicate: NSPredicate.fpusForChart,
             predicate: NSPredicate.fpusForChart,

+ 20 - 14
Trio/Sources/Modules/Home/HomeStateModel+Setup/DeterminationSetup.swift

@@ -4,28 +4,34 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupDeterminationsArray() {
     func setupDeterminationsArray() {
         Task {
         Task {
-            // Get the NSManagedObjectIDs
-            async let enactedObjectIds = determinationStorage
-                .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
-            async let enactedAndNonEnactedObjectIds = fetchCobAndIob()
+            do {
+                // Get the NSManagedObjectIDs
+                async let enactedObjectIds = determinationStorage
+                    .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
+                async let enactedAndNonEnactedObjectIds = fetchCobAndIob()
 
 
-            let enactedIDs = await enactedObjectIds
-            let enactedAndNonEnactedIds = await enactedAndNonEnactedObjectIds
+                let enactedIDs = try await enactedObjectIds
+                let enactedAndNonEnactedIds = try await enactedAndNonEnactedObjectIds
 
 
-            // Get the NSManagedObjects and return them on the Main Thread
-            await updateDeterminationsArray(with: enactedIDs, keyPath: \.determinationsFromPersistence)
-            await updateDeterminationsArray(with: enactedAndNonEnactedIds, keyPath: \.enactedAndNonEnactedDeterminations)
+                // Get the NSManagedObjects and return them on the Main Thread
+                try await updateDeterminationsArray(with: enactedIDs, keyPath: \.determinationsFromPersistence)
+                try await updateDeterminationsArray(with: enactedAndNonEnactedIds, keyPath: \.enactedAndNonEnactedDeterminations)
 
 
-            await updateForecastData()
+                await updateForecastData()
+            } catch let error as CoreDataError {
+                debug(.default, "Core Data error in setupDeterminationsArray: \(error.localizedDescription)")
+            } catch {
+                debug(.default, "Unexpected error in setupDeterminationsArray: \(error.localizedDescription)")
+            }
         }
         }
     }
     }
 
 
     @MainActor private func updateDeterminationsArray(
     @MainActor private func updateDeterminationsArray(
         with IDs: [NSManagedObjectID],
         with IDs: [NSManagedObjectID],
         keyPath: ReferenceWritableKeyPath<Home.StateModel, [OrefDetermination]>
         keyPath: ReferenceWritableKeyPath<Home.StateModel, [OrefDetermination]>
-    ) async {
+    ) async throws {
         // Fetch the objects off the main thread
         // Fetch the objects off the main thread
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
+        let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
             .getNSManagedObject(with: IDs, context: viewContext)
             .getNSManagedObject(with: IDs, context: viewContext)
 
 
         // Update the array on the main thread
         // Update the array on the main thread
@@ -33,8 +39,8 @@ extension Home.StateModel {
     }
     }
 
 
     // Custom fetch to more efficiently filter only for cob and iob
     // Custom fetch to more efficiently filter only for cob and iob
-    private func fetchCobAndIob() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchCobAndIob() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: determinationFetchContext,
             onContext: determinationFetchContext,
             predicate: NSPredicate.determinationsForCobIobCharts,
             predicate: NSPredicate.determinationsForCobIobCharts,

+ 19 - 10
Trio/Sources/Modules/Home/HomeStateModel+Setup/ForecastSetup.swift

@@ -6,18 +6,27 @@ extension Home.StateModel {
     func preprocessForecastData() async -> [(
     func preprocessForecastData() async -> [(
         id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID]
         id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID]
     )] {
     )] {
-        // Get the Determination ID on the main context
-        guard let determination = await viewContext.perform({
-            self.enactedAndNonEnactedDeterminations.first
-        }) else {
+        do {
+            // Get the Determination ID on the main context
+            guard let determination = await viewContext.perform({
+                self.enactedAndNonEnactedDeterminations.first
+            }) else {
+                debug(.default, "No determination found for forecast preprocessing")
+                return []
+            }
+
+            // Fetch complete forecast hierarchy with prefetched values
+            return try await determinationStorage.fetchForecastHierarchy(
+                for: determination.objectID,
+                in: taskContext
+            )
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to preprocess forecast data: \(error.localizedDescription)"
+            )
             return []
             return []
         }
         }
-
-        // Fetch complete forecast hierarchy with prefetched values
-        return await determinationStorage.fetchForecastHierarchy(
-            for: determination.objectID,
-            in: taskContext
-        )
     }
     }
 
 
     // Update forecast data and UI on the main thread
     // Update forecast data and UI on the main thread

+ 13 - 5
Trio/Sources/Modules/Home/HomeStateModel+Setup/GlucoseSetup.swift

@@ -4,14 +4,22 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupGlucoseArray() {
     func setupGlucoseArray() {
         Task {
         Task {
-            let ids = await self.fetchGlucose()
-            let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateGlucoseArray(with: glucoseObjects)
+            do {
+                let ids = try await self.fetchGlucose()
+                let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateGlucoseArray(with: glucoseObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up glucose array: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: glucoseFetchContext,
             onContext: glucoseFetchContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,

+ 24 - 13
Trio/Sources/Modules/Home/HomeStateModel+Setup/OverrideSetup.swift

@@ -5,14 +5,21 @@ extension Home.StateModel {
     // Setup Overrides
     // Setup Overrides
     func setupOverrides() {
     func setupOverrides() {
         Task {
         Task {
-            let ids = await self.fetchOverrides()
-            let overrideObjects: [OverrideStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateOverrideArray(with: overrideObjects)
+            do {
+                let ids = try await self.fetchOverrides()
+                let overrideObjects: [OverrideStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateOverrideArray(with: overrideObjects)
+            } catch let error as CoreDataError {
+                debug(.default, "Core Data error in setupOverrides: \(error.localizedDescription)")
+            } catch {
+                debug(.default, "Unexpected error in setupOverrides: \(error.localizedDescription)")
+            }
         }
         }
     }
     }
 
 
-    private func fetchOverrides() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchOverrides() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: overrideFetchContext,
             onContext: overrideFetchContext,
             predicate: NSPredicate.lastActiveOverride, // this predicate filters for all Overrides within the last 24h
             predicate: NSPredicate.lastActiveOverride, // this predicate filters for all Overrides within the last 24h
@@ -22,7 +29,6 @@ extension Home.StateModel {
 
 
         return await overrideFetchContext.perform {
         return await overrideFetchContext.perform {
             guard let fetchedResults = results as? [OverrideStored] else { return [] }
             guard let fetchedResults = results as? [OverrideStored] else { return [] }
-
             return fetchedResults.map(\.objectID)
             return fetchedResults.map(\.objectID)
         }
         }
     }
     }
@@ -41,16 +47,22 @@ extension Home.StateModel {
     // Setup expired Overrides
     // Setup expired Overrides
     func setupOverrideRunStored() {
     func setupOverrideRunStored() {
         Task {
         Task {
-            let ids = await self.fetchOverrideRunStored()
-            let overrideRunObjects: [OverrideRunStored] = await CoreDataStack.shared
-                .getNSManagedObject(with: ids, context: viewContext)
-            await updateOverrideRunStoredArray(with: overrideRunObjects)
+            do {
+                let ids = try await self.fetchOverrideRunStored()
+                let overrideRunObjects: [OverrideRunStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateOverrideRunStoredArray(with: overrideRunObjects)
+            } catch let error as CoreDataError {
+                debug(.default, "Core Data error in setupOverrideRunStored: \(error.localizedDescription)")
+            } catch {
+                debug(.default, "Unexpected error in setupOverrideRunStored: \(error.localizedDescription)")
+            }
         }
         }
     }
     }
 
 
-    private func fetchOverrideRunStored() async -> [NSManagedObjectID] {
+    private func fetchOverrideRunStored() async throws -> [NSManagedObjectID] {
         let predicate = NSPredicate(format: "startDate >= %@", Date.oneDayAgo as NSDate)
         let predicate = NSPredicate(format: "startDate >= %@", Date.oneDayAgo as NSDate)
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideRunStored.self,
             ofType: OverrideRunStored.self,
             onContext: overrideFetchContext,
             onContext: overrideFetchContext,
             predicate: predicate,
             predicate: predicate,
@@ -60,7 +72,6 @@ extension Home.StateModel {
 
 
         return await overrideFetchContext.perform {
         return await overrideFetchContext.perform {
             guard let fetchedResults = results as? [OverrideRunStored] else { return [] }
             guard let fetchedResults = results as? [OverrideRunStored] else { return [] }
-
             return fetchedResults.map(\.objectID)
             return fetchedResults.map(\.objectID)
         }
         }
     }
     }

+ 24 - 9
Trio/Sources/Modules/Home/HomeStateModel+Setup/PumpHistorySetup.swift

@@ -4,14 +4,22 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupInsulinArray() {
     func setupInsulinArray() {
         Task {
         Task {
-            let ids = await self.fetchInsulin()
-            let insulinObjects: [PumpEventStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateInsulinArray(with: insulinObjects)
+            do {
+                let ids = try await self.fetchInsulin()
+                let insulinObjects: [PumpEventStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateInsulinArray(with: insulinObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up insulin array: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchInsulin() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchInsulin() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: pumpHistoryFetchContext,
             onContext: pumpHistoryFetchContext,
             predicate: NSPredicate.pumpHistoryLast24h,
             predicate: NSPredicate.pumpHistoryLast24h,
@@ -48,13 +56,20 @@ extension Home.StateModel {
     // The predicate filters out all external boluses to prevent the progress bar from displaying the amount of an external bolus when an external bolus is added after a pump bolus
     // The predicate filters out all external boluses to prevent the progress bar from displaying the amount of an external bolus when an external bolus is added after a pump bolus
     func setupLastBolus() {
     func setupLastBolus() {
         Task {
         Task {
-            guard let id = await self.fetchLastBolus() else { return }
-            await updateLastBolus(with: id)
+            do {
+                guard let id = try await self.fetchLastBolus() else { return }
+                await updateLastBolus(with: id)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up last bolus: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    func fetchLastBolus() async -> NSManagedObjectID? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchLastBolus() async throws -> NSManagedObjectID? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: pumpHistoryFetchContext,
             onContext: pumpHistoryFetchContext,
             predicate: NSPredicate.lastPumpBolus,
             predicate: NSPredicate.lastPumpBolus,

+ 26 - 12
Trio/Sources/Modules/Home/HomeStateModel+Setup/TempTargetSetup.swift

@@ -4,15 +4,22 @@ import Foundation
 extension Home.StateModel {
 extension Home.StateModel {
     func setupTempTargetsStored() {
     func setupTempTargetsStored() {
         Task {
         Task {
-            let ids = await self.fetchTempTargets()
-            let tempTargetObjects: [TempTargetStored] = await CoreDataStack.shared
-                .getNSManagedObject(with: ids, context: viewContext)
-            await updateTempTargetsArray(with: tempTargetObjects)
+            do {
+                let ids = try await self.fetchTempTargets()
+                let tempTargetObjects: [TempTargetStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateTempTargetsArray(with: tempTargetObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up tempTargetStored: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchTempTargets() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchTempTargets() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             ofType: TempTargetStored.self,
             onContext: tempTargetFetchContext,
             onContext: tempTargetFetchContext,
             predicate: NSPredicate.lastActiveTempTarget,
             predicate: NSPredicate.lastActiveTempTarget,
@@ -33,16 +40,23 @@ extension Home.StateModel {
     // Setup expired TempTargets
     // Setup expired TempTargets
     func setupTempTargetsRunStored() {
     func setupTempTargetsRunStored() {
         Task {
         Task {
-            let ids = await self.fetchTempTargetRunStored()
-            let tempTargetRunObjects: [TempTargetRunStored] = await CoreDataStack.shared
-                .getNSManagedObject(with: ids, context: viewContext)
-            await updateTempTargetRunStoredArray(with: tempTargetRunObjects)
+            do {
+                let ids = try await self.fetchTempTargetRunStored()
+                let tempTargetRunObjects: [TempTargetRunStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateTempTargetRunStoredArray(with: tempTargetRunObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up temp targetsRunStored: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchTempTargetRunStored() async -> [NSManagedObjectID] {
+    private func fetchTempTargetRunStored() async throws -> [NSManagedObjectID] {
         let predicate = NSPredicate(format: "startDate >= %@", Date.oneDayAgo as NSDate)
         let predicate = NSPredicate(format: "startDate >= %@", Date.oneDayAgo as NSDate)
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetRunStored.self,
             ofType: TempTargetRunStored.self,
             onContext: tempTargetFetchContext,
             onContext: tempTargetFetchContext,
             predicate: predicate,
             predicate: predicate,

+ 1 - 1
Trio/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -86,7 +86,7 @@ extension ISFEditor {
 
 
             Task.detached(priority: .low) {
             Task.detached(priority: .low) {
                 debug(.nightscout, "Attempting to upload ISF to Nightscout")
                 debug(.nightscout, "Attempting to upload ISF to Nightscout")
-                await self.nightscout.uploadProfiles()
+                try await self.nightscout.uploadProfiles()
             }
             }
         }
         }
 
 

+ 18 - 13
Trio/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -75,7 +75,7 @@ extension NightscoutConfig {
                     if enabled {
                     if enabled {
                         debug(.nightscout, "Upload has been enabled by the user.")
                         debug(.nightscout, "Upload has been enabled by the user.")
                         Task {
                         Task {
-                            await self.nightscoutManager.uploadProfiles()
+                            try await self.nightscoutManager.uploadProfiles()
                         }
                         }
                     } else {
                     } else {
                         debug(.nightscout, "Upload has been disabled by the user.")
                         debug(.nightscout, "Upload has been disabled by the user.")
@@ -344,20 +344,25 @@ extension NightscoutConfig {
             let glucose = await nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval))
             let glucose = await nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval))
 
 
             if glucose.isNotEmpty {
             if glucose.isNotEmpty {
-                await MainActor.run {
-                    self.backfilling = false
-                }
-
-                await glucoseStorage.storeGlucose(glucose)
+                do {
+                    try await glucoseStorage.storeGlucose(glucose)
 
 
-                Task.detached {
-                    await self.healthKitManager.uploadGlucose()
+                    Task.detached {
+                        await self.healthKitManager.uploadGlucose()
+                    }
+                } catch let error as CoreDataError {
+                    debug(.nightscout, "Core Data error while storing backfilled glucose: \(error.localizedDescription)")
+                    message = "Error: \(error.localizedDescription)"
+                } catch {
+                    debug(.nightscout, "Unexpected error while storing backfilled glucose: \(error.localizedDescription)")
+                    message = "Error: \(error.localizedDescription)"
                 }
                 }
             } else {
             } else {
-                await MainActor.run {
-                    self.backfilling = false
-                    debug(.nightscout, "No glucose values found or fetched to backfill.")
-                }
+                debug(.nightscout, "No glucose values found or fetched to backfill.")
+            }
+
+            await MainActor.run {
+                self.backfilling = false
             }
             }
         }
         }
 
 
@@ -384,7 +389,7 @@ extension NightscoutConfig {
 
 
                         Task.detached(priority: .low) {
                         Task.detached(priority: .low) {
                             debug(.nightscout, "Attempting to upload DIA to Nightscout after import review")
                             debug(.nightscout, "Attempting to upload DIA to Nightscout after import review")
-                            await self.nightscoutManager.uploadProfiles()
+                            try await self.nightscoutManager.uploadProfiles()
                         }
                         }
                     } receiveValue: {}
                     } receiveValue: {}
                     .store(in: &lifetime)
                     .store(in: &lifetime)

+ 15 - 0
Trio/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -16,6 +16,8 @@ extension NightscoutConfig {
         @State var hintLabel: String?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
         @State private var booleanPlaceholder: Bool = false
+        @State var backfillAlert: Alert?
+        @State var isBackfillAlertPresented = false
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @Environment(AppState.self) var appState
         @Environment(AppState.self) var appState
@@ -132,6 +134,16 @@ extension NightscoutConfig {
                                 Button {
                                 Button {
                                     Task {
                                     Task {
                                         await state.backfillGlucose()
                                         await state.backfillGlucose()
+                                        if !state.message.isEmpty && state.message.hasPrefix("Error:") {
+                                            DispatchQueue.main.async {
+                                                backfillAlert = Alert(
+                                                    title: Text("Backfill Failed"),
+                                                    message: Text(state.message),
+                                                    dismissButton: .default(Text("OK"))
+                                                )
+                                                isBackfillAlertPresented = true
+                                            }
+                                        }
                                     }
                                     }
                                 } label: {
                                 } label: {
                                     Text("Backfill Glucose")
                                     Text("Backfill Glucose")
@@ -194,6 +206,9 @@ extension NightscoutConfig {
             .alert(isPresented: $isImportAlertPresented) {
             .alert(isPresented: $isImportAlertPresented) {
                 importAlert ?? Alert(title: Text("Unknown Error"))
                 importAlert ?? Alert(title: Text("Unknown Error"))
             }
             }
+            .alert(isPresented: $isBackfillAlertPresented) {
+                backfillAlert ?? Alert(title: Text("Unknown Error"))
+            }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
         }
         }

+ 30 - 26
Trio/Sources/Modules/Stat/StatStateModel.swift

@@ -46,35 +46,39 @@ extension Stat {
         }
         }
 
 
         private func fetchGlucose(for duration: Duration) async -> [NSManagedObjectID] {
         private func fetchGlucose(for duration: Duration) async -> [NSManagedObjectID] {
-            let predicate: NSPredicate
-
-            switch duration {
-            case .Day:
-                predicate = NSPredicate.glucoseForStatsDay
-            case .Week:
-                predicate = NSPredicate.glucoseForStatsWeek
-            case .Today:
-                predicate = NSPredicate.glucoseForStatsToday
-            case .Month:
-                predicate = NSPredicate.glucoseForStatsMonth
-            case .Total:
-                predicate = NSPredicate.glucoseForStatsTotal
-            }
+            do {
+                let predicate: NSPredicate
 
 
-            let results = await CoreDataStack.shared.fetchEntitiesAsync(
-                ofType: GlucoseStored.self,
-                onContext: context,
-                predicate: predicate,
-                key: "date",
-                ascending: false,
-                batchSize: 100,
-                propertiesToFetch: ["glucose", "objectID"]
-            )
+                switch duration {
+                case .Day:
+                    predicate = NSPredicate.glucoseForStatsDay
+                case .Week:
+                    predicate = NSPredicate.glucoseForStatsWeek
+                case .Today:
+                    predicate = NSPredicate.glucoseForStatsToday
+                case .Month:
+                    predicate = NSPredicate.glucoseForStatsMonth
+                case .Total:
+                    predicate = NSPredicate.glucoseForStatsTotal
+                }
 
 
-            return await context.perform {
-                guard let fetchedResults = results as? [[String: Any]] else { return [] }
+                let results = try await CoreDataStack.shared.fetchEntitiesAsync(
+                    ofType: GlucoseStored.self,
+                    onContext: context,
+                    predicate: predicate,
+                    key: "date",
+                    ascending: false,
+                    batchSize: 100,
+                    propertiesToFetch: ["glucose", "objectID"]
+                )
 
 
-                return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }
+                return await context.perform {
+                    guard let fetchedResults = results as? [[String: Any]] else { return [] }
+                    return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }
+                }
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) Error fetching glucose for stats: \(error.localizedDescription)")
+                return []
             }
             }
         }
         }
 
 

+ 1 - 1
Trio/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -84,7 +84,7 @@ extension TargetsEditor {
 
 
             Task.detached(priority: .low) {
             Task.detached(priority: .low) {
                 debug(.nightscout, "Attempting to upload targets to Nightscout")
                 debug(.nightscout, "Attempting to upload targets to Nightscout")
-                await self.nightscout.uploadProfiles()
+                try await self.nightscout.uploadProfiles()
             }
             }
         }
         }
 
 

+ 140 - 108
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -170,32 +170,39 @@ extension Treatments {
         private func setupBolusStateConcurrently() {
         private func setupBolusStateConcurrently() {
             debug(.bolusState, "setupBolusStateConcurrently fired")
             debug(.bolusState, "setupBolusStateConcurrently fired")
             Task {
             Task {
-                await withTaskGroup(of: Void.self) { group in
-                    group.addTask {
-                        self.setupGlucoseArray()
-                    }
-                    group.addTask {
-                        self.setupDeterminationsAndForecasts()
-                    }
-                    group.addTask {
-                        await self.setupSettings()
-                    }
-                    group.addTask {
-                        self.registerObservers()
-                    }
-
-                    if self.waitForSuggestionInitial {
+                do {
+                    try await withThrowingTaskGroup(of: Void.self) { group in
+                        group.addTask {
+                            self.setupGlucoseArray()
+                        }
+                        group.addTask {
+                            self.setupDeterminationsAndForecasts()
+                        }
                         group.addTask {
                         group.addTask {
-                            let isDetermineBasalSuccessful = await self.apsManager.determineBasal()
-                            if !isDetermineBasalSuccessful {
-                                await MainActor.run {
-                                    self.waitForSuggestion = false
-                                    self.insulinRequired = 0
-                                    self.insulinRecommended = 0
+                            await self.setupSettings()
+                        }
+                        group.addTask {
+                            self.registerObservers()
+                        }
+
+                        if self.waitForSuggestionInitial {
+                            group.addTask {
+                                let isDetermineBasalSuccessful = try await self.apsManager.determineBasal()
+                                if !isDetermineBasalSuccessful {
+                                    await MainActor.run {
+                                        self.waitForSuggestion = false
+                                        self.insulinRequired = 0
+                                        self.insulinRecommended = 0
+                                    }
                                 }
                                 }
                             }
                             }
                         }
                         }
+
+                        // Wait for all tasks to complete
+                        try await group.waitForAll()
                     }
                     }
+                } catch let error as NSError {
+                    debug(.default, "Failed to setup bolus state concurrently: \(error.localizedDescription)")
                 }
                 }
             }
             }
         }
         }
@@ -206,7 +213,7 @@ extension Treatments {
         ///   - `apsManager.bolusProgress` is a `CurrentValueSubject<Decimal?, Never>`.
         ///   - `apsManager.bolusProgress` is a `CurrentValueSubject<Decimal?, Never>`.
         ///   - When a bolus starts, this subject emits `0` (or a fraction like `0.1, 0.5, etc.`).
         ///   - When a bolus starts, this subject emits `0` (or a fraction like `0.1, 0.5, etc.`).
         ///   - When the bolus finishes, the subject is typically set to `nil`.
         ///   - When the bolus finishes, the subject is typically set to `nil`.
-        ///   - This treats ANY non-nil value as “bolus in progress.”
+        ///   - This treats ANY non-nil value as "bolus in progress."
         ///
         ///
         private func subscribeToBolusProgress() {
         private func subscribeToBolusProgress() {
             bolusProgressCancellable = apsManager.bolusProgress
             bolusProgressCancellable = apsManager.bolusProgress
@@ -537,34 +544,39 @@ extension Treatments {
         // MARK: - Carbs
         // MARK: - Carbs
 
 
         func saveMeal() async {
         func saveMeal() async {
-            guard carbs > 0 || fat > 0 || protein > 0 else { return }
+            do {
+                guard carbs > 0 || fat > 0 || protein > 0 else { return }
 
 
-            await MainActor.run {
-                self.carbs = min(self.carbs, self.maxCarbs)
-                self.fat = min(self.fat, self.maxFat)
-                self.protein = min(self.protein, self.maxProtein)
-                self.id_ = UUID().uuidString
-            }
+                await MainActor.run {
+                    self.carbs = min(self.carbs, self.maxCarbs)
+                    self.fat = min(self.fat, self.maxFat)
+                    self.protein = min(self.protein, self.maxProtein)
+                    self.id_ = UUID().uuidString
+                }
 
 
-            let carbsToStore = [CarbsEntry(
-                id: id_,
-                createdAt: now,
-                actualDate: date,
-                carbs: carbs,
-                fat: fat,
-                protein: protein,
-                note: note,
-                enteredBy: CarbsEntry.local,
-                isFPU: false,
-                fpuID: fat > 0 || protein > 0 ? UUID().uuidString : nil
-            )]
-            await carbsStorage.storeCarbs(carbsToStore, areFetchedFromRemote: false)
-
-            if carbs > 0 || fat > 0 || protein > 0 {
-                // only perform determine basal sync if the user doesn't use the pump bolus, otherwise the enact bolus func in the APSManger does a sync
-                if amount <= 0 {
-                    await apsManager.determineBasalSync()
+                let carbsToStore = [CarbsEntry(
+                    id: id_,
+                    createdAt: now,
+                    actualDate: date,
+                    carbs: carbs,
+                    fat: fat,
+                    protein: protein,
+                    note: note,
+                    enteredBy: CarbsEntry.local,
+                    isFPU: false,
+                    fpuID: fat > 0 || protein > 0 ? UUID().uuidString : nil
+                )]
+
+                try await carbsStorage.storeCarbs(carbsToStore, areFetchedFromRemote: false)
+
+                if carbs > 0 || fat > 0 || protein > 0 {
+                    // only perform determine basal sync if the user doesn't use the pump bolus, otherwise the enact bolus func in the APSManger does a sync
+                    if amount <= 0 {
+                        await apsManager.determineBasalSync()
+                    }
                 }
                 }
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) Failed to save meal with error: \(error.localizedDescription)")
             }
             }
         }
         }
 
 
@@ -673,14 +685,22 @@ extension Treatments.StateModel {
     // Glucose
     // Glucose
     private func setupGlucoseArray() {
     private func setupGlucoseArray() {
         Task {
         Task {
-            let ids = await self.fetchGlucose()
-            let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(with: ids, context: viewContext)
-            await updateGlucoseArray(with: glucoseObjects)
+            do {
+                let ids = try await self.fetchGlucose()
+                let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: ids, context: viewContext)
+                await updateGlucoseArray(with: glucoseObjects)
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Error setting up glucose array: \(error.localizedDescription)"
+                )
+            }
         }
         }
     }
     }
 
 
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: glucoseFetchContext,
             onContext: glucoseFetchContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,
@@ -708,71 +728,83 @@ extension Treatments.StateModel {
 
 
     // Determinations
     // Determinations
     private func setupDeterminationsArray() async {
     private func setupDeterminationsArray() async {
-        // Fetch object IDs on a background thread
-        let fetchedObjectIDs = await determinationStorage.fetchLastDeterminationObjectID(
-            predicate: NSPredicate.predicateFor30MinAgoForDetermination
-        )
+        do {
+            let fetchedObjectIDs = try await determinationStorage.fetchLastDeterminationObjectID(
+                predicate: NSPredicate.predicateFor30MinAgoForDetermination
+            )
 
 
-        // Update determinationObjectIDs on the main thread
-        await MainActor.run {
-            determinationObjectIDs = fetchedObjectIDs
-        }
+            await MainActor.run {
+                determinationObjectIDs = fetchedObjectIDs
+            }
 
 
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
-            .getNSManagedObject(with: determinationObjectIDs, context: viewContext)
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
+                .getNSManagedObject(with: determinationObjectIDs, context: viewContext)
 
 
-        updateDeterminationsArray(with: determinationObjects)
+            updateDeterminationsArray(with: determinationObjects)
+        } catch let error as CoreDataError {
+            debug(.default, "Core Data error: \(error.localizedDescription)")
+        } catch {
+            debug(.default, "Unexpected error: \(error.localizedDescription)")
+        }
     }
     }
 
 
     private func mapForecastsForChart() async -> Determination? {
     private func mapForecastsForChart() async -> Determination? {
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
-            .getNSManagedObject(with: determinationObjectIDs, context: determinationFetchContext)
-
-        return await determinationFetchContext.perform {
-            guard let determinationObject = determinationObjects.first else {
-                return nil
-            }
+        do {
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
+                .getNSManagedObject(with: determinationObjectIDs, context: determinationFetchContext)
 
 
-            let eventualBG = determinationObject.eventualBG?.intValue
-
-            let forecastsSet = determinationObject.forecasts ?? []
-            let predictions = Predictions(
-                iob: forecastsSet.extractValues(for: "iob"),
-                zt: forecastsSet.extractValues(for: "zt"),
-                cob: forecastsSet.extractValues(for: "cob"),
-                uam: forecastsSet.extractValues(for: "uam")
-            )
+            return await determinationFetchContext.perform {
+                guard let determinationObject = determinationObjects.first else {
+                    return nil
+                }
 
 
-            return Determination(
-                id: UUID(),
-                reason: "",
-                units: 0,
-                insulinReq: 0,
-                eventualBG: eventualBG,
-                sensitivityRatio: 0,
-                rate: 0,
-                duration: 0,
-                iob: 0,
-                cob: 0,
-                predictions: predictions.isEmpty ? nil : predictions,
-                carbsReq: 0,
-                temp: nil,
-                bg: 0,
-                reservoir: 0,
-                isf: 0,
-                tdd: 0,
-                insulin: nil,
-                current_target: 0,
-                insulinForManualBolus: 0,
-                manualBolusErrorString: 0,
-                minDelta: 0,
-                expectedDelta: 0,
-                minGuardBG: 0,
-                minPredBG: 0,
-                threshold: 0,
-                carbRatio: 0,
-                received: false
+                let eventualBG = determinationObject.eventualBG?.intValue
+
+                let forecastsSet = determinationObject.forecasts ?? []
+                let predictions = Predictions(
+                    iob: forecastsSet.extractValues(for: "iob"),
+                    zt: forecastsSet.extractValues(for: "zt"),
+                    cob: forecastsSet.extractValues(for: "cob"),
+                    uam: forecastsSet.extractValues(for: "uam")
+                )
+
+                return Determination(
+                    id: UUID(),
+                    reason: "",
+                    units: 0,
+                    insulinReq: 0,
+                    eventualBG: eventualBG,
+                    sensitivityRatio: 0,
+                    rate: 0,
+                    duration: 0,
+                    iob: 0,
+                    cob: 0,
+                    predictions: predictions.isEmpty ? nil : predictions,
+                    carbsReq: 0,
+                    temp: nil,
+                    bg: 0,
+                    reservoir: 0,
+                    isf: 0,
+                    tdd: 0,
+                    insulin: nil,
+                    current_target: 0,
+                    insulinForManualBolus: 0,
+                    manualBolusErrorString: 0,
+                    minDelta: 0,
+                    expectedDelta: 0,
+                    minGuardBG: 0,
+                    minPredBG: 0,
+                    threshold: 0,
+                    carbRatio: 0,
+                    received: false
+                )
+            }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Error mapping forecasts for chart: \(error.localizedDescription)"
             )
             )
+            return nil
         }
         }
     }
     }
 
 

+ 98 - 65
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -191,8 +191,8 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
 
 
     /// Fetches recent glucose readings from CoreData
     /// Fetches recent glucose readings from CoreData
     /// - Returns: Array of NSManagedObjectIDs for glucose readings
     /// - Returns: Array of NSManagedObjectIDs for glucose readings
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: glucoseFetchContext,
             onContext: glucoseFetchContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,
@@ -266,64 +266,73 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         carbs: Decimal,
         carbs: Decimal,
         useFattyMealCorrection: Bool,
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool
         useSuperBolus: Bool
-    ) async -> CalculationInput {
-        // Get settings
-        let settings = await getSettings()
-
-        // Get max bolus
-        let maxBolus = await getPumpSettings().maxBolus
-
-        // Get current profile values
-        let currentBasal = await getCurrentSettingValue(for: .basal)
-        let currentCarbRatio = await getCurrentSettingValue(for: .carbRatio)
-        let currentBGTarget = await getCurrentSettingValue(for: .bgTarget)
-        let currentISF = await getCurrentSettingValue(for: .isf)
-
-        // Fetch glucose data
-        let glucoseIds = await fetchGlucose()
-        let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(
-            with: glucoseIds,
-            context: glucoseFetchContext
-        )
-        let glucoseVars = await glucoseFetchContext.perform {
-            self.updateGlucoseVariables(with: glucoseObjects)
-        }
+    ) async throws -> CalculationInput {
+        do {
+            // Get settings
+            let settings = await getSettings()
+
+            // Get max bolus
+            let maxBolus = await getPumpSettings().maxBolus
+
+            // Get current profile values
+            let currentBasal = await getCurrentSettingValue(for: .basal)
+            let currentCarbRatio = await getCurrentSettingValue(for: .carbRatio)
+            let currentBGTarget = await getCurrentSettingValue(for: .bgTarget)
+            let currentISF = await getCurrentSettingValue(for: .isf)
+
+            // Fetch glucose data
+            let glucoseIds = try await fetchGlucose()
+            let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared.getNSManagedObject(
+                with: glucoseIds,
+                context: glucoseFetchContext
+            )
+            let glucoseVars = await glucoseFetchContext.perform {
+                self.updateGlucoseVariables(with: glucoseObjects)
+            }
 
 
-        // Fetch determination data
-        let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
-            predicate: NSPredicate.predicateFor30MinAgoForDetermination
-        )
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared.getNSManagedObject(
-            with: determinationIds,
-            context: determinationFetchContext
-        )
-        let bolusVars = await determinationFetchContext.perform {
-            self.updateBolusCalculatorVariables(
-                with: determinationObjects,
-                currentBGTarget: currentBGTarget,
-                currentISF: currentISF,
-                currentCarbRatio: currentCarbRatio,
-                currentBasal: currentBasal
+            // Fetch determination data
+            let determinationIds = try await determinationStorage.fetchLastDeterminationObjectID(
+                predicate: NSPredicate.predicateFor30MinAgoForDetermination
             )
             )
-        }
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared.getNSManagedObject(
+                with: determinationIds,
+                context: determinationFetchContext
+            )
+            let bolusVars = await determinationFetchContext.perform {
+                self.updateBolusCalculatorVariables(
+                    with: determinationObjects,
+                    currentBGTarget: currentBGTarget,
+                    currentISF: currentISF,
+                    currentCarbRatio: currentCarbRatio,
+                    currentBasal: currentBasal
+                )
+            }
 
 
-        return CalculationInput(
-            carbs: carbs,
-            currentBG: glucoseVars.currentBG,
-            deltaBG: glucoseVars.deltaBG,
-            target: bolusVars.target,
-            isf: bolusVars.isf,
-            carbRatio: bolusVars.carbRatio,
-            iob: bolusVars.iob,
-            cob: bolusVars.cob,
-            useFattyMealCorrectionFactor: useFattyMealCorrection,
-            fattyMealFactor: settings.fattyMealFactor,
-            useSuperBolus: useSuperBolus,
-            sweetMealFactor: settings.sweetMealFactor,
-            basal: bolusVars.basal,
-            fraction: settings.fraction,
-            maxBolus: maxBolus
-        )
+            return CalculationInput(
+                carbs: carbs,
+                currentBG: glucoseVars.currentBG,
+                deltaBG: glucoseVars.deltaBG,
+                target: bolusVars.target,
+                isf: bolusVars.isf,
+                carbRatio: bolusVars.carbRatio,
+                iob: bolusVars.iob,
+                cob: bolusVars.cob,
+                useFattyMealCorrectionFactor: useFattyMealCorrection,
+                fattyMealFactor: settings.fattyMealFactor,
+                useSuperBolus: useSuperBolus,
+                sweetMealFactor: settings.sweetMealFactor,
+                basal: bolusVars.basal,
+                fraction: settings.fraction,
+                maxBolus: maxBolus
+            )
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Error preparing calculation input: \(error.localizedDescription)"
+            )
+            // Return default values in case of error
+            throw error
+        }
     }
     }
 
 
     /// Calculates the recommended insulin dose based on various parameters
     /// Calculates the recommended insulin dose based on various parameters
@@ -401,14 +410,38 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     ///   - useFattyMealCorrection: Whether to apply fatty meal correction
     ///   - useFattyMealCorrection: Whether to apply fatty meal correction
     ///   - useSuperBolus: Whether to use super bolus calculation
     ///   - useSuperBolus: Whether to use super bolus calculation
     /// - Returns: CalculationResult containing the calculated insulin dose and details
     /// - Returns: CalculationResult containing the calculated insulin dose and details
-    func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool) async -> CalculationResult {
-        let input = await prepareCalculationInput(
-            carbs: carbs,
-            useFattyMealCorrection: useFattyMealCorrection,
-            useSuperBolus: useSuperBolus
-        )
-        let result = await calculateInsulin(input: input)
-        return result
+    func handleBolusCalculation(
+        carbs: Decimal,
+        useFattyMealCorrection: Bool,
+        useSuperBolus: Bool
+    ) async -> CalculationResult {
+        do {
+            let input = try await prepareCalculationInput(
+                carbs: carbs,
+                useFattyMealCorrection: useFattyMealCorrection,
+                useSuperBolus: useSuperBolus
+            )
+            let result = await calculateInsulin(input: input)
+            return result
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Error in bolus calculation: \(error.localizedDescription)"
+            )
+            // Return safe default values
+            return CalculationResult(
+                insulinCalculated: 0,
+                wholeCalc: 0,
+                correctionInsulin: 0,
+                iobInsulinReduction: 0,
+                superBolusInsulin: 0,
+                targetDifference: 0,
+                targetDifferenceInsulin: 0,
+                fifteenMinutesInsulin: 0,
+                wholeCob: 0,
+                wholeCobInsulin: 0
+            )
+        }
     }
     }
 }
 }
 
 

+ 13 - 13
Trio/Sources/Services/Calendar/CalendarManager.swift

@@ -175,8 +175,8 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         EKEventStore().calendars(for: .event).map(\.title)
         EKEventStore().calendars(for: .event).map(\.title)
     }
     }
 
 
-    private func getLastDetermination() async -> NSManagedObjectID? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func getLastDetermination() async throws -> NSManagedObjectID? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
@@ -193,8 +193,8 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         }
         }
     }
     }
 
 
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.predicateFor30MinAgo,
             predicate: NSPredicate.predicateFor30MinAgo,
@@ -211,19 +211,19 @@ final class BaseCalendarManager: CalendarManager, Injectable {
     }
     }
 
 
     @MainActor func createEvent() async {
     @MainActor func createEvent() async {
-        guard settingsManager.settings.useCalendar, let calendar = currentCalendar,
-              let determinationId = await getLastDetermination() else { return }
+        do {
+            guard settingsManager.settings.useCalendar, let calendar = currentCalendar,
+                  let determinationId = try await getLastDetermination() else { return }
 
 
-        // Ignore the update if the determinationId is the same as it was at last update
-        if determinationId == previousDeterminationId {
-            return
-        }
+            // Ignore the update if the determinationId is the same as it was at last update
+            if determinationId == previousDeterminationId {
+                return
+            }
 
 
-        let glucoseIds = await fetchGlucose()
+            let glucoseIds = try await fetchGlucose()
 
 
-        deleteAllEvents(in: calendar)
+            deleteAllEvents(in: calendar)
 
 
-        do {
             guard let determinationObject = try viewContext.existingObject(with: determinationId) as? OrefDetermination
             guard let determinationObject = try viewContext.existingObject(with: determinationId) as? OrefDetermination
             else { return }
             else { return }
 
 

+ 69 - 64
Trio/Sources/Services/ContactImage/ContactImageManager.swift

@@ -88,8 +88,8 @@ final class BaseContactImageManager: NSObject, ContactImageManager, Injectable {
 
 
     // MARK: - Core Data Fetches
     // MARK: - Core Data Fetches
 
 
-    private func fetchlastDetermination() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchlastDetermination() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate(format: "deliverAt >= %@", Date.halfHourAgo as NSDate), // fetches enacted and suggested
             predicate: NSPredicate(format: "deliverAt >= %@", Date.halfHourAgo as NSDate), // fetches enacted and suggested
@@ -105,8 +105,8 @@ final class BaseContactImageManager: NSObject, ContactImageManager, Injectable {
         }
         }
     }
     }
 
 
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.predicateFor20MinAgo,
             predicate: NSPredicate.predicateFor20MinAgo,
@@ -180,72 +180,77 @@ final class BaseContactImageManager: NSObject, ContactImageManager, Injectable {
     /// and updates the `state` object, which represents the current contact trick state.
     /// and updates the `state` object, which represents the current contact trick state.
     /// - Important: This function must be called on the main actor to ensure thread safety. Otherwise, we would need to ensure thread safety by either using an actor or a perform closure
     /// - Important: This function must be called on the main actor to ensure thread safety. Otherwise, we would need to ensure thread safety by either using an actor or a perform closure
     @MainActor func updateContactImageState() async {
     @MainActor func updateContactImageState() async {
-        // Get NSManagedObjectIDs on backgroundContext
-        let glucoseValuesIds = await fetchGlucose()
-        let determinationIds = await fetchlastDetermination()
-
-        // Get NSManagedObjects on MainActor
-        let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: glucoseValuesIds, context: viewContext)
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
-            .getNSManagedObject(with: determinationIds, context: viewContext)
-        let lastDetermination = determinationObjects.last
-
-        if let firstGlucoseValue = glucoseObjects.first {
-            let value = settingsManager.settings.units == .mgdL
-                ? Decimal(firstGlucoseValue.glucose)
-                : Decimal(firstGlucoseValue.glucose).asMmolL
-
-            state.glucose = Formatter.glucoseFormatter(for: units).string(from: value as NSNumber)
-            state.trend = firstGlucoseValue.directionEnum?.symbol
-
-            let delta = glucoseObjects.count >= 2
-                ? Decimal(firstGlucoseValue.glucose) - Decimal(glucoseObjects.dropFirst().first?.glucose ?? 0)
-                : 0
-            let deltaConverted = settingsManager.settings.units == .mgdL ? delta : delta.asMmolL
-            state.delta = deltaFormatter.string(from: deltaConverted as NSNumber)
-        }
+        do {
+            // Get NSManagedObjectIDs on backgroundContext
+            let glucoseValuesIds = try await fetchGlucose()
+            let determinationIds = try await fetchlastDetermination()
+
+            // Get NSManagedObjects on MainActor
+            let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared
+                .getNSManagedObject(with: glucoseValuesIds, context: viewContext)
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
+                .getNSManagedObject(with: determinationIds, context: viewContext)
+            let lastDetermination = determinationObjects.last
+
+            if let firstGlucoseValue = glucoseObjects.first {
+                let value = settingsManager.settings.units == .mgdL
+                    ? Decimal(firstGlucoseValue.glucose)
+                    : Decimal(firstGlucoseValue.glucose).asMmolL
+
+                state.glucose = Formatter.glucoseFormatter(for: units).string(from: value as NSNumber)
+                state.trend = firstGlucoseValue.directionEnum?.symbol
+
+                let delta = glucoseObjects.count >= 2
+                    ? Decimal(firstGlucoseValue.glucose) - Decimal(glucoseObjects.dropFirst().first?.glucose ?? 0)
+                    : 0
+                let deltaConverted = settingsManager.settings.units == .mgdL ? delta : delta.asMmolL
+                state.delta = deltaFormatter.string(from: deltaConverted as NSNumber)
+            }
 
 
-        state.lastLoopDate = lastDetermination?.timestamp
+            state.lastLoopDate = lastDetermination?.timestamp
 
 
-        let iobValue = lastDetermination?.iob as? Decimal ?? 0.0
-        state.iob = iobValue
-        state.iobText = Formatter.decimalFormatterWithOneFractionDigit.string(from: iobValue as NSNumber)
+            let iobValue = lastDetermination?.iob as? Decimal ?? 0.0
+            state.iob = iobValue
+            state.iobText = Formatter.decimalFormatterWithOneFractionDigit.string(from: iobValue as NSNumber)
 
 
-        // we need to do it complex and unelegant, otherwise unwrapping and parsing of cob results in 0
-        if let cobValue = lastDetermination?.cob {
-            state.cob = Decimal(cobValue)
-            state.cobText = Formatter.integerFormatter.string(from: Int(cobValue) as NSNumber)
+            // we need to do it complex and unelegant, otherwise unwrapping and parsing of cob results in 0
+            if let cobValue = lastDetermination?.cob {
+                state.cob = Decimal(cobValue)
+                state.cobText = Formatter.integerFormatter.string(from: Int(cobValue) as NSNumber)
 
 
-        } else {
-            state.cob = 0
-            state.cobText = "0"
-        }
+            } else {
+                state.cob = 0
+                state.cobText = "0"
+            }
 
 
-        if let eventualBG = settingsManager.settings.units == .mgdL ? lastDetermination?
-            .eventualBG : lastDetermination?
-            .eventualBG?.decimalValue.asMmolL as NSDecimalNumber?
-        {
-            let eventualBGAsString = Formatter.decimalFormatterWithOneFractionDigit.string(from: eventualBG)
-            state.eventualBG = eventualBGAsString.map { "⇢ " + $0 }
-        }
+            if let eventualBG = settingsManager.settings.units == .mgdL ? lastDetermination?
+                .eventualBG : lastDetermination?
+                .eventualBG?.decimalValue.asMmolL as NSDecimalNumber?
+            {
+                let eventualBGAsString = Formatter.decimalFormatterWithOneFractionDigit.string(from: eventualBG)
+                state.eventualBG = eventualBGAsString.map { "⇢ " + $0 }
+            }
 
 
-        // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
-        let hardCodedLow = Decimal(55)
-        let hardCodedHigh = Decimal(220)
-        let isDynamicColorScheme = settingsManager.settings.glucoseColorScheme == .dynamicColor
-        let highGlucoseColorValue = isDynamicColorScheme ? hardCodedHigh : settingsManager.settings.highGlucose
-        let lowGlucoseColorValue = isDynamicColorScheme ? hardCodedLow : settingsManager.settings.lowGlucose
-
-        state.highGlucoseColorValue = units == .mgdL ? highGlucoseColorValue : highGlucoseColorValue.asMmolL
-        state.lowGlucoseColorValue = units == .mgdL ? lowGlucoseColorValue : lowGlucoseColorValue.asMmolL
-        state
-            .targetGlucose = await getCurrentGlucoseTarget() ??
-            (settingsManager.settings.units == .mgdL ? Decimal(100) : 100.asMmolL)
-        state.glucoseColorScheme = settingsManager.settings.glucoseColorScheme
-
-        // Notify delegate about state update on main thread
-        await MainActor.run {
+            // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+            let hardCodedLow = Decimal(55)
+            let hardCodedHigh = Decimal(220)
+            let isDynamicColorScheme = settingsManager.settings.glucoseColorScheme == .dynamicColor
+            let highGlucoseColorValue = isDynamicColorScheme ? hardCodedHigh : settingsManager.settings.highGlucose
+            let lowGlucoseColorValue = isDynamicColorScheme ? hardCodedLow : settingsManager.settings.lowGlucose
+
+            state.highGlucoseColorValue = units == .mgdL ? highGlucoseColorValue : highGlucoseColorValue.asMmolL
+            state.lowGlucoseColorValue = units == .mgdL ? lowGlucoseColorValue : lowGlucoseColorValue.asMmolL
+            state
+                .targetGlucose = await getCurrentGlucoseTarget() ??
+                (settingsManager.settings.units == .mgdL ? Decimal(100) : 100.asMmolL)
+            state.glucoseColorScheme = settingsManager.settings.glucoseColorScheme
+
+            // Notify delegate about state update on main thread
+            await MainActor.run {
+                delegate?.contactImageManagerDidUpdateState(state)
+            }
+        } catch {
+            // Still notify delegate with current state, even if there was an error
             delegate?.contactImageManagerDidUpdateState(state)
             delegate?.contactImageManagerDidUpdateState(state)
         }
         }
     }
     }

+ 103 - 74
Trio/Sources/Services/HealthKit/HealthKitManager.swift

@@ -156,8 +156,18 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     // Glucose Upload
     // Glucose Upload
 
 
     func uploadGlucose() async {
     func uploadGlucose() async {
-        await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToHealth())
-        await uploadGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToHealth())
+        do {
+            let glucose = try await glucoseStorage.getGlucoseNotYetUploadedToHealth()
+            await uploadGlucose(glucose)
+
+            let manualGlucose = try await glucoseStorage.getManualGlucoseNotYetUploadedToHealth()
+            await uploadGlucose(manualGlucose)
+        } catch {
+            debug(
+                .service,
+                "\(DebuggingIdentifiers.failed) Error fetching glucose for health upload: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadGlucose(_ glucose: [BloodGlucose]) async {
     func uploadGlucose(_ glucose: [BloodGlucose]) async {
@@ -228,7 +238,15 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     // Carbs Upload
     // Carbs Upload
 
 
     func uploadCarbs() async {
     func uploadCarbs() async {
-        await uploadCarbs(carbsStorage.getCarbsNotYetUploadedToHealth())
+        do {
+            let carbs = try await carbsStorage.getCarbsNotYetUploadedToHealth()
+            await uploadCarbs(carbs)
+        } catch {
+            debug(
+                .service,
+                "\(DebuggingIdentifiers.failed) Error fetching carbs for health upload: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadCarbs(_ carbs: [CarbsEntry]) async {
     func uploadCarbs(_ carbs: [CarbsEntry]) async {
@@ -341,7 +359,15 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     // Insulin Upload
     // Insulin Upload
 
 
     func uploadInsulin() async {
     func uploadInsulin() async {
-        await uploadInsulin(pumpHistoryStorage.getPumpHistoryNotYetUploadedToHealth())
+        do {
+            let events = try await pumpHistoryStorage.getPumpHistoryNotYetUploadedToHealth()
+            await uploadInsulin(events)
+        } catch {
+            debug(
+                .service,
+                "\(DebuggingIdentifiers.failed) Error fetching insulin events for health upload: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadInsulin(_ insulinEvents: [PumpHistoryEvent]) async {
     func uploadInsulin(_ insulinEvents: [PumpHistoryEvent]) async {
@@ -350,87 +376,90 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
               checkWriteToHealthPermissions(objectTypeToHealthStore: sampleType),
               checkWriteToHealthPermissions(objectTypeToHealthStore: sampleType),
               insulinEvents.isNotEmpty else { return }
               insulinEvents.isNotEmpty else { return }
 
 
-        // Fetch existing temp basal entries from Core Data for the last 24 hours
-        let fetchedInsulinEntries = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: PumpEventStored.self,
-            onContext: backgroundContext,
-            predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [
-                NSPredicate.pumpHistoryLast24h,
-                NSPredicate(format: "tempBasal != nil")
-            ]),
-            key: "timestamp",
-            ascending: true,
-            batchSize: 50
-        )
-
-        var insulinSamples: [HKQuantitySample] = []
+        do {
+            let fetchedInsulinEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
+                ofType: PumpEventStored.self,
+                onContext: backgroundContext,
+                predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [
+                    NSPredicate.pumpHistoryLast24h,
+                    NSPredicate(format: "tempBasal != nil")
+                ]),
+                key: "timestamp",
+                ascending: true,
+                batchSize: 50
+            )
 
 
-        await backgroundContext.perform {
-            guard let existingTempBasalEntries = fetchedInsulinEntries as? [PumpEventStored] else { return }
-
-            for event in insulinEvents {
-                switch event.type {
-                case .bolus:
-                    // For bolus events, create a HealthKit sample directly
-                    if let sample = self.createSample(for: event, sampleType: sampleType) {
-                        debug(.service, "Created HealthKit sample for bolus entry: \(sample)")
-                        insulinSamples.append(sample)
-                    }
-                case .tempBasal:
-                    // For temp basal events, process them and adjust overlapping durations if necessary
-                    guard let duration = event.duration, let amount = event.amount else { continue }
-
-                    let value = (Decimal(duration) / 60.0) * amount
-                    let valueRounded = self.deviceDataManager?.pumpManager?
-                        .roundToSupportedBolusVolume(units: Double(value)) ?? Double(value)
-
-                    // Use binary search for efficient lookup of matching entry
-                    if let matchingIndex = self.binarySearch(entries: existingTempBasalEntries, timestamp: event.timestamp) {
-                        let predecessorIndex = matchingIndex - 1
-
-                        if predecessorIndex >= 0 {
-                            let predecessorEntry = existingTempBasalEntries[predecessorIndex]
-
-                            if let adjustedSample = self.processPredecessorEntry(
-                                predecessorEntry,
-                                nextEventTimestamp: event.timestamp,
-                                sampleType: sampleType
-                            ) {
-                                insulinSamples.append(adjustedSample)
-                            }
-                        }
+            var insulinSamples: [HKQuantitySample] = []
 
 
-                        let newEvent = PumpHistoryEvent(
-                            id: event.id,
-                            type: .tempBasal,
-                            timestamp: event.timestamp,
-                            amount: Decimal(valueRounded),
-                            duration: event.duration
-                        )
+            await backgroundContext.perform {
+                guard let existingTempBasalEntries = fetchedInsulinEntries as? [PumpEventStored] else { return }
 
 
-                        if let sample = self.createSample(for: newEvent, sampleType: sampleType) {
-                            debug(.service, "Created HealthKit sample for initial temp basal entry: \(sample)")
+                for event in insulinEvents {
+                    switch event.type {
+                    case .bolus:
+                        // For bolus events, create a HealthKit sample directly
+                        if let sample = self.createSample(for: event, sampleType: sampleType) {
+                            debug(.service, "Created HealthKit sample for bolus entry: \(sample)")
                             insulinSamples.append(sample)
                             insulinSamples.append(sample)
                         }
                         }
-                    }
+                    case .tempBasal:
+                        // For temp basal events, process them and adjust overlapping durations if necessary
+                        guard let duration = event.duration, let amount = event.amount else { continue }
+
+                        let value = (Decimal(duration) / 60.0) * amount
+                        let valueRounded = self.deviceDataManager?.pumpManager?
+                            .roundToSupportedBolusVolume(units: Double(value)) ?? Double(value)
+
+                        // Use binary search for efficient lookup of matching entry
+                        if let matchingIndex = self.binarySearch(entries: existingTempBasalEntries, timestamp: event.timestamp) {
+                            let predecessorIndex = matchingIndex - 1
+
+                            if predecessorIndex >= 0 {
+                                let predecessorEntry = existingTempBasalEntries[predecessorIndex]
+
+                                if let adjustedSample = self.processPredecessorEntry(
+                                    predecessorEntry,
+                                    nextEventTimestamp: event.timestamp,
+                                    sampleType: sampleType
+                                ) {
+                                    insulinSamples.append(adjustedSample)
+                                }
+                            }
+
+                            let newEvent = PumpHistoryEvent(
+                                id: event.id,
+                                type: .tempBasal,
+                                timestamp: event.timestamp,
+                                amount: Decimal(valueRounded),
+                                duration: event.duration
+                            )
+
+                            if let sample = self.createSample(for: newEvent, sampleType: sampleType) {
+                                debug(.service, "Created HealthKit sample for initial temp basal entry: \(sample)")
+                                insulinSamples.append(sample)
+                            }
+                        }
 
 
-                default:
-                    break
+                    default:
+                        break
+                    }
                 }
                 }
             }
             }
-        }
 
 
-        do {
-            guard insulinSamples.isNotEmpty else {
-                debug(.service, "No insulin samples available for upload.")
-                return
-            }
+            do {
+                guard insulinSamples.isNotEmpty else {
+                    debug(.service, "No insulin samples available for upload.")
+                    return
+                }
 
 
-            try await healthKitStore.save(insulinSamples)
-            debug(.service, "Successfully stored \(insulinSamples.count) insulin samples in HealthKit.")
-            await updateInsulinAsUploaded(insulinEvents)
+                try await healthKitStore.save(insulinSamples)
+                debug(.service, "Successfully stored \(insulinSamples.count) insulin samples in HealthKit.")
+                await updateInsulinAsUploaded(insulinEvents)
+            } catch {
+                debug(.service, "Failed to upload insulin samples to HealthKit: \(error.localizedDescription)")
+            }
         } catch {
         } catch {
-            debug(.service, "Failed to upload insulin samples to HealthKit: \(error.localizedDescription)")
+            debug(.service, "\(DebuggingIdentifiers.failed) Error fetching temp basal entries: \(error.localizedDescription)")
         }
         }
     }
     }
 
 

+ 6 - 6
Trio/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -4,8 +4,8 @@ import Foundation
 
 
 @available(iOS 16.2, *)
 @available(iOS 16.2, *)
 extension LiveActivityBridge {
 extension LiveActivityBridge {
-    func fetchAndMapGlucose() async -> [GlucoseData] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchAndMapGlucose() async throws -> [GlucoseData] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateForSixHoursAgo,
             predicate: NSPredicate.predicateForSixHoursAgo,
@@ -25,8 +25,8 @@ extension LiveActivityBridge {
         }
         }
     }
     }
 
 
-    func fetchAndMapDetermination() async -> DeterminationData? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchAndMapDetermination() async throws -> DeterminationData? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
@@ -53,8 +53,8 @@ extension LiveActivityBridge {
         }
         }
     }
     }
 
 
-    func fetchAndMapOverride() async -> OverrideData? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchAndMapOverride() async throws -> OverrideData? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             ofType: OverrideStored.self,
             onContext: context,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgo,
             predicate: NSPredicate.predicateForOneDayAgo,

+ 3 - 3
Trio/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -135,7 +135,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
 
 
     private func cobOrIobDidUpdate() {
     private func cobOrIobDidUpdate() {
         Task { @MainActor in
         Task { @MainActor in
-            self.determination = await fetchAndMapDetermination()
+            self.determination = try await fetchAndMapDetermination()
             if let determination = determination {
             if let determination = determination {
                 await self.updateContentState(determination)
                 await self.updateContentState(determination)
             }
             }
@@ -144,7 +144,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
 
 
     private func overridesDidUpdate() {
     private func overridesDidUpdate() {
         Task { @MainActor in
         Task { @MainActor in
-            self.override = await fetchAndMapOverride()
+            self.override = try await fetchAndMapOverride()
             if let determination = determination {
             if let determination = determination {
                 await self.updateContentState(determination)
                 await self.updateContentState(determination)
             }
             }
@@ -205,7 +205,7 @@ final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
 
 
     private func setupGlucoseArray() {
     private func setupGlucoseArray() {
         Task { @MainActor in
         Task { @MainActor in
-            self.glucoseFromPersistence = await fetchAndMapGlucose()
+            self.glucoseFromPersistence = try await fetchAndMapGlucose()
             glucoseDidUpdate(glucoseFromPersistence ?? [])
             glucoseDidUpdate(glucoseFromPersistence ?? [])
         }
         }
     }
     }

+ 64 - 18
Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift

@@ -12,14 +12,14 @@ protocol NightscoutManager: GlucoseSource {
     func deleteCarbs(withID id: String) async
     func deleteCarbs(withID id: String) async
     func deleteInsulin(withID id: String) async
     func deleteInsulin(withID id: String) async
     func deleteManualGlucose(withID id: String) async
     func deleteManualGlucose(withID id: String) async
-    func uploadDeviceStatus() async
+    func uploadDeviceStatus() async throws
     func uploadGlucose() async
     func uploadGlucose() async
     func uploadCarbs() async
     func uploadCarbs() async
     func uploadPumpHistory() async
     func uploadPumpHistory() async
     func uploadOverrides() async
     func uploadOverrides() async
     func uploadTempTargets() async
     func uploadTempTargets() async
     func uploadManualGlucose() async
     func uploadManualGlucose() async
-    func uploadProfiles() async
+    func uploadProfiles() async throws
     func uploadNoteTreatment(note: String) async
     func uploadNoteTreatment(note: String) async
     func importSettings() async -> ScheduledNightscoutProfile?
     func importSettings() async -> ScheduledNightscoutProfile?
     var cgmURL: URL? { get }
     var cgmURL: URL? { get }
@@ -107,7 +107,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             async let lastEnactedDeterminationID = determinationStorage
             async let lastEnactedDeterminationID = determinationStorage
                 .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
                 .fetchLastDeterminationObjectID(predicate: NSPredicate.enactedDetermination)
 
 
-            self.lastEnactedDetermination = await determinationStorage
+            self.lastEnactedDetermination = try await determinationStorage
                 .getOrefDeterminationNotYetUploadedToNightscout(lastEnactedDeterminationID)
                 .getOrefDeterminationNotYetUploadedToNightscout(lastEnactedDeterminationID)
         }
         }
     }
     }
@@ -246,7 +246,11 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .sink { [weak self] in
             .sink { [weak self] in
                 guard let self = self else { return }
                 guard let self = self else { return }
                 Task {
                 Task {
-                    await self.uploadDeviceStatus()
+                    do {
+                        try await self.uploadDeviceStatus()
+                    } catch {
+                        debug(.nightscout, "\(DebuggingIdentifiers.failed) failed to upload device status")
+                    }
                 }
                 }
             }
             }
             .store(in: &subscriptions)
             .store(in: &subscriptions)
@@ -504,7 +508,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     ///
     ///
     /// - Note: Ensure `nightscoutAPI` is initialized and `isUploadEnabled` is set to `true` before invoking this function.
     /// - Note: Ensure `nightscoutAPI` is initialized and `isUploadEnabled` is set to `true` before invoking this function.
     /// - Returns: Nothing.
     /// - Returns: Nothing.
-    func uploadDeviceStatus() async {
+    func uploadDeviceStatus() async throws {
         guard let nightscout = nightscoutAPI, isUploadEnabled else {
         guard let nightscout = nightscoutAPI, isUploadEnabled else {
             debug(.nightscout, "NS API not available or upload disabled. Aborting NS Status upload.")
             debug(.nightscout, "NS API not available or upload disabled. Aborting NS Status upload.")
             return
             return
@@ -522,7 +526,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         async let fetchedIOBEntry = storage.retrieveAsync(OpenAPS.Monitor.iob, as: [IOBEntry].self)
         async let fetchedIOBEntry = storage.retrieveAsync(OpenAPS.Monitor.iob, as: [IOBEntry].self)
         async let fetchedPumpStatus = storage.retrieveAsync(OpenAPS.Monitor.status, as: PumpStatus.self)
         async let fetchedPumpStatus = storage.retrieveAsync(OpenAPS.Monitor.status, as: PumpStatus.self)
 
 
-        var (fetchedEnactedDetermination, fetchedSuggestedDetermination) = await (
+        var (fetchedEnactedDetermination, fetchedSuggestedDetermination) = try await (
             determinationStorage.getOrefDeterminationNotYetUploadedToNightscout(enactedDeterminationID),
             determinationStorage.getOrefDeterminationNotYetUploadedToNightscout(enactedDeterminationID),
             determinationStorage.getOrefDeterminationNotYetUploadedToNightscout(suggestedDeterminationID)
             determinationStorage.getOrefDeterminationNotYetUploadedToNightscout(suggestedDeterminationID)
         )
         )
@@ -691,7 +695,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func uploadProfiles() async {
+    func uploadProfiles() async throws {
         if isUploadEnabled {
         if isUploadEnabled {
             do {
             do {
                 guard let sensitivities = await storage.retrieveAsync(
                 guard let sensitivities = await storage.retrieveAsync(
@@ -793,7 +797,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""
                 let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""
                 let deviceToken = UserDefaults.standard.string(forKey: "deviceToken") ?? ""
                 let deviceToken = UserDefaults.standard.string(forKey: "deviceToken") ?? ""
                 let isAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
                 let isAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
-                let presetOverrides = await overridesStorage.getPresetOverridesForNightscout()
+                let presetOverrides = try await overridesStorage.getPresetOverridesForNightscout()
                 let teamID = Bundle.main.object(forInfoDictionaryKey: "TeamID") as? String ?? ""
                 let teamID = Bundle.main.object(forInfoDictionaryKey: "TeamID") as? String ?? ""
 
 
                 let profileStore = NightscoutProfileStore(
                 let profileStore = NightscoutProfileStore(
@@ -845,31 +849,73 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
     }
 
 
     func uploadGlucose() async {
     func uploadGlucose() async {
-        await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToNightscout())
-        await uploadNonCoreDataTreatments(glucoseStorage.getCGMStateNotYetUploadedToNightscout())
+        do {
+            try await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToNightscout())
+            try await uploadNonCoreDataTreatments(glucoseStorage.getCGMStateNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload glucose with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadManualGlucose() async {
     func uploadManualGlucose() async {
-        await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToNightscout())
+        do {
+            try await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload manual glucose with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadPumpHistory() async {
     func uploadPumpHistory() async {
-        await uploadPumpHistory(pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout())
+        do {
+            try await uploadPumpHistory(pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload pump history with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadCarbs() async {
     func uploadCarbs() async {
-        await uploadCarbs(carbsStorage.getCarbsNotYetUploadedToNightscout())
-        await uploadCarbs(carbsStorage.getFPUsNotYetUploadedToNightscout())
+        do {
+            try await uploadCarbs(carbsStorage.getCarbsNotYetUploadedToNightscout())
+            try await uploadCarbs(carbsStorage.getFPUsNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload carbs with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadOverrides() async {
     func uploadOverrides() async {
-        await uploadOverrides(overridesStorage.getOverridesNotYetUploadedToNightscout())
-        await uploadOverrideRuns(overridesStorage.getOverrideRunsNotYetUploadedToNightscout())
+        do {
+            try await uploadOverrides(overridesStorage.getOverridesNotYetUploadedToNightscout())
+            try await uploadOverrideRuns(overridesStorage.getOverrideRunsNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload overrides with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     func uploadTempTargets() async {
     func uploadTempTargets() async {
-        await uploadTempTargets(await tempTargetsStorage.getTempTargetsNotYetUploadedToNightscout())
-        await uploadTempTargetRuns(await tempTargetsStorage.getTempTargetRunsNotYetUploadedToNightscout())
+        do {
+            try await uploadTempTargets(await tempTargetsStorage.getTempTargetsNotYetUploadedToNightscout())
+            try await uploadTempTargetRuns(await tempTargetsStorage.getTempTargetRunsNotYetUploadedToNightscout())
+        } catch {
+            debug(
+                .nightscout,
+                "\(DebuggingIdentifiers.failed) failed to upload temp targets with error: \(error.localizedDescription)"
+            )
+        }
     }
     }
 
 
     private func uploadGlucose(_ glucose: [BloodGlucose]) async {
     private func uploadGlucose(_ glucose: [BloodGlucose]) async {

+ 116 - 96
Trio/Sources/Services/Network/TidepoolManager.swift

@@ -187,7 +187,11 @@ extension BaseTidepoolManager: ServiceDelegate {
 /// Carb Upload and Deletion Functionality
 /// Carb Upload and Deletion Functionality
 extension BaseTidepoolManager {
 extension BaseTidepoolManager {
     func uploadCarbs() async {
     func uploadCarbs() async {
-        uploadCarbs(await carbsStorage.getCarbsNotYetUploadedToTidepool())
+        do {
+            try uploadCarbs(await carbsStorage.getCarbsNotYetUploadedToTidepool())
+        } catch {
+            debug(.service, "\(DebuggingIdentifiers.failed) Failed to upload carbs with error: \(error.localizedDescription)")
+        }
     }
     }
 
 
     func uploadCarbs(_ carbs: [CarbsEntry]) {
     func uploadCarbs(_ carbs: [CarbsEntry]) {
@@ -274,115 +278,127 @@ extension BaseTidepoolManager {
 /// Insulin Upload and Deletion Functionality
 /// Insulin Upload and Deletion Functionality
 extension BaseTidepoolManager {
 extension BaseTidepoolManager {
     func uploadInsulin() async {
     func uploadInsulin() async {
-        await uploadDose(await pumpHistoryStorage.getPumpHistoryNotYetUploadedToTidepool())
+        do {
+            let events = try await pumpHistoryStorage.getPumpHistoryNotYetUploadedToTidepool()
+            await uploadDose(events)
+        } catch {
+            debug(.service, "Error fetching pump history: \(error.localizedDescription)")
+        }
     }
     }
 
 
     func uploadDose(_ events: [PumpHistoryEvent]) async {
     func uploadDose(_ events: [PumpHistoryEvent]) async {
         guard !events.isEmpty, let tidepoolService = self.tidepoolService else { return }
         guard !events.isEmpty, let tidepoolService = self.tidepoolService else { return }
 
 
-        // Fetch all temp basal entries from Core Data for the last 24 hours
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: PumpEventStored.self,
-            onContext: backgroundContext,
-            predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [
-                NSPredicate.pumpHistoryLast24h,
-                NSPredicate(format: "tempBasal != nil")
-            ]),
-            key: "timestamp",
-            ascending: true,
-            batchSize: 50
-        )
-
-        // Ensure that the processing happens within the background context for thread safety
-        await backgroundContext.perform {
-            guard let existingTempBasalEntries = results as? [PumpEventStored] else { return }
-
-            let insulinDoseEvents: [DoseEntry] = events.reduce([]) { result, event in
-                var result = result
-                switch event.type {
-                case .tempBasal:
-                    result
-                        .append(contentsOf: self.processTempBasalEvent(event, existingTempBasalEntries: existingTempBasalEntries))
-                case .bolus:
-                    let bolusDoseEntry = DoseEntry(
-                        type: .bolus,
-                        startDate: event.timestamp,
-                        endDate: event.timestamp,
-                        value: Double(event.amount!),
-                        unit: .units,
-                        deliveredUnits: nil,
-                        syncIdentifier: event.id,
-                        scheduledBasalRate: nil,
-                        insulinType: self.apsManager.pumpManager?.status.insulinType ?? nil,
-                        automatic: event.isSMB ?? true,
-                        manuallyEntered: event.isExternal ?? false
-                    )
-                    result.append(bolusDoseEntry)
-                default:
-                    break
+        do {
+            // Fetch all temp basal entries from Core Data for the last 24 hours
+            let results = try await CoreDataStack.shared.fetchEntitiesAsync(
+                ofType: PumpEventStored.self,
+                onContext: backgroundContext,
+                predicate: NSCompoundPredicate(andPredicateWithSubpredicates: [
+                    NSPredicate.pumpHistoryLast24h,
+                    NSPredicate(format: "tempBasal != nil")
+                ]),
+                key: "timestamp",
+                ascending: true,
+                batchSize: 50
+            )
+
+            // Ensure that the processing happens within the background context for thread safety
+            await backgroundContext.perform {
+                guard let existingTempBasalEntries = results as? [PumpEventStored] else { return }
+
+                let insulinDoseEvents: [DoseEntry] = events.reduce([]) { result, event in
+                    var result = result
+                    switch event.type {
+                    case .tempBasal:
+                        result
+                            .append(
+                                contentsOf: self
+                                    .processTempBasalEvent(event, existingTempBasalEntries: existingTempBasalEntries)
+                            )
+                    case .bolus:
+                        let bolusDoseEntry = DoseEntry(
+                            type: .bolus,
+                            startDate: event.timestamp,
+                            endDate: event.timestamp,
+                            value: Double(event.amount!),
+                            unit: .units,
+                            deliveredUnits: nil,
+                            syncIdentifier: event.id,
+                            scheduledBasalRate: nil,
+                            insulinType: self.apsManager.pumpManager?.status.insulinType ?? nil,
+                            automatic: event.isSMB ?? true,
+                            manuallyEntered: event.isExternal ?? false
+                        )
+                        result.append(bolusDoseEntry)
+                    default:
+                        break
+                    }
+                    return result
                 }
                 }
-                return result
-            }
 
 
-            debug(.service, "TIDEPOOL DOSE ENTRIES: \(insulinDoseEvents)")
+                debug(.service, "TIDEPOOL DOSE ENTRIES: \(insulinDoseEvents)")
+
+                let pumpEvents: [PersistedPumpEvent] = events.compactMap { event -> PersistedPumpEvent? in
+                    if let pumpEventType = event.type.mapEventTypeToPumpEventType() {
+                        let dose: DoseEntry? = switch pumpEventType {
+                        case .suspend:
+                            DoseEntry(suspendDate: event.timestamp, automatic: true)
+                        case .resume:
+                            DoseEntry(resumeDate: event.timestamp, automatic: true)
+                        default:
+                            nil
+                        }
 
 
-            let pumpEvents: [PersistedPumpEvent] = events.compactMap { event -> PersistedPumpEvent? in
-                if let pumpEventType = event.type.mapEventTypeToPumpEventType() {
-                    let dose: DoseEntry? = switch pumpEventType {
-                    case .suspend:
-                        DoseEntry(suspendDate: event.timestamp, automatic: true)
-                    case .resume:
-                        DoseEntry(resumeDate: event.timestamp, automatic: true)
-                    default:
-                        nil
+                        return PersistedPumpEvent(
+                            date: event.timestamp,
+                            persistedDate: event.timestamp,
+                            dose: dose,
+                            isUploaded: true,
+                            objectIDURL: URL(string: "x-coredata:///PumpEvent/\(event.id)")!,
+                            raw: event.id.data(using: .utf8),
+                            title: event.note,
+                            type: pumpEventType
+                        )
+                    } else {
+                        return nil
                     }
                     }
-
-                    return PersistedPumpEvent(
-                        date: event.timestamp,
-                        persistedDate: event.timestamp,
-                        dose: dose,
-                        isUploaded: true,
-                        objectIDURL: URL(string: "x-coredata:///PumpEvent/\(event.id)")!,
-                        raw: event.id.data(using: .utf8),
-                        title: event.note,
-                        type: pumpEventType
-                    )
-                } else {
-                    return nil
                 }
                 }
-            }
 
 
-            self.processQueue.async {
-                tidepoolService.uploadDoseData(created: insulinDoseEvents, deleted: []) { result in
-                    switch result {
-                    case let .failure(error):
-                        debug(.nightscout, "Error synchronizing dose data with Tidepool: \(String(describing: error))")
-                    case .success:
-                        debug(.nightscout, "Success synchronizing dose data. Upload to Tidepool complete.")
-                        Task {
-                            let insulinEvents = events.filter {
-                                $0.type == .tempBasal || $0.type == .tempBasalDuration || $0.type == .bolus
+                self.processQueue.async {
+                    tidepoolService.uploadDoseData(created: insulinDoseEvents, deleted: []) { result in
+                        switch result {
+                        case let .failure(error):
+                            debug(.nightscout, "Error synchronizing dose data with Tidepool: \(String(describing: error))")
+                        case .success:
+                            debug(.nightscout, "Success synchronizing dose data. Upload to Tidepool complete.")
+                            Task {
+                                let insulinEvents = events.filter {
+                                    $0.type == .tempBasal || $0.type == .tempBasalDuration || $0.type == .bolus
+                                }
+                                await self.updateInsulinAsUploaded(insulinEvents)
                             }
                             }
-                            await self.updateInsulinAsUploaded(insulinEvents)
                         }
                         }
                     }
                     }
-                }
 
 
-                tidepoolService.uploadPumpEventData(pumpEvents) { result in
-                    switch result {
-                    case let .failure(error):
-                        debug(.nightscout, "Error synchronizing pump events data: \(String(describing: error))")
-                    case .success:
-                        debug(.nightscout, "Success synchronizing pump events data. Upload to Tidepool complete.")
-                        Task {
-                            let pumpEventType = events.map { $0.type.mapEventTypeToPumpEventType() }
-                            let pumpEvents = events.filter { _ in pumpEventType.contains(pumpEventType) }
-
-                            await self.updateInsulinAsUploaded(pumpEvents)
+                    tidepoolService.uploadPumpEventData(pumpEvents) { result in
+                        switch result {
+                        case let .failure(error):
+                            debug(.nightscout, "Error synchronizing pump events data: \(String(describing: error))")
+                        case .success:
+                            debug(.nightscout, "Success synchronizing pump events data. Upload to Tidepool complete.")
+                            Task {
+                                let pumpEventType = events.map { $0.type.mapEventTypeToPumpEventType() }
+                                let pumpEvents = events.filter { _ in pumpEventType.contains(pumpEventType) }
+
+                                await self.updateInsulinAsUploaded(pumpEvents)
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
             }
             }
+        } catch {
+            debug(.service, "Error fetching temp basal entries: \(error.localizedDescription)")
         }
         }
     }
     }
 
 
@@ -573,11 +589,15 @@ extension BaseTidepoolManager {
 /// Glucose Upload Functionality
 /// Glucose Upload Functionality
 extension BaseTidepoolManager {
 extension BaseTidepoolManager {
     func uploadGlucose() async {
     func uploadGlucose() async {
-        uploadGlucose(await glucoseStorage.getGlucoseNotYetUploadedToTidepool())
-        uploadGlucose(
-            await glucoseStorage
-                .getManualGlucoseNotYetUploadedToTidepool()
-        )
+        do {
+            let glucose = try await glucoseStorage.getGlucoseNotYetUploadedToTidepool()
+            uploadGlucose(glucose)
+
+            let manualGlucose = try await glucoseStorage.getManualGlucoseNotYetUploadedToTidepool()
+            uploadGlucose(manualGlucose)
+        } catch {
+            debug(.service, "Error fetching glucose data: \(error.localizedDescription)")
+        }
     }
     }
 
 
     func uploadGlucose(_ glucose: [StoredGlucoseSample]) {
     func uploadGlucose(_ glucose: [StoredGlucoseSample]) {

+ 2 - 2
Trio/Sources/Services/RemoteControl/TrioRemoteControl+APNS.swift

@@ -1,7 +1,7 @@
 import Foundation
 import Foundation
 
 
 extension TrioRemoteControl {
 extension TrioRemoteControl {
-    internal func handleAPNSChanges(deviceToken: String?) async {
+    internal func handleAPNSChanges(deviceToken: String?) async throws {
         let previousDeviceToken = UserDefaults.standard.string(forKey: "deviceToken")
         let previousDeviceToken = UserDefaults.standard.string(forKey: "deviceToken")
         let previousIsAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
         let previousIsAPNSProduction = UserDefaults.standard.bool(forKey: "isAPNSProduction")
 
 
@@ -21,7 +21,7 @@ extension TrioRemoteControl {
         }
         }
 
 
         if shouldUploadProfiles {
         if shouldUploadProfiles {
-            await nightscoutManager.uploadProfiles()
+            try await nightscoutManager.uploadProfiles()
         } else {
         } else {
             debug(.remoteControl, "No changes detected in device token or APNS environment.")
             debug(.remoteControl, "No changes detected in device token or APNS environment.")
         }
         }

+ 8 - 7
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift

@@ -1,7 +1,7 @@
 import Foundation
 import Foundation
 
 
 extension TrioRemoteControl {
 extension TrioRemoteControl {
-    internal func handleBolusCommand(_ pushMessage: PushMessage) async {
+    internal func handleBolusCommand(_ pushMessage: PushMessage) async throws {
         guard let bolusAmount = pushMessage.bolusAmount else {
         guard let bolusAmount = pushMessage.bolusAmount else {
             await logError("Command rejected: bolus amount is missing or invalid.", pushMessage: pushMessage)
             await logError("Command rejected: bolus amount is missing or invalid.", pushMessage: pushMessage)
             return
             return
@@ -18,7 +18,7 @@ extension TrioRemoteControl {
         }
         }
 
 
         let maxIOB = settings.preferences.maxIOB
         let maxIOB = settings.preferences.maxIOB
-        let currentIOB = await fetchCurrentIOB()
+        let currentIOB = try await fetchCurrentIOB()
         if (currentIOB + bolusAmount) > maxIOB {
         if (currentIOB + bolusAmount) > maxIOB {
             await logError(
             await logError(
                 "Command rejected: bolus amount (\(bolusAmount) units) would exceed max IOB (\(maxIOB) units). Current IOB: \(currentIOB) units.",
                 "Command rejected: bolus amount (\(bolusAmount) units) would exceed max IOB (\(maxIOB) units). Current IOB: \(currentIOB) units.",
@@ -27,7 +27,8 @@ extension TrioRemoteControl {
             return
             return
         }
         }
 
 
-        let totalRecentBolusAmount = await fetchTotalRecentBolusAmount(since: Date(timeIntervalSince1970: pushMessage.timestamp))
+        let totalRecentBolusAmount =
+            try await fetchTotalRecentBolusAmount(since: Date(timeIntervalSince1970: pushMessage.timestamp))
 
 
         if totalRecentBolusAmount >= bolusAmount * 0.2 {
         if totalRecentBolusAmount >= bolusAmount * 0.2 {
             await logError(
             await logError(
@@ -55,10 +56,10 @@ extension TrioRemoteControl {
         )
         )
     }
     }
 
 
-    private func fetchCurrentIOB() async -> Decimal {
+    private func fetchCurrentIOB() async throws -> Decimal {
         let predicate = NSPredicate.predicateFor30MinAgoForDetermination
         let predicate = NSPredicate.predicateFor30MinAgoForDetermination
 
 
-        let determinations = await CoreDataStack.shared.fetchEntitiesAsync(
+        let determinations = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: pumpHistoryFetchContext,
             onContext: pumpHistoryFetchContext,
             predicate: predicate,
             predicate: predicate,
@@ -79,14 +80,14 @@ extension TrioRemoteControl {
         return iob
         return iob
     }
     }
 
 
-    private func fetchTotalRecentBolusAmount(since date: Date) async -> Decimal {
+    private func fetchTotalRecentBolusAmount(since date: Date) async throws -> Decimal {
         let predicate = NSPredicate(
         let predicate = NSPredicate(
             format: "type == %@ AND timestamp > %@",
             format: "type == %@ AND timestamp > %@",
             PumpEventStored.EventType.bolus.rawValue,
             PumpEventStored.EventType.bolus.rawValue,
             date as NSDate
             date as NSDate
         )
         )
 
 
-        let results: Any = await CoreDataStack.shared.fetchEntitiesAsync(
+        let results: Any = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: pumpHistoryFetchContext,
             onContext: pumpHistoryFetchContext,
             predicate: predicate,
             predicate: predicate,

+ 2 - 2
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift

@@ -1,7 +1,7 @@
 import Foundation
 import Foundation
 
 
 extension TrioRemoteControl {
 extension TrioRemoteControl {
-    func handleMealCommand(_ pushMessage: PushMessage) async {
+    func handleMealCommand(_ pushMessage: PushMessage) async throws {
         guard pushMessage.carbs != nil || pushMessage.fat != nil || pushMessage.protein != nil else {
         guard pushMessage.carbs != nil || pushMessage.fat != nil || pushMessage.protein != nil else {
             await logError("Command rejected: meal data is incomplete or invalid.", pushMessage: pushMessage)
             await logError("Command rejected: meal data is incomplete or invalid.", pushMessage: pushMessage)
             return
             return
@@ -72,7 +72,7 @@ extension TrioRemoteControl {
             fpuID: fatDecimal ?? 0 > 0 || proteinDecimal ?? 0 > 0 ? UUID().uuidString : nil
             fpuID: fatDecimal ?? 0 > 0 || proteinDecimal ?? 0 > 0 ? UUID().uuidString : nil
         )
         )
 
 
-        await carbsStorage.storeCarbs([mealEntry], areFetchedFromRemote: false)
+        try await carbsStorage.storeCarbs([mealEntry], areFetchedFromRemote: false)
 
 
         debug(
         debug(
             .remoteControl,
             .remoteControl,

+ 37 - 23
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Override.swift

@@ -12,21 +12,35 @@ extension TrioRemoteControl {
     }
     }
 
 
     @MainActor internal func handleStartOverrideCommand(_ pushMessage: PushMessage) async {
     @MainActor internal func handleStartOverrideCommand(_ pushMessage: PushMessage) async {
-        guard let overrideName = pushMessage.overrideName, !overrideName.isEmpty else {
-            await logError("Command rejected: override name is missing.", pushMessage: pushMessage)
-            return
-        }
+        do {
+            guard let overrideName = pushMessage.overrideName, !overrideName.isEmpty else {
+                await logError("Command rejected: override name is missing.", pushMessage: pushMessage)
+                return
+            }
 
 
-        let presetIDs = await overrideStorage.fetchForOverridePresets()
+            let presetIDs = try await overrideStorage.fetchForOverridePresets()
 
 
-        let presets = presetIDs.compactMap { id in
-            try? viewContext.existingObject(with: id) as? OverrideStored
-        }
+            let presets = try presetIDs.compactMap { id in
+                try viewContext.existingObject(with: id) as? OverrideStored
+            }
 
 
-        if let preset = presets.first(where: { $0.name == overrideName }) {
-            await enactOverridePreset(preset: preset, pushMessage: pushMessage)
-        } else {
-            await logError("Command rejected: override preset '\(overrideName)' not found.", pushMessage: pushMessage)
+            if let preset = presets.first(where: { $0.name == overrideName }) {
+                await enactOverridePreset(preset: preset, pushMessage: pushMessage)
+            } else {
+                await logError(
+                    "Command rejected: override preset '\(overrideName)' not found.",
+                    pushMessage: pushMessage
+                )
+            }
+        } catch {
+            debug(
+                .remoteControl,
+                "\(DebuggingIdentifiers.failed) Failed to handle start override command: \(error.localizedDescription)"
+            )
+            await logError(
+                "Command failed: \(error.localizedDescription)",
+                pushMessage: pushMessage
+            )
         }
         }
     }
     }
 
 
@@ -52,10 +66,10 @@ extension TrioRemoteControl {
     }
     }
 
 
     @MainActor private func disableAllActiveOverrides(except overrideID: NSManagedObjectID? = nil) async {
     @MainActor private func disableAllActiveOverrides(except overrideID: NSManagedObjectID? = nil) async {
-        let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
+        do {
+            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
 
 
-        let didPostNotification = await viewContext.perform { () -> Bool in
-            do {
+            let didPostNotification = try await viewContext.perform { () -> Bool in
                 let results = try ids.compactMap { id in
                 let results = try ids.compactMap { id in
                     try self.viewContext.existingObject(with: id) as? OverrideStored
                     try self.viewContext.existingObject(with: id) as? OverrideStored
                 }
                 }
@@ -89,16 +103,16 @@ extension TrioRemoteControl {
                 } else {
                 } else {
                     return false
                     return false
                 }
                 }
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
-                )
-                return false
             }
             }
-        }
 
 
-        if didPostNotification {
-            await awaitNotification(.didUpdateOverrideConfiguration)
+            if didPostNotification {
+                await awaitNotification(.didUpdateOverrideConfiguration)
+            }
+        } catch {
+            debug(
+                .remoteControl,
+                "\(DebuggingIdentifiers.failed) Failed to disable active overrides: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 }
 }

+ 13 - 13
Trio/Sources/Services/RemoteControl/TrioRemoteControl+TempTarget.swift

@@ -47,10 +47,10 @@ extension TrioRemoteControl {
     }
     }
 
 
     @MainActor func disableAllActiveTempTargets() async {
     @MainActor func disableAllActiveTempTargets() async {
-        let ids = await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
+        do {
+            let ids = try await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
 
 
-        let didPostNotification = await viewContext.perform { () -> Bool in
-            do {
+            let didPostNotification = try await viewContext.perform { () -> Bool in
                 let results = try ids.compactMap { id in
                 let results = try ids.compactMap { id in
                     try self.viewContext.existingObject(with: id) as? TempTargetStored
                     try self.viewContext.existingObject(with: id) as? TempTargetStored
                 }
                 }
@@ -68,8 +68,7 @@ extension TrioRemoteControl {
                     newTempTargetRunStored.name = canceledTempTarget.name
                     newTempTargetRunStored.name = canceledTempTarget.name
                     newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
                     newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
                     newTempTargetRunStored.endDate = Date()
                     newTempTargetRunStored.endDate = Date()
-                    newTempTargetRunStored
-                        .target = canceledTempTarget.target ?? 0
+                    newTempTargetRunStored.target = canceledTempTarget.target ?? 0
                     newTempTargetRunStored.tempTarget = canceledTempTarget
                     newTempTargetRunStored.tempTarget = canceledTempTarget
                     newTempTargetRunStored.isUploadedToNS = false
                     newTempTargetRunStored.isUploadedToNS = false
 
 
@@ -87,16 +86,17 @@ extension TrioRemoteControl {
                 } else {
                 } else {
                     return false
                     return false
                 }
                 }
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active TempTargets with error: \(error.localizedDescription)"
-                )
-                return false
             }
             }
-        }
 
 
-        if didPostNotification {
-            await awaitNotification(.didUpdateTempTargetConfiguration)
+            if didPostNotification {
+                await awaitNotification(.didUpdateTempTargetConfiguration)
+            }
+        } catch {
+            debug(
+                .remoteControl,
+                "\(DebuggingIdentifiers.failed) Failed to disable active temp targets: \(error.localizedDescription)"
+            )
+            await logError("Failed to disable temp targets: \(error.localizedDescription)")
         }
         }
     }
     }
 }
 }

+ 4 - 4
Trio/Sources/Services/RemoteControl/TrioRemoteControl.swift

@@ -22,7 +22,7 @@ class TrioRemoteControl: Injectable {
         injectServices(TrioApp.resolver)
         injectServices(TrioApp.resolver)
     }
     }
 
 
-    func handleRemoteNotification(pushMessage: PushMessage) async {
+    func handleRemoteNotification(pushMessage: PushMessage) async throws {
         let isTrioRemoteControlEnabled = UserDefaults.standard.bool(forKey: "isTrioRemoteControlEnabled")
         let isTrioRemoteControlEnabled = UserDefaults.standard.bool(forKey: "isTrioRemoteControlEnabled")
         guard isTrioRemoteControlEnabled else {
         guard isTrioRemoteControlEnabled else {
             await logError("Remote command received, but remote control is disabled in settings. Ignoring the command.")
             await logError("Remote command received, but remote control is disabled in settings. Ignoring the command.")
@@ -67,16 +67,16 @@ class TrioRemoteControl: Injectable {
 
 
         switch pushMessage.commandType {
         switch pushMessage.commandType {
         case .bolus:
         case .bolus:
-            await handleBolusCommand(pushMessage)
+            try await handleBolusCommand(pushMessage)
         case .tempTarget:
         case .tempTarget:
             await handleTempTargetCommand(pushMessage)
             await handleTempTargetCommand(pushMessage)
         case .cancelTempTarget:
         case .cancelTempTarget:
             await cancelTempTarget(pushMessage)
             await cancelTempTarget(pushMessage)
         case .meal:
         case .meal:
-            await handleMealCommand(pushMessage)
+            try await handleMealCommand(pushMessage)
 
 
             if pushMessage.bolusAmount != nil {
             if pushMessage.bolusAmount != nil {
-                await handleBolusCommand(pushMessage)
+                try await handleBolusCommand(pushMessage)
             }
             }
         case .startOverride:
         case .startOverride:
             await handleStartOverrideCommand(pushMessage)
             await handleStartOverrideCommand(pushMessage)

+ 3 - 3
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -241,8 +241,8 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         )
         )
     }
     }
 
 
-    private func fetchGlucoseIDs() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucoseIDs() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.predicateFor20MinAgo,
             predicate: NSPredicate.predicateFor20MinAgo,
@@ -261,7 +261,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     @MainActor private func sendGlucoseNotification() async {
     @MainActor private func sendGlucoseNotification() async {
         do {
         do {
             addAppBadge(glucose: nil)
             addAppBadge(glucose: nil)
-            let glucoseIDs = await fetchGlucoseIDs()
+            let glucoseIDs = try await fetchGlucoseIDs()
             let glucoseObjects = try glucoseIDs.compactMap { id in
             let glucoseObjects = try glucoseIDs.compactMap { id in
                 try viewContext.existingObject(with: id) as? GlucoseStored
                 try viewContext.existingObject(with: id) as? GlucoseStored
             }
             }

+ 155 - 136
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -149,159 +149,171 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     /// Prepares the current state data to be sent to the Watch
     /// Prepares the current state data to be sent to the Watch
     /// - Returns: WatchState containing current glucose readings and trends and determination infos for displaying cob and iob in the view
     /// - Returns: WatchState containing current glucose readings and trends and determination infos for displaying cob and iob in the view
     func setupWatchState() async -> WatchState {
     func setupWatchState() async -> WatchState {
-        // Get NSManagedObjectIDs
-        let glucoseIds = await fetchGlucose()
-        // TODO: - if we want that the watch immediately displays updated cob and iob values when entered via treatment view from phone, we would need to use a predicate here that also filters for NON-ENACTED Determinations
-        let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
-            predicate: NSPredicate.predicateFor30MinAgoForDetermination
-        )
-        let overridePresetIds = await overrideStorage.fetchForOverridePresets()
-        let tempTargetPresetIds = await tempTargetStorage.fetchForTempTargetPresets()
-
-        // Get NSManagedObjects
-        let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: glucoseIds, context: backgroundContext)
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
-            .getNSManagedObject(with: determinationIds, context: backgroundContext)
-        let overridePresetObjects: [OverrideStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: overridePresetIds, context: backgroundContext)
-        let tempTargetPresetObjects: [TempTargetStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: tempTargetPresetIds, context: backgroundContext)
-
-        return await backgroundContext.perform {
-            var watchState = WatchState(date: Date())
-
-            // Set lastLoopDate
-            let lastLoopMinutes = Int((Date().timeIntervalSince(self.apsManager.lastLoopDate) - 30) / 60) + 1
-            if lastLoopMinutes > 1440 {
-                watchState.lastLoopTime = "--"
-            } else {
-                watchState.lastLoopTime = "\(lastLoopMinutes) min"
-            }
+        do {
+            // Get NSManagedObjectIDs
+            let glucoseIds = try await fetchGlucose()
+            let determinationIds = try await determinationStorage.fetchLastDeterminationObjectID(
+                predicate: NSPredicate.predicateFor30MinAgoForDetermination
+            )
+            let overridePresetIds = try await overrideStorage.fetchForOverridePresets()
+            let tempTargetPresetIds = try await tempTargetStorage.fetchForTempTargetPresets()
+
+            // Get NSManagedObjects
+            let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared
+                .getNSManagedObject(with: glucoseIds, context: backgroundContext)
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
+                .getNSManagedObject(with: determinationIds, context: backgroundContext)
+            let overridePresetObjects: [OverrideStored] = try await CoreDataStack.shared
+                .getNSManagedObject(with: overridePresetIds, context: backgroundContext)
+            let tempTargetPresetObjects: [TempTargetStored] = try await CoreDataStack.shared
+                .getNSManagedObject(with: tempTargetPresetIds, context: backgroundContext)
+
+            return await backgroundContext.perform {
+                var watchState = WatchState(date: Date())
+
+                // Set lastLoopDate
+                let lastLoopMinutes = Int((Date().timeIntervalSince(self.apsManager.lastLoopDate) - 30) / 60) + 1
+                if lastLoopMinutes > 1440 {
+                    watchState.lastLoopTime = "--"
+                } else {
+                    watchState.lastLoopTime = "\(lastLoopMinutes) min"
+                }
 
 
-            // Set IOB and COB from latest determination
-            if let latestDetermination = determinationObjects.first {
-                let iob = latestDetermination.iob ?? 0
-                watchState.iob = Formatter.decimalFormatterWithTwoFractionDigits.string(from: iob)
+                // Set IOB and COB from latest determination
+                if let latestDetermination = determinationObjects.first {
+                    let iob = latestDetermination.iob ?? 0
+                    watchState.iob = Formatter.decimalFormatterWithTwoFractionDigits.string(from: iob)
 
 
-                let cob = NSNumber(value: latestDetermination.cob)
-                watchState.cob = Formatter.integerFormatter.string(from: cob)
-            }
+                    let cob = NSNumber(value: latestDetermination.cob)
+                    watchState.cob = Formatter.integerFormatter.string(from: cob)
+                }
 
 
-            // Set override presets with their enabled status
-            watchState.overridePresets = overridePresetObjects.map { override in
-                OverridePresetWatch(
-                    name: override.name ?? "",
-                    isEnabled: override.enabled
-                )
-            }
+                // Set override presets with their enabled status
+                watchState.overridePresets = overridePresetObjects.map { override in
+                    OverridePresetWatch(
+                        name: override.name ?? "",
+                        isEnabled: override.enabled
+                    )
+                }
 
 
-            guard let latestGlucose = glucoseObjects.first else {
-                return watchState
-            }
+                guard let latestGlucose = glucoseObjects.first else {
+                    return watchState
+                }
 
 
-            // Assign currentGlucose and its color
-            /// Set current glucose with proper formatting
-            if self.units == .mgdL {
-                watchState.currentGlucose = "\(latestGlucose.glucose)"
-            } else {
-                let mgdlValue = Decimal(latestGlucose.glucose)
-                let latestGlucoseValue = mgdlValue.formattedAsMmolL
-                watchState.currentGlucose = "\(latestGlucoseValue)"
-            }
-
-            /// Calculate latest color
-            let hardCodedLow = Decimal(55)
-            let hardCodedHigh = Decimal(220)
-            let isDynamicColorScheme = self.glucoseColorScheme == .dynamicColor
-
-            let highGlucoseValue = isDynamicColorScheme ? hardCodedHigh : self.highGlucose
-            let lowGlucoseValue = isDynamicColorScheme ? hardCodedLow : self.lowGlucose
-            let highGlucoseColorValue = highGlucoseValue
-            let lowGlucoseColorValue = lowGlucoseValue
-            let targetGlucose = self.currentGlucoseTarget
-
-            let currentGlucoseColor = Trio.getDynamicGlucoseColor(
-                glucoseValue: Decimal(latestGlucose.glucose),
-                highGlucoseColorValue: highGlucoseColorValue,
-                lowGlucoseColorValue: lowGlucoseColorValue,
-                targetGlucose: targetGlucose,
-                glucoseColorScheme: self.glucoseColorScheme
-            )
+                // Assign currentGlucose and its color
+                /// Set current glucose with proper formatting
+                if self.units == .mgdL {
+                    watchState.currentGlucose = "\(latestGlucose.glucose)"
+                } else {
+                    let mgdlValue = Decimal(latestGlucose.glucose)
+                    let latestGlucoseValue = mgdlValue.formattedAsMmolL
+                    watchState.currentGlucose = "\(latestGlucoseValue)"
+                }
 
 
-            if Decimal(latestGlucose.glucose) <= self.lowGlucose || Decimal(latestGlucose.glucose) >= self.highGlucose {
-                watchState.currentGlucoseColorString = currentGlucoseColor.toHexString()
-            } else {
-                watchState.currentGlucoseColorString = "#ffffff" // white when in range; colored when out of range
-            }
+                /// Calculate latest color
+                let hardCodedLow = Decimal(55)
+                let hardCodedHigh = Decimal(220)
+                let isDynamicColorScheme = self.glucoseColorScheme == .dynamicColor
 
 
-            // Map glucose values
-            watchState.glucoseValues = glucoseObjects.compactMap { glucose in
-                let glucoseValue = self.units == .mgdL
-                    ? Double(glucose.glucose)
-                    : Double(truncating: Decimal(glucose.glucose).asMmolL as NSNumber)
+                let highGlucoseValue = isDynamicColorScheme ? hardCodedHigh : self.highGlucose
+                let lowGlucoseValue = isDynamicColorScheme ? hardCodedLow : self.lowGlucose
+                let highGlucoseColorValue = highGlucoseValue
+                let lowGlucoseColorValue = lowGlucoseValue
+                let targetGlucose = self.currentGlucoseTarget
 
 
-                let glucoseColor = Trio.getDynamicGlucoseColor(
-                    glucoseValue: Decimal(glucose.glucose),
+                let currentGlucoseColor = Trio.getDynamicGlucoseColor(
+                    glucoseValue: Decimal(latestGlucose.glucose),
                     highGlucoseColorValue: highGlucoseColorValue,
                     highGlucoseColorValue: highGlucoseColorValue,
                     lowGlucoseColorValue: lowGlucoseColorValue,
                     lowGlucoseColorValue: lowGlucoseColorValue,
                     targetGlucose: targetGlucose,
                     targetGlucose: targetGlucose,
                     glucoseColorScheme: self.glucoseColorScheme
                     glucoseColorScheme: self.glucoseColorScheme
                 )
                 )
 
 
-                return WatchGlucoseObject(date: glucose.date ?? Date(), glucose: glucoseValue, color: glucoseColor.toHexString())
-            }
-            .sorted { $0.date < $1.date }
+                if Decimal(latestGlucose.glucose) <= self.lowGlucose || Decimal(latestGlucose.glucose) >= self.highGlucose {
+                    watchState.currentGlucoseColorString = currentGlucoseColor.toHexString()
+                } else {
+                    watchState.currentGlucoseColorString = "#ffffff" // white when in range; colored when out of range
+                }
 
 
-            // Convert direction to trend string
-            watchState.trend = latestGlucose.direction
+                // Map glucose values
+                watchState.glucoseValues = glucoseObjects.compactMap { glucose in
+                    let glucoseValue = self.units == .mgdL
+                        ? Double(glucose.glucose)
+                        : Double(truncating: Decimal(glucose.glucose).asMmolL as NSNumber)
+
+                    let glucoseColor = Trio.getDynamicGlucoseColor(
+                        glucoseValue: Decimal(glucose.glucose),
+                        highGlucoseColorValue: highGlucoseColorValue,
+                        lowGlucoseColorValue: lowGlucoseColorValue,
+                        targetGlucose: targetGlucose,
+                        glucoseColorScheme: self.glucoseColorScheme
+                    )
+
+                    return WatchGlucoseObject(
+                        date: glucose.date ?? Date(),
+                        glucose: glucoseValue,
+                        color: glucoseColor.toHexString()
+                    )
+                }
+                .sorted { $0.date < $1.date }
+
+                // Convert direction to trend string
+                watchState.trend = latestGlucose.direction
+
+                // Calculate delta if we have at least 2 readings
+                if glucoseObjects.count >= 2 {
+                    var deltaValue = Decimal(glucoseObjects[0].glucose - glucoseObjects[1].glucose)
 
 
-            // Calculate delta if we have at least 2 readings
-            if glucoseObjects.count >= 2 {
-                var deltaValue = Decimal(glucoseObjects[0].glucose - glucoseObjects[1].glucose)
+                    if self.units == .mmolL {
+                        deltaValue = Double(truncating: deltaValue as NSNumber).asMmolL
+                    }
 
 
-                if self.units == .mmolL {
-                    deltaValue = Double(truncating: deltaValue as NSNumber).asMmolL
+                    let formattedDelta = Formatter.glucoseFormatter(for: self.units)
+                        .string(from: deltaValue as NSNumber) ?? "0"
+                    watchState.delta = deltaValue < 0 ? "\(formattedDelta)" : "+\(formattedDelta)"
                 }
                 }
 
 
-                let formattedDelta = Formatter.glucoseFormatter(for: self.units)
-                    .string(from: deltaValue as NSNumber) ?? "0"
-                watchState.delta = deltaValue < 0 ? "\(formattedDelta)" : "+\(formattedDelta)"
-            }
+                // Set temp target presets with their enabled status
+                watchState.tempTargetPresets = tempTargetPresetObjects.map { tempTarget in
+                    TempTargetPresetWatch(
+                        name: tempTarget.name ?? "",
+                        isEnabled: tempTarget.enabled
+                    )
+                }
 
 
-            // Set temp target presets with their enabled status
-            watchState.tempTargetPresets = tempTargetPresetObjects.map { tempTarget in
-                TempTargetPresetWatch(
-                    name: tempTarget.name ?? "",
-                    isEnabled: tempTarget.enabled
-                )
-            }
+                // Set units
+                watchState.units = self.units
 
 
-            // Set units
-            watchState.units = self.units
+                // Add limits and pump specific dosing increment settings values
+                watchState.maxBolus = self.settingsManager.pumpSettings.maxBolus
+                watchState.maxCarbs = self.settingsManager.settings.maxCarbs
+                watchState.maxFat = self.settingsManager.settings.maxFat
+                watchState.maxProtein = self.settingsManager.settings.maxProtein
+                watchState.bolusIncrement = self.settingsManager.preferences.bolusIncrement
+                watchState.confirmBolusFaster = self.settingsManager.settings.confirmBolusFaster
 
 
-            // Add limits and pump specific dosing increment settings values
-            watchState.maxBolus = self.settingsManager.pumpSettings.maxBolus
-            watchState.maxCarbs = self.settingsManager.settings.maxCarbs
-            watchState.maxFat = self.settingsManager.settings.maxFat
-            watchState.maxProtein = self.settingsManager.settings.maxProtein
-            watchState.bolusIncrement = self.settingsManager.preferences.bolusIncrement
-            watchState.confirmBolusFaster = self.settingsManager.settings.confirmBolusFaster
+                debug(
+                    .watchManager,
+
+                    "📱 Setup WatchState - currentGlucose: \(watchState.currentGlucose ?? "nil"), trend: \(watchState.trend ?? "nil"), delta: \(watchState.delta ?? "nil"), values: \(watchState.glucoseValues.count)"
+                )
 
 
+                return watchState
+            }
+        } catch {
             debug(
             debug(
                 .watchManager,
                 .watchManager,
-
-                "📱 Setup WatchState - currentGlucose: \(watchState.currentGlucose ?? "nil"), trend: \(watchState.trend ?? "nil"), delta: \(watchState.delta ?? "nil"), values: \(watchState.glucoseValues.count)"
+                "\(DebuggingIdentifiers.failed) Error setting up watch state: \(error.localizedDescription)"
             )
             )
-
-            return watchState
+            // Return empty state in case of error
+            return WatchState(date: Date())
         }
         }
     }
     }
 
 
     /// Fetches recent glucose readings from CoreData
     /// Fetches recent glucose readings from CoreData
     /// - Returns: Array of NSManagedObjectIDs for glucose readings
     /// - Returns: Array of NSManagedObjectIDs for glucose readings
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,
@@ -319,8 +331,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
 
     /// Fetches last pump event that is a non-external bolus from CoreData
     /// Fetches last pump event that is a non-external bolus from CoreData
     /// - Returns: NSManagedObjectIDs for last bolus
     /// - Returns: NSManagedObjectIDs for last bolus
-    func fetchLastBolus() async -> NSManagedObjectID? {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    func fetchLastBolus() async throws -> NSManagedObjectID? {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
             ofType: PumpEventStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.lastPumpBolus,
             predicate: NSPredicate.lastPumpBolus,
@@ -338,11 +350,18 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
 
     /// Gets the active bolus amount by fetching last (active) bolus.
     /// Gets the active bolus amount by fetching last (active) bolus.
     @MainActor func getActiveBolusAmount() async {
     @MainActor func getActiveBolusAmount() async {
-        if let lastBolusObjectId = await fetchLastBolus() {
-            let lastBolusObject: [PumpEventStored] = await CoreDataStack.shared
-                .getNSManagedObject(with: [lastBolusObjectId], context: viewContext)
+        do {
+            if let lastBolusObjectId = try await fetchLastBolus() {
+                let lastBolusObject: [PumpEventStored] = try await CoreDataStack.shared
+                    .getNSManagedObject(with: [lastBolusObjectId], context: viewContext)
 
 
-            activeBolusAmount = lastBolusObject.first?.bolus?.amount?.doubleValue ?? 0.0
+                activeBolusAmount = lastBolusObject.first?.bolus?.amount?.doubleValue ?? 0.0
+            }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Error getting active bolus amount: \(error.localizedDescription)"
+            )
         }
         }
     }
     }
 
 
@@ -678,7 +697,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         Task {
         Task {
             let context = CoreDataStack.shared.newTaskContext()
             let context = CoreDataStack.shared.newTaskContext()
 
 
-            if let overrideId = await overrideStorage.fetchLatestActiveOverride() {
+            if let overrideId = try await overrideStorage.fetchLatestActiveOverride() {
                 let override = await context.perform {
                 let override = await context.perform {
                     context.object(with: overrideId) as? OverrideStored
                     context.object(with: overrideId) as? OverrideStored
                 }
                 }
@@ -716,12 +735,12 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             let context = CoreDataStack.shared.newTaskContext()
             let context = CoreDataStack.shared.newTaskContext()
 
 
             // Fetch all presets to find the one to activate
             // Fetch all presets to find the one to activate
-            let presetIds = await overrideStorage.fetchForOverridePresets()
-            let presets: [OverrideStored] = await CoreDataStack.shared
+            let presetIds = try await overrideStorage.fetchForOverridePresets()
+            let presets: [OverrideStored] = try await CoreDataStack.shared
                 .getNSManagedObject(with: presetIds, context: context)
                 .getNSManagedObject(with: presetIds, context: context)
 
 
             // Check for active override
             // Check for active override
-            if let activeOverrideId = await overrideStorage.fetchLatestActiveOverride() {
+            if let activeOverrideId = try await overrideStorage.fetchLatestActiveOverride() {
                 let activeOverride = await context.perform {
                 let activeOverride = await context.perform {
                     context.object(with: activeOverrideId) as? OverrideStored
                     context.object(with: activeOverrideId) as? OverrideStored
                 }
                 }
@@ -768,12 +787,12 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             let context = CoreDataStack.shared.newTaskContext()
             let context = CoreDataStack.shared.newTaskContext()
 
 
             // Fetch all presets to find the one to activate
             // Fetch all presets to find the one to activate
-            let presetIds = await tempTargetStorage.fetchForTempTargetPresets()
-            let presets: [TempTargetStored] = await CoreDataStack.shared
+            let presetIds = try await tempTargetStorage.fetchForTempTargetPresets()
+            let presets: [TempTargetStored] = try await CoreDataStack.shared
                 .getNSManagedObject(with: presetIds, context: context)
                 .getNSManagedObject(with: presetIds, context: context)
 
 
             // Check for active temp target
             // Check for active temp target
-            if let activeTempTargetId = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
+            if let activeTempTargetId = try await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
                 let activeTempTarget = await context.perform {
                 let activeTempTarget = await context.perform {
                     context.object(with: activeTempTargetId) as? TempTargetStored
                     context.object(with: activeTempTargetId) as? TempTargetStored
                 }
                 }
@@ -840,7 +859,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         Task {
         Task {
             let context = CoreDataStack.shared.newTaskContext()
             let context = CoreDataStack.shared.newTaskContext()
 
 
-            if let tempTargetId = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
+            if let tempTargetId = try await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
                 let tempTarget = await context.perform {
                 let tempTarget = await context.perform {
                     context.object(with: tempTargetId) as? TempTargetStored
                     context.object(with: tempTargetId) as? TempTargetStored
                 }
                 }

+ 105 - 90
Trio/Sources/Services/WatchManager/GarminManager.swift

@@ -53,7 +53,7 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
     /// Stores, retrieves, and updates insulin dose determinations in CoreData.
     /// Stores, retrieves, and updates insulin dose determinations in CoreData.
     @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var determinationStorage: DeterminationStorage!
 
 
-    /// Persists the users device list between app launches.
+    /// Persists the user's device list between app launches.
     @Persisted(key: "BaseGarminManager.persistedDevices") private var persistedDevices: [GarminDevice] = []
     @Persisted(key: "BaseGarminManager.persistedDevices") private var persistedDevices: [GarminDevice] = []
 
 
     /// Router for presenting alerts or navigation flows (injected via Swinject).
     /// Router for presenting alerts or navigation flows (injected via Swinject).
@@ -134,9 +134,16 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
             .sink { [weak self] _ in
             .sink { [weak self] _ in
                 guard let self = self else { return }
                 guard let self = self else { return }
                 Task {
                 Task {
-                    let watchState = await self.setupGarminWatchState()
-                    let watchStateData = try JSONEncoder().encode(watchState)
-                    self.sendWatchStateData(watchStateData)
+                    do {
+                        let watchState = try await self.setupGarminWatchState()
+                        let watchStateData = try JSONEncoder().encode(watchState)
+                        self.sendWatchStateData(watchStateData)
+                    } catch {
+                        debug(
+                            .watchManager,
+                            "\(DebuggingIdentifiers.failed) Error updating watch state: \(error.localizedDescription)"
+                        )
+                    }
                 }
                 }
             }
             }
             .store(in: &subscriptions)
             .store(in: &subscriptions)
@@ -154,7 +161,7 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
             .sink { [weak self] _ in
             .sink { [weak self] _ in
                 guard let self = self else { return }
                 guard let self = self else { return }
                 Task {
                 Task {
-                    let watchState = await self.setupGarminWatchState()
+                    let watchState = try await self.setupGarminWatchState()
                     let watchStateData = try JSONEncoder().encode(watchState)
                     let watchStateData = try JSONEncoder().encode(watchState)
                     self.sendWatchStateData(watchStateData)
                     self.sendWatchStateData(watchStateData)
                 }
                 }
@@ -167,7 +174,7 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
             .sink { [weak self] _ in
             .sink { [weak self] _ in
                 guard let self = self else { return }
                 guard let self = self else { return }
                 Task {
                 Task {
-                    let watchState = await self.setupGarminWatchState()
+                    let watchState = try await self.setupGarminWatchState()
                     let watchStateData = try JSONEncoder().encode(watchState)
                     let watchStateData = try JSONEncoder().encode(watchState)
                     self.sendWatchStateData(watchStateData)
                     self.sendWatchStateData(watchStateData)
                 }
                 }
@@ -177,8 +184,8 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
 
 
     /// Fetches recent glucose readings from CoreData, up to 288 results.
     /// Fetches recent glucose readings from CoreData, up to 288 results.
     /// - Returns: An array of `NSManagedObjectID`s for glucose readings.
     /// - Returns: An array of `NSManagedObjectID`s for glucose readings.
-    private func fetchGlucose() async -> [NSManagedObjectID] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+    private func fetchGlucose() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: backgroundContext,
             onContext: backgroundContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,
@@ -195,101 +202,109 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
 
 
     /// Builds a `GarminWatchState` reflecting the latest glucose, trend, delta, eventual BG, ISF, IOB, and COB.
     /// Builds a `GarminWatchState` reflecting the latest glucose, trend, delta, eventual BG, ISF, IOB, and COB.
     /// - Returns: A `GarminWatchState` containing the most recent device- and therapy-related info.
     /// - Returns: A `GarminWatchState` containing the most recent device- and therapy-related info.
-    func setupGarminWatchState() async -> GarminWatchState {
-        // Get Glucose IDs
-        let glucoseIds = await fetchGlucose()
-
-        // Fetch the latest OrefDetermination object if available
-        let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
-            predicate: NSPredicate.predicateFor30MinAgoForDetermination
-        )
-
-        // Turn those IDs into live NSManagedObjects
-        let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: glucoseIds, context: backgroundContext)
-        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
-            .getNSManagedObject(with: determinationIds, context: backgroundContext)
-
-        // Perform logic on the background context
-        return await backgroundContext.perform {
-            var watchState = GarminWatchState()
+    func setupGarminWatchState() async throws -> GarminWatchState {
+        do {
+            // Get Glucose IDs
+            let glucoseIds = try await fetchGlucose()
+
+            // Fetch the latest OrefDetermination object if available
+            let determinationIds = try await determinationStorage.fetchLastDeterminationObjectID(
+                predicate: NSPredicate.predicateFor30MinAgoForDetermination
+            )
 
 
-            /// Pull `glucose`, `trendRaw`, `delta`, `lastLoopDateInterval`, `iob`, `cob`,  `isf`, and `eventualBGRaw` from the latest determination.
-            if let latestDetermination = determinationObjects.first {
-                watchState.lastLoopDateInterval = latestDetermination.timestamp.map {
-                    guard $0.timeIntervalSince1970 > 0 else { return 0 }
-                    return UInt64($0.timeIntervalSince1970)
+            // Turn those IDs into live NSManagedObjects
+            let glucoseObjects: [GlucoseStored] = try await CoreDataStack.shared
+                .getNSManagedObject(with: glucoseIds, context: backgroundContext)
+            let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
+                .getNSManagedObject(with: determinationIds, context: backgroundContext)
+
+            // Perform logic on the background context
+            return await backgroundContext.perform {
+                var watchState = GarminWatchState()
+
+                /// Pull `glucose`, `trendRaw`, `delta`, `lastLoopDateInterval`, `iob`, `cob`,  `isf`, and `eventualBGRaw` from the latest determination.
+                if let latestDetermination = determinationObjects.first {
+                    watchState.lastLoopDateInterval = latestDetermination.timestamp.map {
+                        guard $0.timeIntervalSince1970 > 0 else { return 0 }
+                        return UInt64($0.timeIntervalSince1970)
+                    }
+
+                    let iobValue = latestDetermination.iob ?? 0
+                    watchState.iob = Formatter.decimalFormatterWithTwoFractionDigits.string(from: iobValue)
+
+                    let cobNumber = NSNumber(value: latestDetermination.cob)
+                    watchState.cob = Formatter.integerFormatter.string(from: cobNumber)
+
+                    let insulinSensitivity = latestDetermination.insulinSensitivity ?? 0
+                    let eventualBG = latestDetermination.eventualBG ?? 0
+
+                    if self.units == .mgdL {
+                        watchState.isf = insulinSensitivity.description
+                        watchState.eventualBGRaw = Formatter.glucoseFormatter(for: self.units)
+                            .string(from: eventualBG) ?? "0"
+                    } else {
+                        let parsedIsf = Double(truncating: insulinSensitivity).asMmolL
+                        let parsedEventualBG = Double(truncating: eventualBG).asMmolL
+
+                        watchState.isf = parsedIsf.description
+                        watchState.eventualBGRaw = Formatter.glucoseFormatter(for: self.units)
+                            .string(from: parsedEventualBG as NSNumber) ?? "0"
+                    }
                 }
                 }
 
 
-                let iobValue = latestDetermination.iob ?? 0
-                watchState.iob = Formatter.decimalFormatterWithTwoFractionDigits.string(from: iobValue)
-
-                let cobNumber = NSNumber(value: latestDetermination.cob)
-                watchState.cob = Formatter.integerFormatter.string(from: cobNumber)
-
-                let insulinSensitivity = latestDetermination.insulinSensitivity ?? 0
-                let eventualBG = latestDetermination.eventualBG ?? 0
+                // If no glucose data is present, just return partial watch state
+                guard let latestGlucose = glucoseObjects.first else {
+                    return watchState
+                }
 
 
+                // Format the current glucose reading
                 if self.units == .mgdL {
                 if self.units == .mgdL {
-                    watchState.isf = insulinSensitivity.description
-                    watchState.eventualBGRaw = Formatter.glucoseFormatter(for: self.units)
-                        .string(from: eventualBG) ?? "0"
+                    watchState.glucose = "\(latestGlucose.glucose)"
                 } else {
                 } else {
-                    let parsedIsf = Double(truncating: insulinSensitivity).asMmolL
-                    let parsedEventualBG = Double(truncating: eventualBG).asMmolL
-
-                    watchState.isf = parsedIsf.description
-                    watchState.eventualBGRaw = Formatter.glucoseFormatter(for: self.units)
-                        .string(from: parsedEventualBG as NSNumber) ?? "0"
+                    let mgdlValue = Decimal(latestGlucose.glucose)
+                    let latestGlucoseValue = Double(truncating: mgdlValue.asMmolL as NSNumber)
+                    watchState.glucose = "\(latestGlucoseValue)"
                 }
                 }
-            }
-
-            // If no glucose data is present, just return partial watch state
-            guard let latestGlucose = glucoseObjects.first else {
-                return watchState
-            }
 
 
-            // Format the current glucose reading
-            if self.units == .mgdL {
-                watchState.glucose = "\(latestGlucose.glucose)"
-            } else {
-                let mgdlValue = Decimal(latestGlucose.glucose)
-                let latestGlucoseValue = Double(truncating: mgdlValue.asMmolL as NSNumber)
-                watchState.glucose = "\(latestGlucoseValue)"
-            }
+                // Convert direction to a textual trend
+                watchState.trendRaw = latestGlucose.direction ?? "--"
 
 
-            // Convert direction to a textual trend
-            watchState.trendRaw = latestGlucose.direction ?? "--"
+                // Calculate a glucose delta if we have at least two readings
+                if glucoseObjects.count >= 2 {
+                    var deltaValue = Decimal(glucoseObjects[0].glucose - glucoseObjects[1].glucose)
 
 
-            // Calculate a glucose delta if we have at least two readings
-            if glucoseObjects.count >= 2 {
-                var deltaValue = Decimal(glucoseObjects[0].glucose - glucoseObjects[1].glucose)
+                    if self.units == .mmolL {
+                        deltaValue = Double(truncating: deltaValue as NSNumber).asMmolL
+                    }
 
 
-                if self.units == .mmolL {
-                    deltaValue = Double(truncating: deltaValue as NSNumber).asMmolL
+                    let formattedDelta = Formatter.glucoseFormatter(for: self.units)
+                        .string(from: deltaValue as NSNumber) ?? "0"
+                    watchState.delta = deltaValue < 0 ? "\(formattedDelta)" : "+\(formattedDelta)"
                 }
                 }
 
 
-                let formattedDelta = Formatter.glucoseFormatter(for: self.units)
-                    .string(from: deltaValue as NSNumber) ?? "0"
-                watchState.delta = deltaValue < 0 ? "\(formattedDelta)" : "+\(formattedDelta)"
-            }
+                debug(
+                    .watchManager,
+                    """
+                    📱 Setup GarminWatchState - \
+                    glucose: \(watchState.glucose ?? "nil"), \
+                    trendRaw: \(watchState.trendRaw ?? "nil"), \
+                    delta: \(watchState.delta ?? "nil"), \
+                    eventualBGRaw: \(watchState.eventualBGRaw ?? "nil"), \
+                    isf: \(watchState.isf ?? "nil"), \
+                    cob: \(watchState.cob ?? "nil"), \
+                    iob: \(watchState.iob ?? "nil"), \
+                    lastLoopDateInterval: \(watchState.lastLoopDateInterval?.description ?? "nil")
+                    """
+                )
 
 
+                return watchState
+            }
+        } catch {
             debug(
             debug(
                 .watchManager,
                 .watchManager,
-                """
-                📱 Setup GarminWatchState - \
-                glucose: \(watchState.glucose ?? "nil"), \
-                trendRaw: \(watchState.trendRaw ?? "nil"), \
-                delta: \(watchState.delta ?? "nil"), \
-                eventualBGRaw: \(watchState.eventualBGRaw ?? "nil"), \
-                isf: \(watchState.isf ?? "nil"), \
-                cob: \(watchState.cob ?? "nil"), \
-                iob: \(watchState.iob ?? "nil"), \
-                lastLoopDateInterval: \(watchState.lastLoopDateInterval?.description ?? "nil")
-                """
+                "\(DebuggingIdentifiers.failed) Error setting up Garmin watch state: \(error.localizedDescription)"
             )
             )
-
-            return watchState
+            throw error
         }
         }
     }
     }
 
 
@@ -357,7 +372,7 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
     }
     }
 
 
     /// Subscribes to any watch-state dictionaries published via `watchStateSubject`, and throttles them
     /// Subscribes to any watch-state dictionaries published via `watchStateSubject`, and throttles them
-    /// so updates arent sent too frequently. Each update triggers a broadcast to all watch apps.
+    /// so updates aren't sent too frequently. Each update triggers a broadcast to all watch apps.
     private func subscribeToWatchState() {
     private func subscribeToWatchState() {
         watchStateSubject
         watchStateSubject
             .throttle(for: .seconds(10), scheduler: DispatchQueue.main, latest: true)
             .throttle(for: .seconds(10), scheduler: DispatchQueue.main, latest: true)
@@ -419,7 +434,7 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
         .eraseToAnyPublisher()
         .eraseToAnyPublisher()
     }
     }
 
 
-    /// Updates the managers list of devices, typically after user selection or manual changes.
+    /// Updates the manager's list of devices, typically after user selection or manual changes.
     /// - Parameter devices: The new array of `IQDevice` objects to track.
     /// - Parameter devices: The new array of `IQDevice` objects to track.
     func updateDeviceList(_ devices: [IQDevice]) {
     func updateDeviceList(_ devices: [IQDevice]) {
         self.devices = devices
         self.devices = devices
@@ -527,7 +542,7 @@ extension BaseGarminManager: IQUIOverrideDelegate, IQDeviceEventDelegate, IQAppM
 
 
             do {
             do {
                 // Fetch the latest watch state (async) and encode it to JSON data
                 // Fetch the latest watch state (async) and encode it to JSON data
-                let watchState = await self.setupGarminWatchState()
+                let watchState = try await self.setupGarminWatchState()
                 let watchStateData = try JSONEncoder().encode(watchState)
                 let watchStateData = try JSONEncoder().encode(watchState)
 
 
                 // Now send that JSON data to the watch
                 // Now send that JSON data to the watch
@@ -560,7 +575,7 @@ extension BaseGarminManager: SettingsObserver {
         units = settingsManager.settings.units
         units = settingsManager.settings.units
 
 
         Task {
         Task {
-            let watchState = await setupGarminWatchState()
+            let watchState = try await setupGarminWatchState()
             let watchStateData = try JSONEncoder().encode(watchState)
             let watchStateData = try JSONEncoder().encode(watchState)
             sendWatchStateData(watchStateData)
             sendWatchStateData(watchStateData)
         }
         }

+ 1 - 1
Trio/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift

@@ -15,7 +15,7 @@ import Foundation
 
 
         let carbs = min(Decimal(quantityCarbs), settingsManager.settings.maxCarbs)
         let carbs = min(Decimal(quantityCarbs), settingsManager.settings.maxCarbs)
 
 
-        await carbsStorage.storeCarbs(
+        try await carbsStorage.storeCarbs(
             [CarbsEntry(
             [CarbsEntry(
                 id: UUID().uuidString,
                 id: UUID().uuidString,
                 createdAt: dateAdded,
                 createdAt: dateAdded,

+ 2 - 2
Trio/Sources/Shortcuts/Override/OverridePresetEntity.swift

@@ -18,10 +18,10 @@ struct OverridePreset: AppEntity, Identifiable {
 
 
 struct OverridePresetsQuery: EntityQuery {
 struct OverridePresetsQuery: EntityQuery {
     func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
     func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
-        await OverridePresetsIntentRequest().fetchIDs(identifiers)
+        try await OverridePresetsIntentRequest().fetchIDs(identifiers)
     }
     }
 
 
     func suggestedEntities() async throws -> [OverridePreset] {
     func suggestedEntities() async throws -> [OverridePreset] {
-        await OverridePresetsIntentRequest().fetchAndProcessOverrides()
+        try await OverridePresetsIntentRequest().fetchAndProcessOverrides()
     }
     }
 }
 }

+ 45 - 48
Trio/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift

@@ -9,13 +9,13 @@ import UIKit
         case noActiveOverride
         case noActiveOverride
     }
     }
 
 
-    func fetchAndProcessOverrides() async -> [OverridePreset] {
-        // Fetch all Override Presets via OverrideStorage
-        let allOverridePresetsIDs = await overrideStorage.fetchForOverridePresets()
+    func fetchAndProcessOverrides() async throws -> [OverridePreset] {
+        do {
+            // Fetch all Override Presets via OverrideStorage
+            let allOverridePresetsIDs = try await overrideStorage.fetchForOverridePresets()
 
 
-        // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first
-        return await coredataContext.perform {
-            do {
+            // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first
+            return try await coredataContext.perform {
                 let overrideObjects = try allOverridePresetsIDs.compactMap { id in
                 let overrideObjects = try allOverridePresetsIDs.compactMap { id in
                     try self.coredataContext.existingObject(with: id) as? OverrideStored
                     try self.coredataContext.existingObject(with: id) as? OverrideStored
                 }
                 }
@@ -25,18 +25,18 @@ import UIKit
                           let name = object.name else { return OverridePreset(id: UUID().uuidString, name: "") }
                           let name = object.name else { return OverridePreset(id: UUID().uuidString, name: "") }
                     return OverridePreset(id: id, name: name)
                     return OverridePreset(id: id, name: name)
                 }
                 }
-
-            } catch {
-                debugPrint(
-                    "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while fetching/ processing the overrides Array: \(error.localizedDescription)"
-                )
-                return [OverridePreset(id: UUID().uuidString, name: "")]
             }
             }
+        } catch {
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Error fetching/processing overrides: \(error.localizedDescription)"
+            )
+            throw error
         }
         }
     }
     }
 
 
-    func fetchIDs(_ uuid: [OverridePreset.ID]) async -> [OverridePreset] {
-        await coredataContext.perform {
+    func fetchIDs(_ uuid: [OverridePreset.ID]) async throws -> [OverridePreset] {
+        try await coredataContext.perform {
             let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
             let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
             fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
             fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
 
 
@@ -44,36 +44,40 @@ import UIKit
                 let result = try self.coredataContext.fetch(fetchRequest)
                 let result = try self.coredataContext.fetch(fetchRequest)
 
 
                 if result.isEmpty {
                 if result.isEmpty {
-                    debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) No OverrideStored found for ids: \(uuid)")
-                    return [OverridePreset(id: UUID().uuidString, name: "")]
+                    debug(
+                        .default,
+                        "\(DebuggingIdentifiers.failed) No OverrideStored found for ids: \(uuid)"
+                    )
+                    throw overridePresetsError.noTempOverrideFound
                 }
                 }
 
 
                 return result.map { overrideStored in
                 return result.map { overrideStored in
                     OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "")
                     OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "")
                 }
                 }
-            } catch let error as NSError {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
+            } catch {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) Failed to fetch Override: \(error.localizedDescription)"
                 )
                 )
-                return [OverridePreset(id: UUID().uuidString, name: "")]
+                throw error
             }
             }
         }
         }
     }
     }
 
 
-    private func fetchOverrideID(_ preset: OverridePreset) async -> NSManagedObjectID? {
+    private func fetchOverrideID(_ preset: OverridePreset) async throws -> NSManagedObjectID {
         let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
         let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
         fetchRequest.fetchLimit = 1
         fetchRequest.fetchLimit = 1
 
 
-        return await coredataContext.perform {
-            do {
-                return try self.coredataContext.fetch(fetchRequest).first?.objectID
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
+        return try await coredataContext.perform {
+            guard let objectID = try self.coredataContext.fetch(fetchRequest).first?.objectID else {
+                debug(
+                    .default,
+                    "\(DebuggingIdentifiers.failed) No override found for preset: \(preset.name)"
                 )
                 )
-                return nil
+                throw overridePresetsError.noTempOverrideFound
             }
             }
+            return objectID
         }
         }
     }
     }
 
 
@@ -83,13 +87,11 @@ import UIKit
         backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
         backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
             guard backgroundTaskID != .invalid else { return }
             guard backgroundTaskID != .invalid else { return }
             Task {
             Task {
-                // End background task when the time is about to expire
                 UIApplication.shared.endBackgroundTask(backgroundTaskID)
                 UIApplication.shared.endBackgroundTask(backgroundTaskID)
             }
             }
             backgroundTaskID = .invalid
             backgroundTaskID = .invalid
         }
         }
 
 
-        // Defer block to end background task when function exits
         defer {
         defer {
             if backgroundTaskID != .invalid {
             if backgroundTaskID != .invalid {
                 Task {
                 Task {
@@ -101,37 +103,33 @@ import UIKit
 
 
         do {
         do {
             // Get NSManagedObjectID of Preset
             // Get NSManagedObjectID of Preset
-            guard let overrideID = await fetchOverrideID(preset),
-                  let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored
-            else { return false }
+            let overrideID = try await fetchOverrideID(preset)
+            guard let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else {
+                throw overridePresetsError.noTempOverrideFound
+            }
 
 
             // Enable Override
             // Enable Override
             overrideObject.enabled = true
             overrideObject.enabled = true
             overrideObject.date = Date()
             overrideObject.date = Date()
             overrideObject.isUploadedToNS = false
             overrideObject.isUploadedToNS = false
 
 
-            // Disable previous overrides if necessary, without starting a background task
+            // Disable previous overrides if necessary
             await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
             await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
 
 
             if viewContext.hasChanges {
             if viewContext.hasChanges {
                 try viewContext.save()
                 try viewContext.save()
-
-                // Update State variables in OverrideView
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
-
-                // Await the notification
-                print("Waiting for notification...")
                 await awaitNotification(.didUpdateOverrideConfiguration)
                 await awaitNotification(.didUpdateOverrideConfiguration)
-                print("Notification received, continuing...")
-
                 return true
                 return true
             }
             }
+            return false
         } catch {
         } catch {
-            // Handle error and ensure background task is ended
-            debugPrint("Failed to enact Override: \(error.localizedDescription)")
+            debug(
+                .default,
+                "\(DebuggingIdentifiers.failed) Failed to enact override: \(error.localizedDescription)"
+            )
+            return false
         }
         }
-
-        return false
     }
     }
 
 
     func cancelOverride() async {
     func cancelOverride() async {
@@ -167,10 +165,9 @@ import UIKit
             }
             }
         }
         }
 
 
-        // Get NSManagedObjectID of all active overrides
-        let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
-
         do {
         do {
+            // Get NSManagedObjectID of all active overrides
+            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
             // Fetch existing OverrideStored objects
             // Fetch existing OverrideStored objects
             let results = try ids.compactMap { id in
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? OverrideStored
                 try self.viewContext.existingObject(with: id) as? OverrideStored

+ 2 - 2
Trio/Sources/Shortcuts/State/StateIntentRequest.swift

@@ -61,7 +61,7 @@ final class StateIntentRequest: BaseIntentsRequest {
         -> (dateGlucose: Date, glucose: String, trend: String, delta: String)
         -> (dateGlucose: Date, glucose: String, trend: String, delta: String)
     {
     {
         do {
         do {
-            let results = CoreDataStack.shared.fetchEntities(
+            let results = try CoreDataStack.shared.fetchEntities(
                 ofType: GlucoseStored.self,
                 ofType: GlucoseStored.self,
                 onContext: onContext,
                 onContext: onContext,
                 predicate: NSPredicate.predicateFor30MinAgo,
                 predicate: NSPredicate.predicateFor30MinAgo,
@@ -102,7 +102,7 @@ final class StateIntentRequest: BaseIntentsRequest {
     }
     }
 
 
     func getIobAndCob(onContext: NSManagedObjectContext) throws -> (iob: Double, cob: Double) {
     func getIobAndCob(onContext: NSManagedObjectContext) throws -> (iob: Double, cob: Double) {
-        let results = CoreDataStack.shared.fetchEntities(
+        let results = try CoreDataStack.shared.fetchEntities(
             ofType: OrefDetermination.self,
             ofType: OrefDetermination.self,
             onContext: onContext,
             onContext: onContext,
             predicate: NSPredicate.enactedDetermination,
             predicate: NSPredicate.enactedDetermination,

+ 1 - 1
Trio/Sources/Shortcuts/TempPresets/TempPresetIntent.swift

@@ -25,6 +25,6 @@ struct TempPresetsQuery: EntityQuery {
     }
     }
 
 
     func suggestedEntities() async throws -> [TempPreset] {
     func suggestedEntities() async throws -> [TempPreset] {
-        await TempPresetsIntentRequest().fetchAndProcessTempTargets()
+        try await TempPresetsIntentRequest().fetchAndProcessTempTargets()
     }
     }
 }
 }

+ 4 - 5
Trio/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -8,9 +8,9 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         case noDurationDefined
         case noDurationDefined
     }
     }
 
 
-    func fetchAndProcessTempTargets() async -> [TempPreset] {
+    func fetchAndProcessTempTargets() async throws -> [TempPreset] {
         // Fetch all Temp Target Presets via TempTargetStorage
         // Fetch all Temp Target Presets via TempTargetStorage
-        let allTempTargetPresetsIDs = await tempTargetsStorage.fetchForTempTargetPresets()
+        let allTempTargetPresetsIDs = try await tempTargetsStorage.fetchForTempTargetPresets()
 
 
         // Perform the fetch and process on the Core Data context's thread
         // Perform the fetch and process on the Core Data context's thread
         return await coredataContext.perform {
         return await coredataContext.perform {
@@ -198,10 +198,9 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
             }
             }
         }
         }
 
 
-        // Get NSManagedObjectID of all active temp Targets
-        let ids = await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
-
         do {
         do {
+            // Get NSManagedObjectID of all active temp Targets
+            let ids = try await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
             // Fetch existing OverrideStored objects
             // Fetch existing OverrideStored objects
             let results = try ids.compactMap { id in
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? TempTargetStored
                 try self.viewContext.existingObject(with: id) as? TempTargetStored