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

Adapt the remaining storages to the new pattern

Marvin Polscheit 1 месяц назад
Родитель
Сommit
af6c1711a9

+ 29 - 20
Trio/Sources/APS/Storage/CarbsStorage.swift

@@ -33,10 +33,10 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         updateSubject.eraseToAnyPublisher()
     }
 
-    private let context: NSManagedObjectContext
+    private let makeContext: () -> NSManagedObjectContext
 
-    init(resolver: Resolver, context: NSManagedObjectContext? = nil) {
-        self.context = context ?? CoreDataStack.shared.newTaskContext()
+    init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
+        makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
         injectServices(resolver)
     }
 
@@ -74,6 +74,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     private func filterRemoteEntries(entries: [CarbsEntry]) async throws -> [CarbsEntry] {
+        let context = makeContext()
+        context.name = "filterRemoteEntries"
         // Fetch only the date property from Core Data
         guard let existing24hCarbEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
@@ -280,8 +282,10 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     private func saveCarbsToCoreData(entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
         guard let entry = entries.last else { return }
 
+        let context = makeContext()
+        context.name = "saveCarbsToCoreData"
         await context.perform {
-            let newItem = CarbEntryStored(context: self.context)
+            let newItem = CarbEntryStored(context: context)
             newItem.date = entry.actualDate ?? entry.createdAt
             newItem.carbs = Double(truncating: NSDecimalNumber(decimal: entry.carbs))
             newItem.fat = Double(truncating: NSDecimalNumber(decimal: entry.fat ?? 0))
@@ -298,8 +302,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             }
 
             do {
-                guard self.context.hasChanges else { return }
-                try self.context.save()
+                guard context.hasChanges else { return }
+                try context.save()
             } catch {
                 print(error.localizedDescription)
             }
@@ -327,9 +331,11 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             // do NOT set Health and Tidepool flags to ensure they will NOT be uploaded
             return false // return false to continue
         }
+        let context = makeContext()
+        context.name = "saveFPUToCoreDataAsBatchInsert"
         await context.perform {
             do {
-                try self.context.execute(batchInsert)
+                try context.execute(batchInsert)
                 debugPrint("Carbs Storage: \(DebuggingIdentifiers.succeeded) saved fpus to core data")
 
                 // Notify subscriber in Home State Model to update the FPU Array
@@ -345,19 +351,14 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     func deleteCarbsEntryStored(_ treatmentObjectID: NSManagedObjectID) async {
-        // Use injected context if available, otherwise create new task context
-        let taskContext = context != CoreDataStack.shared.newTaskContext()
-            ? context
-            : CoreDataStack.shared.newTaskContext()
-
-        taskContext.name = "deleteContext"
-        taskContext.transactionAuthor = "deleteCarbs"
+        let context = makeContext()
+        context.name = "deleteCarbsEntryStored"
 
         var carbEntryFromCoreData: CarbEntryStored?
 
-        await taskContext.perform {
+        await context.perform {
             do {
-                carbEntryFromCoreData = try taskContext.existingObject(with: treatmentObjectID) as? CarbEntryStored
+                carbEntryFromCoreData = try context.existingObject(with: treatmentObjectID) as? CarbEntryStored
                 guard let carbEntry = carbEntryFromCoreData else {
                     debugPrint("Carb entry for batch delete not found. \(DebuggingIdentifiers.failed)")
                     return
@@ -377,7 +378,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     deleteRequest.resultType = .resultTypeCount
 
                     // execute the batch delete request
-                    let result = try taskContext.execute(deleteRequest) as? NSBatchDeleteResult
+                    let result = try context.execute(deleteRequest) as? NSBatchDeleteResult
                     debugPrint("\(DebuggingIdentifiers.succeeded) Deleted \(result?.result ?? 0) items with FpuID \(fpuID)")
 
                     // Notifiy subscribers of the batch delete
@@ -386,10 +387,10 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 // entry has no fpuID
                 // => it's a carb-only entry. use its ID to for deletion
                 else {
-                    taskContext.delete(carbEntry)
+                    context.delete(carbEntry)
 
-                    guard taskContext.hasChanges else { return }
-                    try taskContext.save()
+                    guard context.hasChanges else { return }
+                    try context.save()
 
                     debugPrint(
                         "CarbsStorage: \(#function) \(DebuggingIdentifiers.succeeded) deleted carb entry from core data"
@@ -403,6 +404,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     func getCarbsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let context = makeContext()
+        context.name = "getCarbsNotYetUploadedToNightscout"
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,
@@ -442,6 +445,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     func getFPUsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let context = makeContext()
+        context.name = "getFPUsNotYetUploadedToNightscout"
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,
@@ -481,6 +486,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     func getCarbsNotYetUploadedToHealth() async throws -> [CarbsEntry] {
+        let context = makeContext()
+        context.name = "getCarbsNotYetUploadedToHealth"
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,
@@ -512,6 +519,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
 
     func getCarbsNotYetUploadedToTidepool() async throws -> [CarbsEntry] {
+        let context = makeContext()
+        context.name = "getCarbsNotYetUploadedToTidepool"
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,

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

@@ -18,14 +18,16 @@ protocol DeterminationStorage {
 
 final class BaseDeterminationStorage: DeterminationStorage, Injectable {
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
-    private let context: NSManagedObjectContext
+    private let makeContext: () -> NSManagedObjectContext
 
-    init(resolver: Resolver, context: NSManagedObjectContext? = nil) {
-        self.context = context ?? CoreDataStack.shared.newTaskContext()
+    init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
+        makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
         injectServices(resolver)
     }
 
     func fetchLastDeterminationObjectID(predicate: NSPredicate) async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchLastDeterminationObjectID"
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
             onContext: context,
@@ -116,19 +118,21 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
 
     // Convert NSSet to array of Ints for Predictions
     func parseForecastValues(ofType type: String, from determinationID: NSManagedObjectID) async -> [Int]? {
+        let context = makeContext()
+        context.name = "parseForecastValues"
         let forecastIDs = await getForecastIDs(for: determinationID, in: context)
 
         var forecastValuesList: [Int] = []
 
         for forecastID in forecastIDs {
             await context.perform {
-                if let forecast = try? self.context.existingObject(with: forecastID) as? Forecast {
+                if let forecast = try? context.existingObject(with: forecastID) as? Forecast {
                     // Filter the forecast based on the type
                     if forecast.type == type {
                         let forecastValueIDs = forecast.forecastValues?.sorted(by: { $0.index < $1.index }).map(\.objectID) ?? []
 
                         for forecastValueID in forecastValueIDs {
-                            if let forecastValue = try? self.context
+                            if let forecastValue = try? context
                                 .existingObject(with: forecastValueID) as? ForecastValue
                             {
                                 let forecastValueInt = Int(forecastValue.value)
@@ -157,9 +161,11 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
             uam: await parseForecastValues(ofType: "uam", from: determinationId)
         )
 
+        let context = makeContext()
+        context.name = "getOrefDeterminationNotYetUploadedToNightscout"
         return await context.perform {
             do {
-                let orefDetermination = try self.context.existingObject(with: determinationId) as? OrefDetermination
+                let orefDetermination = try context.existingObject(with: determinationId) as? OrefDetermination
 
                 // Check if the fetched object is of the expected type
                 if let orefDetermination = orefDetermination {

+ 77 - 37
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -44,10 +44,10 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         static let filterTime: TimeInterval = 3.5 * 60
     }
 
-    private let context: NSManagedObjectContext
+    private let makeContext: () -> NSManagedObjectContext
 
-    init(resolver: Resolver, context: NSManagedObjectContext? = nil) {
-        self.context = context ?? CoreDataStack.shared.newTaskContext()
+    init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
+        makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
         injectServices(resolver)
     }
 
@@ -76,26 +76,31 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     ///  it isn't within 3.5 minutes of an existing glucose reading, which is simple but not perfect.
     ///  But since this is a corner case that really shouldn't happen often, it's good enough.
     func backfillGlucose(_ glucose: [BloodGlucose]) async throws {
+        let context = makeContext()
+        context.name = "backfillGlucose"
+
         try await context.perform {
             // remove already deleted glucose values
             let withoutDeletedGlucose = self.filterGlucoseValues(
                 glucose,
                 fetchRequest: DeletedGlucoseStored.fetchRequest(),
-                timeBuffer: 1
+                timeBuffer: 1,
+                context: context
             )
 
             // check for a 3.5 minute difference between existing values
             let filteredGlucose = self.filterGlucoseValues(
                 withoutDeletedGlucose,
                 fetchRequest: GlucoseStored.fetchRequest(),
-                timeBuffer: 3.5 * 60
+                timeBuffer: 3.5 * 60,
+                context: context
             )
 
             guard !filteredGlucose.isEmpty else { return }
 
             do {
                 // Store glucose values in Core Data
-                try self.storeGlucoseInCoreData(filteredGlucose)
+                try self.storeGlucoseInCoreData(filteredGlucose, context: context)
             } catch {
                 throw CoreDataError.creationError(
                     function: #function,
@@ -106,14 +111,22 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func storeGlucose(_ glucose: [BloodGlucose]) async throws {
+        let context = makeContext()
+        context.name = "storeGlucose"
+
         try await context.perform {
             // Get new glucose values that don't exist yet
-            let newGlucose = self.filterGlucoseValues(glucose, fetchRequest: GlucoseStored.fetchRequest(), timeBuffer: 1)
+            let newGlucose = self.filterGlucoseValues(
+                glucose,
+                fetchRequest: GlucoseStored.fetchRequest(),
+                timeBuffer: 1,
+                context: context
+            )
             guard !newGlucose.isEmpty else { return }
 
             do {
                 // Store glucose values in Core Data
-                try self.storeGlucoseInCoreData(newGlucose)
+                try self.storeGlucoseInCoreData(newGlucose, context: context)
             } catch {
                 throw CoreDataError.creationError(
                     function: #function,
@@ -134,7 +147,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     private func filterGlucoseValues(
         _ glucose: [BloodGlucose],
         fetchRequest: NSFetchRequest<NSFetchRequestResult>,
-        timeBuffer: TimeInterval
+        timeBuffer: TimeInterval,
+        context: NSManagedObjectContext
     ) -> [BloodGlucose] {
         let datesToCheck = glucose.map(\.dateString).sorted()
         guard let firstDate = datesToCheck.first.map({ $0.addingTimeInterval(-timeBuffer) }),
@@ -171,15 +185,15 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         }
     }
 
-    private func storeGlucoseInCoreData(_ glucose: [BloodGlucose]) throws {
+    private func storeGlucoseInCoreData(_ glucose: [BloodGlucose], context: NSManagedObjectContext) throws {
         if glucose.count > 1 {
-            try storeGlucoseBatch(glucose)
+            try storeGlucoseBatch(glucose, context: context)
         } else {
-            try storeGlucoseRegular(glucose)
+            try storeGlucoseRegular(glucose, context: context)
         }
     }
 
-    private func storeGlucoseRegular(_ glucose: [BloodGlucose]) throws {
+    private func storeGlucoseRegular(_ glucose: [BloodGlucose], context: NSManagedObjectContext) throws {
         for entry in glucose {
             let glucoseEntry = GlucoseStored(context: context)
             configureGlucoseEntry(glucoseEntry, with: entry)
@@ -189,7 +203,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         try context.save()
     }
 
-    private func storeGlucoseBatch(_ glucose: [BloodGlucose]) throws {
+    private func storeGlucoseBatch(_ glucose: [BloodGlucose], context: NSManagedObjectContext) throws {
         var remainingGlucose = glucose
         let batchInsert = NSBatchInsertRequest(
             entity: GlucoseStored.entity(),
@@ -287,8 +301,11 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func addManualGlucose(glucose: Int) {
+        let context = makeContext()
+        context.name = "addManualGlucose"
+
         context.perform {
-            let newItem = GlucoseStored(context: self.context)
+            let newItem = GlucoseStored(context: context)
             newItem.id = UUID()
             newItem.date = Date()
             newItem.glucose = Int16(glucose)
@@ -298,8 +315,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
             newItem.isUploadedToTidepool = false
 
             do {
-                guard self.context.hasChanges else { return }
-                try self.context.save()
+                guard context.hasChanges else { return }
+                try context.save()
 
                 // Glucose subscribers already listen to the update publisher, so call here to update glucose-related data.
                 self.updateSubject.send()
@@ -317,8 +334,10 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func syncDate() -> Date {
+        let context = makeContext()
+        context.name = "syncDate"
+
         // Optimize fetch request to only get the date
-        let taskContext = CoreDataStack.shared.newTaskContext()
         let fr = NSFetchRequest<NSDictionary>(entityName: "GlucoseStored")
         fr.predicate = NSPredicate.predicateForOneDayAgo
         fr.propertiesToFetch = ["date"]
@@ -328,9 +347,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
         var fetchedDate: Date = .distantPast
 
-        taskContext.performAndWait {
+        context.performAndWait {
             do {
-                if let result = try taskContext.fetch(fr).first,
+                if let result = try context.fetch(fr).first,
                    let date = result["date"] as? Date
                 {
                     fetchedDate = date
@@ -344,6 +363,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func lastGlucoseDate() -> Date? {
+        let context = makeContext()
+        context.name = "lastGlucoseDate"
+
         let fetchRequest = GlucoseStored.fetchRequest()
         fetchRequest.predicate = NSPredicate.predicateForOneDayAgo
         fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)]
@@ -352,7 +374,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         var date: Date?
         context.performAndWait {
             do {
-                let results = try self.context.fetch(fetchRequest)
+                let results = try context.fetch(fetchRequest)
                 date = results.first?.date
             } catch let error as NSError {
                 debug(.storage, "Fetch error: \(DebuggingIdentifiers.failed) \(error), \(error.userInfo)")
@@ -382,7 +404,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return filtered
     }
 
-    func fetchLatestGlucose() throws -> GlucoseStored? {
+    func fetchLatestGlucose(context: NSManagedObjectContext) throws -> GlucoseStored? {
         let predicate = NSPredicate.predicateFor20MinAgo
         return (try CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
@@ -397,6 +419,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
     func getGlucoseNotYetUploadedToNightscout() async throws -> [BloodGlucose] {
+        let context = makeContext()
+        context.name = "getGlucoseNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -430,6 +455,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch manual glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
     func getManualGlucoseNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let context = makeContext()
+        context.name = "getManualGlucoseNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -486,6 +514,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of BloodGlucose to ensure the correct format for the NS Upload
     func getGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose] {
+        let context = makeContext()
+        context.name = "getGlucoseNotYetUploadedToHealth"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -518,6 +549,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch manual glucose that is not uploaded to Nightscout yet
     /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
     func getManualGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose] {
+        let context = makeContext()
+        context.name = "getManualGlucoseNotYetUploadedToHealth"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -550,6 +584,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch glucose that is not uploaded to Tidepool yet
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for Tidepool upload
     func getGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample] {
+        let context = makeContext()
+        context.name = "getGlucoseNotYetUploadedToTidepool"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -583,6 +620,9 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     // Fetch manual glucose that is not uploaded to Tidepool yet
     /// - Returns: Array of StoredGlucoseSample to ensure the correct format for the Tidepool upload
     func getManualGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample] {
+        let context = makeContext()
+        context.name = "getManualGlucoseNotYetUploadedToTidepool"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
@@ -613,16 +653,13 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func deleteGlucose(_ treatmentObjectID: NSManagedObjectID) async {
-        // Use injected context if available, otherwise create new task context
-        let taskContext = context != CoreDataStack.shared.newTaskContext()
-            ? context
-            : CoreDataStack.shared.newTaskContext()
-        taskContext.name = "deleteContext"
-        taskContext.transactionAuthor = "deleteGlucose"
-
-        await taskContext.perform {
+        let context = makeContext()
+        context.name = "deleteGlucose"
+        context.transactionAuthor = "deleteGlucose"
+
+        await context.perform {
             do {
-                let result = try taskContext.existingObject(with: treatmentObjectID) as? GlucoseStored
+                let result = try context.existingObject(with: treatmentObjectID) as? GlucoseStored
 
                 guard let glucoseToDelete = result else {
                     debugPrint("Data Table State: \(#function) \(DebuggingIdentifiers.failed) glucose not found in core data")
@@ -631,16 +668,16 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
                 // Create a new DeletedGlucoseStored object and copy the properties
                 if let date = glucoseToDelete.date {
-                    let deletedEntry = DeletedGlucoseStored(context: taskContext)
+                    let deletedEntry = DeletedGlucoseStored(context: context)
                     deletedEntry.date = date
                     deletedEntry.glucose = glucoseToDelete.glucose
                     deletedEntry.isManualGlucoseEntry = glucoseToDelete.isManual
                 }
 
-                taskContext.delete(glucoseToDelete)
+                context.delete(glucoseToDelete)
 
-                guard taskContext.hasChanges else { return }
-                try taskContext.save()
+                guard context.hasChanges else { return }
+                try context.save()
                 debugPrint("\(#file) \(#function) \(DebuggingIdentifiers.succeeded) deleted glucose from core data")
             } catch {
                 debugPrint(
@@ -651,10 +688,13 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     var alarm: GlucoseAlarm? {
+        let context = makeContext()
+        context.name = "alarm"
+
         /// glucose can not be older than 20 minutes due to the predicate in the fetch request
-        context.performAndWait {
+        return context.performAndWait {
             do {
-                guard let glucose = try fetchLatestGlucose() else { return nil }
+                guard let glucose = try fetchLatestGlucose(context: context) else { return nil }
 
                 let glucoseValue = glucose.glucose
 

+ 35 - 12
Trio/Sources/APS/Storage/OverrideStorage.swift

@@ -25,10 +25,10 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     @Injected() private var settingsManager: SettingsManager!
 
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
-    private let context: NSManagedObjectContext
+    private let makeContext: () -> NSManagedObjectContext
 
-    init(resolver: Resolver, context: NSManagedObjectContext? = nil) {
-        self.context = context ?? CoreDataStack.shared.newTaskContext()
+    init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
+        makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
         injectServices(resolver)
     }
 
@@ -41,6 +41,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func fetchLastCreatedOverride() async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchLastCreatedOverride"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -63,6 +66,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func loadLatestOverrideConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "loadLatestOverrideConfigurations"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -83,6 +89,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
 
     /// Returns the NSManagedObjectID of the Override Presets
     func fetchForOverridePresets() async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchForOverridePresets"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -114,8 +123,11 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
             presetCount = presets.count
         }
 
+        let context = makeContext()
+        context.name = "storeOverride"
+
         try await context.perform {
-            let newOverride = OverrideStored(context: self.context)
+            let newOverride = OverrideStored(context: context)
 
             // override key meta data
             if !override.name.isEmpty {
@@ -163,8 +175,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
                 newOverride.smbIsScheduledOff = false
             }
 
-            guard self.context.hasChanges else { return }
-            try self.context.save()
+            guard context.hasChanges else { return }
+            try context.save()
         }
     }
 
@@ -213,12 +225,8 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
 
     /// - Parameter: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
     func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
-        // Use injected context if available, otherwise create new task context
-        let taskContext = context != CoreDataStack.shared.newTaskContext()
-            ? context
-            : CoreDataStack.shared.newTaskContext()
-
-        taskContext.name = "deleteContext"
+        let taskContext = makeContext()
+        taskContext.name = "deleteOverridePreset"
         taskContext.transactionAuthor = "deleteOverride"
 
         await taskContext.perform {
@@ -243,6 +251,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func getOverridesNotYetUploadedToNightscout() async throws -> [NightscoutExercise] {
+        let context = makeContext()
+        context.name = "getOverridesNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -271,6 +282,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func getOverrideRunsNotYetUploadedToNightscout() async throws -> [NightscoutExercise] {
+        let context = makeContext()
+        context.name = "getOverrideRunsNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideRunStored.self,
             onContext: context,
@@ -328,6 +342,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
 
         /// Build a predicate to fetch a stored override (from OverrideStored) whose date is within the tolerance window.
         let predicate = NSPredicate(format: "date >= %@ AND date <= %@", lowerBound as NSDate, upperBound as NSDate)
+        let context = makeContext()
+        context.name = "checkIfShouldDeleteNightscoutOverrideEntry"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -360,6 +377,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func getPresetOverridesForNightscout() async throws -> [NightscoutPresetOverride] {
+        let context = makeContext()
+        context.name = "getPresetOverridesForNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,
@@ -389,6 +409,9 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     }
 
     func fetchLatestActiveOverride() async throws -> NSManagedObjectID? {
+        let context = makeContext()
+        context.name = "fetchLatestActiveOverride"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OverrideStored.self,
             onContext: context,

+ 39 - 16
Trio/Sources/APS/Storage/TempTargetsStorage.swift

@@ -33,14 +33,17 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
 
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
-    private let context: NSManagedObjectContext
+    private let makeContext: () -> NSManagedObjectContext
 
-    init(resolver: Resolver, context: NSManagedObjectContext? = nil) {
-        self.context = context ?? CoreDataStack.shared.newTaskContext()
+    init(resolver: Resolver, contextProvider: (() -> NSManagedObjectContext)? = nil) {
+        makeContext = contextProvider ?? { CoreDataStack.shared.newTaskContext() }
         injectServices(resolver)
     }
 
     func loadLatestTempTargetConfigurations(fetchLimit: Int) async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "loadLatestTempTargetConfigurations"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             onContext: context,
@@ -61,6 +64,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
 
     /// Returns the NSManagedObjectID of the Temp Target Presets
     func fetchForTempTargetPresets() async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchForTempTargetPresets"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             onContext: context,
@@ -79,6 +85,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func fetchScheduledTempTargets() async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchScheduledTempTargets"
+
         let scheduledTempTargets = NSPredicate(format: "date > %@", Date() as NSDate)
 
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
@@ -99,6 +108,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func fetchScheduledTempTarget(for targetDate: Date) async throws -> [NSManagedObjectID] {
+        let context = makeContext()
+        context.name = "fetchScheduledTempTarget"
+
         let predicate = NSPredicate(format: "date == %@", targetDate as NSDate)
 
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
@@ -120,6 +132,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func storeTempTarget(tempTarget: TempTarget) async throws {
+        let context = makeContext()
+        context.name = "storeTempTarget"
+
         var presetCount = -1
         if tempTarget.isPreset == true {
             let presets = try await fetchForTempTargetPresets()
@@ -127,7 +142,7 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         }
 
         try await context.perform {
-            let newTempTarget = TempTargetStored(context: self.context)
+            let newTempTarget = TempTargetStored(context: context)
             newTempTarget.date = tempTarget.createdAt
             newTempTarget.id = UUID()
             newTempTarget.enabled = tempTarget.enabled ?? false
@@ -151,8 +166,8 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
             }
 
             do {
-                guard self.context.hasChanges else { return }
-                try self.context.save()
+                guard context.hasChanges else { return }
+                try context.save()
             } catch let error as NSError {
                 debug(.default, "\(DebuggingIdentifiers.failed) Failed to save new temp target with error: \(error.userInfo)")
                 throw error
@@ -182,13 +197,16 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func existsTempTarget(with date: Date) async -> Bool {
-        await context.perform {
+        let context = makeContext()
+        context.name = "existsTempTarget"
+
+        return await context.perform {
             // Fetch all Temp Targets with the given date
             let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
             fetchRequest.predicate = NSPredicate(format: "date == %@", date as NSDate)
 
             do {
-                let results = try self.context.fetch(fetchRequest)
+                let results = try context.fetch(fetchRequest)
                 return !results.isEmpty
             } catch let error as NSError {
                 debugPrint("\(DebuggingIdentifiers.failed) Failed to check for existing Temp Target: \(error)")
@@ -226,22 +244,21 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func deleteTempTargetPreset(_ objectID: NSManagedObjectID) async {
-        let taskContext = context != CoreDataStack.shared.newTaskContext()
-            ? context
-            : CoreDataStack.shared.newTaskContext()
+        let context = makeContext()
+        context.name = "deleteTempTargetPreset"
 
-        await taskContext.perform {
+        await context.perform {
             do {
-                let result = try taskContext.existingObject(with: objectID) as? TempTargetStored
+                let result = try context.existingObject(with: objectID) as? TempTargetStored
                 guard let tempTarget = result else {
                     debug(.default, "\(DebuggingIdentifiers.failed) Temp Target for batch delete not found.")
                     return
                 }
 
-                taskContext.delete(tempTarget)
+                context.delete(tempTarget)
 
-                guard taskContext.hasChanges else { return }
-                try taskContext.save()
+                guard context.hasChanges else { return }
+                try context.save()
             } catch {
                 debug(.default, "\(DebuggingIdentifiers.failed) Failed to delete Temp Target: \(error)")
             }
@@ -271,6 +288,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func getTempTargetsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let context = makeContext()
+        context.name = "getTempTargetsNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetStored.self,
             onContext: context,
@@ -308,6 +328,9 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     }
 
     func getTempTargetRunsNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
+        let context = makeContext()
+        context.name = "getTempTargetRunsNotYetUploadedToNightscout"
+
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: TempTargetRunStored.self,
             onContext: context,

+ 5 - 5
TrioTests/CoreDataTests/TestAssembly.swift

@@ -18,27 +18,27 @@ class TestAssembly: Assembly {
 
         // Override DeterminationStorage registration for tests
         container.register(DeterminationStorage.self) { r in
-            BaseDeterminationStorage(resolver: r, context: self.testContext)
+            BaseDeterminationStorage(resolver: r, contextProvider: { self.testContext })
         }.inObjectScope(.container)
 
         // Override CarbsStorage registration for tests
         container.register(CarbsStorage.self) { r in
-            BaseCarbsStorage(resolver: r, context: self.testContext)
+            BaseCarbsStorage(resolver: r, contextProvider: { self.testContext })
         }.inObjectScope(.container)
 
         // Override GlucoseStorage registration for tests
         container.register(GlucoseStorage.self) { r in
-            BaseGlucoseStorage(resolver: r, context: self.testContext)
+            BaseGlucoseStorage(resolver: r, contextProvider: { self.testContext })
         }.inObjectScope(.container)
 
         // Override TempTargetStorage registration for tests
         container.register(TempTargetsStorage.self) { r in
-            BaseTempTargetsStorage(resolver: r, context: self.testContext)
+            BaseTempTargetsStorage(resolver: r, contextProvider: { self.testContext })
         }.inObjectScope(.container)
 
         // Override OverrideStorage registration for tests
         container.register(OverrideStorage.self) { r in
-            BaseOverrideStorage(resolver: r, context: self.testContext)
+            BaseOverrideStorage(resolver: r, contextProvider: { self.testContext })
         }.inObjectScope(.container)
     }
 }