polscm32 2 年之前
父节点
当前提交
9641f1034d
共有 31 个文件被更改,包括 361 次插入136 次删除
  1. 6 4
      FreeAPS/Sources/APS/APSManager.swift
  2. 1 1
      FreeAPS/Sources/APS/DeviceDataManager.swift
  3. 1 1
      FreeAPS/Sources/APS/FetchGlucoseManager.swift
  4. 11 7
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  5. 6 2
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  6. 31 3
      FreeAPS/Sources/APS/Storage/GlucoseStorage.swift
  7. 3 2
      FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift
  8. 1 1
      FreeAPS/Sources/Application/FreeAPSApp.swift
  9. 1 1
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetStateModel.swift
  10. 2 2
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  11. 1 1
      FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift
  12. 1 1
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  13. 1 1
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  14. 31 12
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  15. 7 7
      FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift
  16. 8 4
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  17. 18 35
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  18. 1 1
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  19. 1 1
      FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift
  20. 1 1
      FreeAPS/Sources/Services/Calendar/CalendarManager.swift
  21. 2 2
      FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift
  22. 1 1
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  23. 1 1
      FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift
  24. 1 2
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  25. 1 1
      FreeAPS/Sources/Shortcuts/BaseIntentsRequest.swift
  26. 1 1
      FreeAPS/Sources/Shortcuts/State/StateIntentRequest.swift
  27. 215 37
      Model/CoreDataStack.swift
  28. 1 1
      Model/Helper/CoreDataStorage.swift
  29. 0 1
      Model/Helper/Determination+helper.swift
  30. 0 1
      Model/Helper/Forecast+helper.swift
  31. 5 0
      Model/Helper/PumpEvent+helper.swift

+ 6 - 4
FreeAPS/Sources/APS/APSManager.swift

@@ -79,8 +79,8 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    let viewContext = CoreDataStack.shared.viewContext
-    let privateContext = CoreDataStack.shared.backgroundContext
+    let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+    let privateContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private var openAPS: OpenAPS!
 
@@ -727,7 +727,8 @@ final class BaseAPSManager: APSManager, Injectable {
                 determinationUpdated.received = received
 
                 do {
-                    try CoreDataStack.shared.saveContext()
+                    guard privateContext.hasChanges else { return }
+                    try privateContext.save()
                     debugPrint("Update successful in reportEnacted() \(DebuggingIdentifiers.succeeded)")
                 } catch {
                     debugPrint(
@@ -914,8 +915,9 @@ final class BaseAPSManager: APSManager, Injectable {
 
     // fetch glucose for time interval
     func fetchGlucose(predicate: NSPredicate, fetchLimit: Int? = nil, batchSize: Int? = nil) -> [GlucoseStored] {
-        CoreDataStack.shared.fetchEntities(
+        CoreDataStack.shared.fetchEntities2(
             ofType: GlucoseStored.self,
+            onContext: privateContext,
             predicate: predicate,
             key: "date",
             ascending: false,

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

@@ -73,7 +73,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     @SyncAccess private var pumpUpdateCancellable: AnyCancellable?
     private var pumpUpdatePromise: Future<Bool, Never>.Promise?
     @SyncAccess var loopInProgress: Bool = false
-    private let privateContext = CoreDataStack.shared.backgroundContext
+    private let privateContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     var pumpManager: PumpManagerUI? {
         didSet {

+ 1 - 1
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -33,7 +33,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     private lazy var simulatorSource = GlucoseSimulatorSource()
 
     // TODO: - test if we need to use the viewContext here
-    private let context = CoreDataStack.shared.backgroundContext
+    private let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     init(resolver: Resolver) {
         injectServices(resolver)

+ 11 - 7
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -9,7 +9,7 @@ final class OpenAPS {
 
     private let storage: FileStorage
 
-    let context = CoreDataStack.shared.backgroundContext
+    let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     let jsonConverter = JSONConverter()
 
@@ -83,7 +83,8 @@ final class OpenAPS {
 
     func attemptToSaveContext() {
         do {
-            try CoreDataStack.shared.saveContext()
+            guard context.hasChanges else { return }
+            try context.save()
         } catch {
             print(error.localizedDescription)
         }
@@ -94,8 +95,9 @@ final class OpenAPS {
         var glucoseAsJSON: String?
 
         context.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities(
+            let results = CoreDataStack.shared.fetchEntities2(
                 ofType: GlucoseStored.self,
+                onContext: context,
                 predicate: NSPredicate.predicateForSixHoursAgo,
                 key: "date",
                 ascending: false,
@@ -116,8 +118,9 @@ final class OpenAPS {
         var carbsAsJSON: String?
 
         context.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities(
+            let results = CoreDataStack.shared.fetchEntities2(
                 ofType: CarbEntryStored.self,
+                onContext: context,
                 predicate: NSPredicate.predicateForOneDayAgo,
                 key: "date",
                 ascending: false
@@ -131,8 +134,9 @@ final class OpenAPS {
     }
 
     private func fetchPumpHistoryObjectIDs() -> [NSManagedObjectID]? {
-        let results = CoreDataStack.shared.fetchEntities(
+        let results = CoreDataStack.shared.fetchEntities2(
             ofType: PumpEventStored.self,
+            onContext: context,
             predicate: NSPredicate.pumpHistoryLast24h,
             key: "timestamp",
             ascending: false,
@@ -146,10 +150,10 @@ final class OpenAPS {
         guard !pumpHistoryObjectIDs.isEmpty else { return "{}" }
 
         // Execute all operations on the background context
-        let jsonResult = CoreDataStack.shared.backgroundContext.performAndWait {
+        let jsonResult = context.performAndWait {
             // Load the pump events from the object IDs
             let pumpHistory: [PumpEventStored] = pumpHistoryObjectIDs
-                .compactMap { CoreDataStack.shared.backgroundContext.object(with: $0) as? PumpEventStored }
+                .compactMap { context.object(with: $0) as? PumpEventStored }
 
             // Create the DTOs
             let dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in

+ 6 - 2
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -21,7 +21,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var settings: SettingsManager!
 
-    let coredataContext = CoreDataStack.shared.backgroundContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     init(resolver: Resolver) {
         injectServices(resolver)
@@ -153,7 +153,11 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             newItem.id = UUID()
             newItem.isFPU = false
             do {
-                try CoreDataStack.shared.saveContext()
+//                try CoreDataStack.shared.saveContext()
+                guard self.coredataContext.hasChanges else {
+                    return
+                }
+                try self.coredataContext.save()
             } catch {
                 print(error.localizedDescription)
             }

+ 31 - 3
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -24,7 +24,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var settingsManager: SettingsManager!
 
-    let coredataContext = CoreDataStack.shared.backgroundContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private enum Config {
         static let filterTime: TimeInterval = 3.5 * 60
@@ -164,13 +164,41 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     func syncDate() -> Date {
+        let fr = GlucoseStored.fetchRequest()
+        fr.predicate = NSPredicate.predicateForOneDayAgo
+        fr.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)]
+        fr.fetchLimit = 1
+
+        var date: Date?
         coredataContext.performAndWait {
-            fetchGlucose().first?.date ?? .distantPast
+            do {
+                let results = try self.coredataContext.fetch(fr)
+                date = results.first?.date
+            } catch let error as NSError {
+                print("Fetch error: \(DebuggingIdentifiers.failed) \(error.localizedDescription), \(error.userInfo)")
+            }
         }
+
+        return date ?? .distantPast
     }
 
     func lastGlucoseDate() -> Date {
-        fetchGlucose().first?.date ?? .distantPast
+        let fr = GlucoseStored.fetchRequest()
+        fr.predicate = NSPredicate.predicateForOneDayAgo
+        fr.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)]
+        fr.fetchLimit = 1
+
+        var date: Date?
+        coredataContext.performAndWait {
+            do {
+                let results = try self.coredataContext.fetch(fr)
+                date = results.first?.date
+            } catch let error as NSError {
+                print("Fetch error: \(DebuggingIdentifiers.failed) \(error.localizedDescription), \(error.userInfo)")
+            }
+        }
+
+        return date ?? .distantPast
     }
 
     func isGlucoseFresh() -> Bool {

+ 3 - 2
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -29,7 +29,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
     typealias PumpEvent = PumpEventStored.EventType
     typealias TempType = PumpEventStored.TempType
 
-    private let context = CoreDataStack.shared.backgroundContext
+    private let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     func storePumpEvents(_ events: [NewPumpEvent]) {
         processQueue.async {
@@ -57,7 +57,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         // TODO: - do we need duration here?
 
                         do {
-                            try CoreDataStack.shared.saveContext()
+                            guard self.context.hasChanges else { return }
+                            try self.context.save()
                         } catch {
                             print(error.localizedDescription)
                         }

+ 1 - 1
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -60,7 +60,7 @@ import Swinject
     var body: some Scene {
         WindowGroup {
             Main.RootView(resolver: resolver)
-                .environment(\.managedObjectContext, CoreDataStack.shared.viewContext)
+                .environment(\.managedObjectContext, CoreDataStack.shared.persistentContainer.viewContext)
                 .environmentObject(Icons())
                 .onOpenURL(perform: handleURL)
         }

+ 1 - 1
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetStateModel.swift

@@ -6,7 +6,7 @@ extension AddTempTarget {
         @Injected() private var storage: TempTargetsStorage!
         @Injected() var apsManager: APSManager!
 
-        let coredataContext = CoreDataStack.shared.backgroundContext
+        let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         @Published var low: Decimal = 0
         // @Published var target: Decimal = 0

+ 2 - 2
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -97,8 +97,8 @@ extension Bolus {
 
         let now = Date.now
 
-        let context = CoreDataStack.shared.viewContext
-        let backgroundContext = CoreDataStack.shared.backgroundContext
+        let context = CoreDataStack.shared.persistentContainer.viewContext
+        let backgroundContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         typealias PumpEvent = PumpEventStored.EventType
 

+ 1 - 1
FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -16,7 +16,7 @@ extension Calibrations {
         var units: GlucoseUnits = .mmolL
 
         // TODO: - test if we need to use the viewContext here
-        private let context = CoreDataStack.shared.backgroundContext
+        private let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         override func subscribe() {
             units = settingsManager.settings.units

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -10,7 +10,7 @@ extension DataTable {
         @Injected() var pumpHistoryStorage: PumpHistoryStorage!
         @Injected() var healthKitManager: HealthKitManager!
 
-        let coredataContext = CoreDataStack.shared.viewContext
+        let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
         @Published var mode: Mode = .treatments
         @Published var treatments: [Treatment] = []

+ 1 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -73,7 +73,7 @@ extension Home {
 
         @Published var waitForSuggestion: Bool = false
 
-        let context = CoreDataStack.shared.backgroundContext
+        let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         override func subscribe() {
             setupBasals()

+ 31 - 12
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -83,7 +83,7 @@ struct MainChartView: View {
 
     private let now = Date.now
 
-    private let context = CoreDataStack.shared.viewContext
+    private let context = CoreDataStack.shared.persistentContainer.viewContext
 
     @Environment(\.colorScheme) var colorScheme
     @Environment(\.calendar) var calendar
@@ -100,6 +100,26 @@ struct MainChartView: View {
         animation: Animation.bouncy
     ) var manualGlucoseFromPersistence: FetchedResults<GlucoseStored>
 
+    @FetchRequest(
+        fetchRequest: GlucoseStored.fetch(NSPredicate.glucose, ascending: true),
+        animation: Animation.bouncy
+    ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
+
+    @FetchRequest(
+        fetchRequest: CarbEntryStored.fetch(NSPredicate.carbsForChart, ascending: true),
+        animation: Animation.bouncy
+    ) var carbsFromPersistence: FetchedResults<CarbEntryStored>
+
+    @FetchRequest(
+        fetchRequest: CarbEntryStored.fetch(NSPredicate.fpusForChart, ascending: true),
+        animation: Animation.bouncy
+    ) var fpusFromPersistence: FetchedResults<CarbEntryStored>
+
+    @FetchRequest(
+        fetchRequest: OrefDetermination.fetch(NSPredicate.predicateFor30MinAgoForDetermination),
+        animation: .bouncy
+    ) var determination: FetchedResults<OrefDetermination>
+
     private var bolusFormatter: NumberFormatter {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
@@ -136,7 +156,7 @@ struct MainChartView: View {
         if let selection = selection {
             let lowerBound = selection.addingTimeInterval(-120)
             let upperBound = selection.addingTimeInterval(120)
-            return state.glucoseFromPersistence.first { $0.date ?? now >= lowerBound && $0.date ?? now <= upperBound }
+            return glucoseFromPersistence.first { $0.date ?? now >= lowerBound && $0.date ?? now <= upperBound }
         } else {
             return nil
         }
@@ -153,12 +173,12 @@ struct MainChartView: View {
                         updateStartEndMarkers()
                         yAxisChartData()
                         scroller.scrollTo("MainChart", anchor: .trailing)
-                    }.onChange(of: state.glucoseFromPersistence) { _ in
+                    }.onChange(of: glucoseFromPersistence.map(\.id)) { _ in
                         updateStartEndMarkers()
                         yAxisChartData()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
-                    .onChange(of: state.determinationsFromCoreData) { _ in
+                    .onChange(of: determination.map(\.id)) { _ in
                         updateStartEndMarkers()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
@@ -369,7 +389,7 @@ extension MainChartView {
 
     private func drawCarbs() -> some ChartContent {
         /// carbs
-        ForEach(state.carbsFromPersistence) { carb in
+        ForEach(carbsFromPersistence) { carb in
             let carbAmount = carb.carbs
             let yPosition = units == .mgdL ? 60 : 3.33
 
@@ -388,7 +408,7 @@ extension MainChartView {
 
     private func drawFpus() -> some ChartContent {
         /// fpus
-        ForEach(state.fpusFromPersistence, id: \.id) { fpu in
+        ForEach(fpusFromPersistence, id: \.id) { fpu in
             let fpuAmount = fpu.carbs
             let size = (Config.fpuSize + CGFloat(fpuAmount) * Config.carbsScale) * 1.8
             let yPosition = units == .mgdL ? 60 : 3.33
@@ -405,7 +425,7 @@ extension MainChartView {
     private func drawGlucose() -> some ChartContent {
         /// glucose point mark
         /// filtering for high and low bounds in settings
-        ForEach(state.glucoseFromPersistence) { item in
+        ForEach(glucoseFromPersistence) { item in
             if smooth {
                 if item.glucose > Int(highGlucose) {
                     PointMark(
@@ -500,12 +520,11 @@ extension MainChartView {
     }
 
     private func preprocessForecastData() -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] {
-        state.determinationsFromCoreData
+        determination
             .compactMap { determination -> NSManagedObjectID? in
                 determination.objectID
             }
             .flatMap { determinationID -> [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)] in
-                let context = CoreDataStack.shared.viewContext
                 let forecasts = getForecasts(for: determinationID, in: context)
 
                 return forecasts.flatMap { forecast in
@@ -654,12 +673,12 @@ extension MainChartView {
 
     /// calculates the glucose value thats the nearest to parameter 'time'
     private func timeToNearestGlucose(time: TimeInterval) -> GlucoseStored? {
-        guard !state.glucoseFromPersistence.isEmpty else {
+        guard !glucoseFromPersistence.isEmpty else {
             return nil
         }
 
         // sort by date
-        let sortedGlucose = state.glucoseFromPersistence
+        let sortedGlucose = glucoseFromPersistence
             .sorted { $0.date?.timeIntervalSince1970 ?? 0 < $1.date?.timeIntervalSince1970 ?? 0 }
 
         var low = 0
@@ -880,7 +899,7 @@ extension MainChartView {
     // MARK: - Chart formatting
 
     private func yAxisChartData() {
-        let glucoseMapped = state.glucoseFromPersistence.map(\.glucose)
+        let glucoseMapped = glucoseFromPersistence.map(\.glucose)
         guard let minGlucose = glucoseMapped.min(), let maxGlucose = glucoseMapped.max() else {
             // default values
             minValue = 45 * conversionFactor - 20 * conversionFactor

+ 7 - 7
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -7,7 +7,7 @@ struct CurrentGlucoseView: View {
     @Binding var alarm: GlucoseAlarm?
     @Binding var lowGlucose: Decimal
     @Binding var highGlucose: Decimal
-    var glucoseFromPersistence: [GlucoseStored]
+//    var glucoseFromPersistence: [GlucoseStored]
 
     @State private var rotationDegrees: Double = 0.0
     @State private var angularGradient = AngularGradient(colors: [
@@ -21,12 +21,12 @@ struct CurrentGlucoseView: View {
 
     @Environment(\.colorScheme) var colorScheme
 
-//    @FetchRequest(
-//        entity: GlucoseStored.entity(),
-//        sortDescriptors: [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)],
-//        predicate: NSPredicate.predicateFor30MinAgo,
-//        animation: Animation.bouncy
-//    ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
+    @FetchRequest(
+        entity: GlucoseStored.entity(),
+        sortDescriptors: [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: false)],
+        predicate: NSPredicate.predicateFor30MinAgo,
+        animation: Animation.bouncy
+    ) var glucoseFromPersistence: FetchedResults<GlucoseStored>
 
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()

+ 8 - 4
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -8,13 +8,17 @@ struct LoopView: View {
         static let lag: TimeInterval = 30
     }
 
-    @Binding var determination: OrefDetermination?
     @Binding var closedLoop: Bool
     @Binding var timerDate: Date
     @Binding var isLooping: Bool
     @Binding var lastLoopDate: Date
     @Binding var manualTempBasal: Bool
 
+    @FetchRequest(
+        fetchRequest: OrefDetermination.fetch(NSPredicate.enactedDetermination),
+        animation: .bouncy
+    ) var determination: FetchedResults<OrefDetermination>
+
     private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()
         formatter.timeStyle = .short
@@ -36,7 +40,7 @@ struct LoopView: View {
                 Text("looping")
             } else if manualTempBasal {
                 Text("Manual")
-            } else if determination?
+            } else if determination.first?
                 .deliverAt !=
                 nil
             {
@@ -60,7 +64,7 @@ struct LoopView: View {
     }
 
     private var color: Color {
-        guard determination?.timestamp != nil
+        guard determination.first?.timestamp != nil
         else {
             // previously the .timestamp property was used here because this only gets updated when the reportenacted function in the aps manager gets called
             return .secondary
@@ -75,7 +79,7 @@ struct LoopView: View {
         let delta = timerDate.timeIntervalSince(lastLoopDate) - Config.lag
 
         if delta <= 5.minutes.timeInterval {
-            guard determination?.timestamp != nil else {
+            guard determination.first?.timestamp != nil else {
                 return .loopYellow
             }
             return .loopGreen

+ 18 - 35
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -42,12 +42,15 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var fetchedPercent: FetchedResults<Override>
 
-//        @FetchRequest(
-//            entity: OrefDetermination.entity(),
-//            sortDescriptors: [NSSortDescriptor(key: "deliverAt", ascending: false)],
-//            predicate: NSPredicate.predicateFor30MinAgoForDetermination,
-//            animation: Animation.bouncy
-//        ) var determination: FetchedResults<OrefDetermination>
+        @FetchRequest(
+            fetchRequest: OrefDetermination.fetch(NSPredicate.enactedDetermination),
+            animation: .bouncy
+        ) var determination: FetchedResults<OrefDetermination>
+
+        @FetchRequest(
+            fetchRequest: PumpEventStored.fetch(NSPredicate.recentPumpHistory, ascending: false, fetchLimit: 1),
+            animation: .bouncy
+        ) var recentPumpHistory: FetchedResults<PumpEventStored>
 
         @FetchRequest(
             entity: OverridePresets.entity(),
@@ -145,32 +148,13 @@ extension Home {
             }
         }
 
-//        private var determination: OrefDetermination? {
-//            guard let determinationObjectID = state.determinationsFromPersistence.first else {
-//                return nil
-//            }
-//            return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
-//        }
-
-//        private var determinationAsBinding: Binding<OrefDetermination?> {
-//            Binding<OrefDetermination?>(
-//                get: {
-//                    guard let determinationObjectID = state.determinationsFromPersistence.first else {
-//                        return nil
-//                    }
-//                    return CoreDataStack.shared.backgroundContext.object(with: determinationObjectID) as? OrefDetermination
-//                }, set: { _ in }
-//            )
-//        }
-
         var glucoseView: some View {
             CurrentGlucoseView(
                 timerDate: $state.timerDate,
                 units: $state.units,
                 alarm: $state.alarm,
                 lowGlucose: $state.lowGlucose,
-                highGlucose: $state.highGlucose,
-                glucoseFromPersistence: state.glucoseFromPersistence
+                highGlucose: $state.highGlucose
             ).scaleEffect(0.9)
                 .onTapGesture {
                     if state.alarm == nil {
@@ -206,7 +190,7 @@ extension Home {
         }
 
         var tempBasalString: String? {
-            guard let tempRate = state.tempRate else {
+            guard let tempRate = recentPumpHistory.first?.tempBasal?.rate else {
                 return nil
             }
             let rateString = numberFormatter.string(from: tempRate as NSNumber) ?? "0"
@@ -438,7 +422,6 @@ extension Home {
             VStack(alignment: .leading, spacing: 20) {
                 /// Loop view at bottomLeading
                 LoopView(
-                    determination: $state.mostRecentDetermination,
                     closedLoop: $state.closedLoop,
                     timerDate: $state.timerDate,
                     isLooping: $state.isLooping,
@@ -454,7 +437,7 @@ extension Home {
                 }
                 /// eventualBG string at bottomTrailing
 
-                if let eventualBG = state.determinationsFromCoreData.first?.eventualBG {
+                if let eventualBG = determination.first?.eventualBG {
                     let bg = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
@@ -502,7 +485,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(Color.insulin)
                     Text(
-                        (numberFormatter.string(from: (state.mostRecentDetermination?.iob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (determination.first?.iob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -515,7 +498,7 @@ extension Home {
                         .font(.system(size: 16))
                         .foregroundColor(.loopYellow)
                     Text(
-                        (numberFormatter.string(from: (state.mostRecentDetermination?.cob ?? 0) as NSNumber) ?? "0") +
+                        (numberFormatter.string(from: (determination.first?.cob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
                     .font(.system(size: 16, weight: .bold, design: .rounded))
@@ -541,7 +524,7 @@ extension Home {
                         "TDD: " +
                             (
                                 numberFormatter
-                                    .string(from: (state.mostRecentDetermination?.totalDailyDose ?? 0) as NSNumber) ?? "0"
+                                    .string(from: (determination.first?.totalDailyDose ?? 0) as NSNumber) ?? "0"
                             ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
@@ -826,7 +809,7 @@ extension Home {
             ZStack(alignment: .bottom) {
                 TabView {
                     let carbsRequiredBadge: String? = {
-                        guard let carbsRequired = state.mostRecentDetermination?.carbsRequired as? Decimal else { return nil }
+                        guard let carbsRequired = determination.first?.carbsRequired as? Decimal else { return nil }
                         if carbsRequired > state.settingsManager.settings.carbsRequiredThreshold {
                             let numberAsNSNumber = NSDecimalNumber(decimal: carbsRequired)
                             let formattedNumber = numberFormatter.string(from: numberAsNSNumber) ?? ""
@@ -889,7 +872,7 @@ extension Home {
             VStack(alignment: .leading, spacing: 4) {
                 Text(statusTitle).font(.headline).foregroundColor(.white)
                     .padding(.bottom, 4)
-                if let determination = state.determinationsFromCoreData.first {
+                if let determination = determination.first {
                     if determination.glucose == 400 {
                         Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
                         Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
@@ -914,7 +897,7 @@ extension Home {
         }
 
         private func setStatusTitle() {
-            if let determination = state.determinationsFromCoreData.first {
+            if let determination = determination.first {
                 let dateFormatter = DateFormatter()
                 dateFormatter.timeStyle = .short
                 statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") +

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -16,7 +16,7 @@ extension NightscoutConfig {
         @Injected() private var storage: FileStorage!
         @Injected() var apsManager: APSManager!
 
-        let coredataContext = CoreDataStack.shared.backgroundContext
+        let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         @Published var url = ""
         @Published var secret = ""

+ 1 - 1
FreeAPS/Sources/Modules/OverrideProfilesConfig/OverrideProfilesStateModel.swift

@@ -56,7 +56,7 @@ extension OverrideProfilesConfig {
             maxValue = settingsManager.preferences.autosensMax
         }
 
-        let coredataContext = CoreDataStack.shared.backgroundContext
+        let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
         func saveSettings() {
             coredataContext.perform { [self] in

+ 1 - 1
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -25,7 +25,7 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         setupGlucose()
     }
 
-    let coredataContext = CoreDataStack.shared.backgroundContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never> {
         Future { promise in

+ 2 - 2
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -179,7 +179,7 @@ extension LiveActivityAttributes.ContentState {
 
         fetchedResultsController = NSFetchedResultsController(
             fetchRequest: fetchRequest,
-            managedObjectContext: CoreDataStack.shared.viewContext,
+            managedObjectContext: CoreDataStack.shared.persistentContainer.viewContext,
             sectionNameKeyPath: nil,
             cacheName: nil
         )
@@ -209,7 +209,7 @@ extension LiveActivityAttributes.ContentState {
     }
 
     private func fetchDetermination() -> OrefDetermination? {
-        let context = CoreDataStack.shared.viewContext
+        let context = CoreDataStack.shared.persistentContainer.viewContext
         do {
             let determinations = try context.fetch(OrefDetermination.fetch(NSPredicate.enactedDetermination))
             debugPrint("LA Bridge: \(#function) \(DebuggingIdentifiers.succeeded) fetched determinations")

+ 1 - 1
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -62,7 +62,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         return NightscoutAPI(url: url, secret: secret)
     }
 
-    private let context = CoreDataStack.shared.backgroundContext
+    private let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private var lastTwoDeterminations: [OrefDetermination]?
 

+ 1 - 1
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -52,7 +52,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private let center = UNUserNotificationCenter.current()
     private var lifetime = Lifetime()
 
-    private let context = CoreDataStack.shared.viewContext
+    private let context = CoreDataStack.shared.persistentContainer.viewContext
 
     init(resolver: Resolver) {
         super.init()

+ 1 - 2
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -19,7 +19,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
     @Injected() private var garmin: GarminManager!
 
     let coreDataStorage = CoreDataStorage()
-    let context = CoreDataStack.shared.backgroundContext
+    let context = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     private var lifetime = Lifetime()
 
@@ -81,7 +81,6 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
             Date()
         )
 
-        let context = CoreDataStack.shared.backgroundContext
         context.performAndWait {
             let predicate = NSPredicate.predicateFor120MinAgo
             let fetchedGlucose = CoreDataStack.shared.fetchEntities(

+ 1 - 1
FreeAPS/Sources/Shortcuts/BaseIntentsRequest.swift

@@ -16,7 +16,7 @@ import Swinject
 
     let resolver: Resolver
 
-    let coredataContext = CoreDataStack.shared.backgroundContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     override init() {
         resolver = FreeAPSApp.resolver

+ 1 - 1
FreeAPS/Sources/Shortcuts/State/StateIntentRequest.swift

@@ -54,7 +54,7 @@ enum StateIntentError: Error {
 }
 
 @available(iOS 16.0, *) final class StateIntentRequest: BaseIntentsRequest {
-    let moc = CoreDataStack.shared.backgroundContext
+    let moc = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     func getLastGlucose() throws -> (dateGlucose: Date, glucose: String, trend: String, delta: String) {
         do {

+ 215 - 37
Model/CoreDataStack.swift

@@ -1,38 +1,121 @@
 import CoreData
 import Foundation
+import OSLog
 
 class CoreDataStack: ObservableObject {
-    init() {}
-
     static let shared = CoreDataStack()
     static let identifier = "CoreDataStack"
 
+    private var notificationToken: NSObjectProtocol?
+    private let inMemory: Bool
+
+    private init(inMemory: Bool = false) {
+        self.inMemory = inMemory
+
+        // Observe Core Data remote change notifications on the queue where the changes were made
+        notificationToken = Foundation.NotificationCenter.default.addObserver(
+            forName: .NSPersistentStoreRemoteChange,
+            object: nil,
+            queue: nil
+        ) { _ in
+            debugPrint("Received a persistent store remote change notification")
+            Task {
+                await self.fetchPersistentHistory()
+            }
+        }
+    }
+
+    deinit {
+        if let observer = notificationToken {
+            Foundation.NotificationCenter.default.removeObserver(observer)
+        }
+    }
+
+    /// A persistent history token used for fetching transactions from the store
+    private var lastToken: NSPersistentHistoryToken?
+
+    /// A persistent container to set up the Core Data Stack
     lazy var persistentContainer: NSPersistentContainer = {
         let container = NSPersistentContainer(name: "Core_Data")
 
-        container.loadPersistentStores(completionHandler: { _, error in
-            guard let error = error as NSError? else { return }
-            fatalError("Unresolved error: \(error), \(error.userInfo)")
-        })
+        guard let description = container.persistentStoreDescriptions.first else {
+            fatalError("Failed \(DebuggingIdentifiers.failed) to retrieve a persistent store description")
+        }
 
+        if inMemory {
+            description.url = URL(fileURLWithPath: "/dev/null")
+        }
+
+        // Enable persistent store remote change notifications
+        /// - Tag: persistentStoreRemoteChange
+        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
+
+        // Enable persistent history tracking
+        /// - Tag: persistentHistoryTracking
+        description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
+
+        container.loadPersistentStores { _, error in
+            if let error = error as NSError? {
+                fatalError("Unresolved Error \(DebuggingIdentifiers.failed) \(error), \(error.userInfo)")
+            }
+        }
+
+        container.viewContext.automaticallyMergesChangesFromParent = false
+        container.viewContext.name = "viewContext"
+        /// - Tag: viewContextmergePolicy
+        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
+        container.viewContext.undoManager = nil
+        container.viewContext.shouldDeleteInaccessibleFaults = true
         return container
     }()
 
-    // ensure thread safety by creating a NSManagedObjectContext for the main thread and for a background thread
-    lazy var backgroundContext: NSManagedObjectContext = {
-        let newbackgroundContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
-        newbackgroundContext.automaticallyMergesChangesFromParent = true
-        newbackgroundContext
-            .mergePolicy =
-            NSMergeByPropertyStoreTrumpMergePolicy // if two objects with the same unique constraint are found, overwrite with the object in the external storage
-        return newbackgroundContext
-    }()
+    /// Creates and configures a private queue context
+    private func newTaskContext() -> NSManagedObjectContext {
+        // Create a private queue context
+        /// - Tag: newBackgroundContext
+        let taskContext = persistentContainer.newBackgroundContext()
+        taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
+        taskContext.undoManager = nil
+        return taskContext
+    }
 
-    lazy var viewContext: NSManagedObjectContext = {
-        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
-        viewContext.automaticallyMergesChangesFromParent = true
-        return viewContext
-    }()
+    func fetchPersistentHistory() async {
+        do {
+            try await fetchPersistentHistoryTransactionsAndChanges()
+        } catch {
+            debugPrint("\(error.localizedDescription)")
+        }
+    }
+
+    private func fetchPersistentHistoryTransactionsAndChanges() async throws {
+        let taskContext = newTaskContext()
+        taskContext.name = "persistentHistoryContext"
+        debugPrint("Start fetching persistent history changes from the store ... \(DebuggingIdentifiers.inProgress)")
+
+        try await taskContext.perform {
+            // Execute the persistent history change since the last transaction
+            /// - Tag: fetchHistory
+            let changeRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastToken)
+            let historyResult = try taskContext.execute(changeRequest) as? NSPersistentHistoryResult
+            if let history = historyResult?.result as? [NSPersistentHistoryTransaction], !history.isEmpty {
+                self.mergePersistentHistoryChanges(from: history)
+                return
+            }
+        }
+    }
+
+    private func mergePersistentHistoryChanges(from history: [NSPersistentHistoryTransaction]) {
+        debugPrint("Received \(history.count) persistent history transactions")
+        // Update view context with objectIDs from history change request
+        /// - Tag: mergeChanges
+        let viewContext = persistentContainer.viewContext
+        viewContext.perform {
+            for transaction in history {
+                viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
+                self.lastToken = transaction.token
+            }
+        }
+    }
 
     // MARK: - Fetch Requests
 
@@ -49,7 +132,6 @@ class CoreDataStack: ObservableObject {
         fetchLimit: Int? = nil,
         batchSize: Int? = nil,
         propertiesToFetch: [String]? = nil,
-        context: NSManagedObjectContext? = CoreDataStack.shared.backgroundContext,
         callingFunction: String = #function,
         callingClass: String = #fileID
     ) -> [T] {
@@ -69,15 +151,19 @@ class CoreDataStack: ObservableObject {
             request.resultType = .managedObjectResultType
         }
 
+        let taskContext = newTaskContext()
+        taskContext.name = "fetchContext"
+        taskContext.transactionAuthor = "fetchEntities"
+
         var result: [T]?
 
         /// 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
-        context?.performAndWait {
+        taskContext.performAndWait {
             do {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
                 )
-                result = try context?.fetch(request)
+                result = try taskContext.fetch(request)
             } catch let error as NSError {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
@@ -85,13 +171,55 @@ class CoreDataStack: ObservableObject {
             }
         }
 
-//        if result == nil {
-//            debugPrint("Fetch result is nil in \(callingFunction) from \(callingClass) on thread \(Thread.current)")
-//        } else {
-//            debugPrint(
-//                "Fetch result count: \(result?.count ?? 0) in \(callingFunction) from \(callingClass) on thread \(Thread.current)"
-//            )
-//        }
+        return result ?? []
+    }
+
+    func fetchEntities2<T: NSManagedObject>(
+        ofType type: T.Type,
+        onContext context: NSManagedObjectContext,
+        predicate: NSPredicate,
+        key: String,
+        ascending: Bool,
+        fetchLimit: Int? = nil,
+        batchSize: Int? = nil,
+        propertiesToFetch: [String]? = nil,
+        callingFunction: String = #function,
+        callingClass: String = #fileID
+    ) -> [T] {
+        let request = NSFetchRequest<T>(entityName: String(describing: type))
+        request.sortDescriptors = [NSSortDescriptor(key: key, ascending: ascending)]
+        request.predicate = predicate
+        if let limit = fetchLimit {
+            request.fetchLimit = limit
+        }
+        if let batchSize = batchSize {
+            request.fetchBatchSize = batchSize
+        }
+        if let propertiesTofetch = propertiesToFetch {
+            request.propertiesToFetch = propertiesTofetch
+            request.resultType = .managedObjectResultType
+        } else {
+            request.resultType = .managedObjectResultType
+        }
+
+        context.name = "fetchContext"
+        context.transactionAuthor = "fetchEntities"
+
+        var result: [T]?
+
+        /// 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
+        context.performAndWait {
+            do {
+                debugPrint(
+                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
+                )
+                result = try context.fetch(request)
+            } catch let error as NSError {
+                debugPrint(
+                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on Thread: \(Thread.current)"
+                )
+            }
+        }
 
         return result ?? []
     }
@@ -124,17 +252,21 @@ class CoreDataStack: ObservableObject {
             request.propertiesToFetch = propertiesToFetch
         }
 
+        let taskContext = newTaskContext()
+        taskContext.name = "fetchContext"
+        taskContext.transactionAuthor = "fetchEntities"
+
         // perform fetch in the background
         //
         // the fetch returns a NSManagedObjectID which can be safely passed to the main queue because they are thread safe
-        backgroundContext.perform {
+        taskContext.perform {
             var result: [NSManagedObjectID]?
 
             do {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
                 )
-                result = try self.backgroundContext.fetch(request)
+                result = try taskContext.fetch(request)
             } catch let error as NSError {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
@@ -148,7 +280,7 @@ class CoreDataStack: ObservableObject {
                         "Returning fetch result to main thread in \(callingFunction) from \(callingClass) on thread \(Thread.current)"
                     )
                     // Convert NSManagedObjectIDs to objects in the main context
-                    let mainContext = self.viewContext
+                    let mainContext = CoreDataStack.shared.persistentContainer.viewContext
                     let mainContextObjects = result.compactMap { mainContext.object(with: $0) as? T }
                     completion(mainContextObjects)
                 } else {
@@ -186,15 +318,19 @@ class CoreDataStack: ObservableObject {
             request.propertiesToFetch = propertiesToFetch
         }
 
+        let taskContext = newTaskContext()
+        taskContext.name = "fetchContext"
+        taskContext.transactionAuthor = "fetchEntities"
+
         // Perform fetch in the background
-        backgroundContext.perform {
+        taskContext.perform {
             var result: [NSManagedObjectID]?
 
             do {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on thread \(Thread.current)"
                 )
-                result = try self.backgroundContext.fetch(request)
+                result = try taskContext.fetch(request)
             } catch let error as NSError {
                 debugPrint(
                     "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.failed) \(error) on thread \(Thread.current)"
@@ -211,12 +347,12 @@ class CoreDataStack: ObservableObject {
     // takes a context as a parameter to be executed either on the main thread or on a background thread
     // save on the thread of the backgroundContext
     func saveContext(useViewContext: Bool = false, callingFunction: String = #function, callingClass: String = #fileID) throws {
-        let contextToUse = useViewContext ? viewContext : backgroundContext
+        let contextToUse = useViewContext ? CoreDataStack.shared.persistentContainer.viewContext : newTaskContext()
 
         try contextToUse.performAndWait {
             if contextToUse.hasChanges {
                 do {
-                    try self.backgroundContext.save()
+                    try contextToUse.save()
                     debugPrint(
                         "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
                     )
@@ -229,4 +365,46 @@ class CoreDataStack: ObservableObject {
             }
         }
     }
+
+    // MARK: - Delete
+
+    //
+    /// Synchronously delete entries with specified object IDs
+    func deleteObject(identifiedBy objectIDs: [NSManagedObjectID]) {
+        let viewContext = persistentContainer.viewContext
+        debugPrint("Start deleting data from the store ...\(DebuggingIdentifiers.inProgress)")
+
+        viewContext.perform {
+            objectIDs.forEach { objectID in
+                let entryToDelete = viewContext.object(with: objectID)
+                viewContext.delete(entryToDelete)
+            }
+        }
+
+        debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
+    }
+
+    /// Asynchronously deletes records
+//    func batchDelete<T: NSManagedObject>(_ objects: [T]) async throws {
+//        let objectIDs = objects.map(\.objectID)
+//        let taskContext = newTaskContext()
+//        // Add name and author to identify source of persistent history changes.
+//        taskContext.name = "deleteContext"
+//        taskContext.transactionAuthor = "batchDelete"
+//        debugPrint("Start deleting data from the store... \(DebuggingIdentifiers.inProgress)")
+//
+//        try await taskContext.perform {
+//            // Execute the batch delete.
+//            let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
+//            guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
+//                  let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
+//                  let success = batchDeleteResult.result as? Bool, success
+//            else {
+//                debugPrint("Failed to execute batch delete request \(DebuggingIdentifiers.failed)")
+//                throw CoreDataError.batchDeleteError
+//            }
+//        }
+//
+//        debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
+//    }
 }

+ 1 - 1
Model/Helper/CoreDataStorage.swift

@@ -4,7 +4,7 @@ import SwiftDate
 import Swinject
 
 final class CoreDataStorage {
-    let coredataContext = CoreDataStack.shared.backgroundContext
+    let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
     func fetchLatestOverride() -> [Override] {
         var overrideArray = [Override]()

+ 0 - 1
Model/Helper/Determination+helper.swift

@@ -7,7 +7,6 @@ extension OrefDetermination {
         request.sortDescriptors = [NSSortDescriptor(keyPath: \OrefDetermination.deliverAt, ascending: false)]
         request.predicate = predicate
         request.fetchLimit = 1
-        request.returnsObjectsAsFaults = true
         return request
     }
 }

+ 0 - 1
Model/Helper/Forecast+helper.swift

@@ -7,7 +7,6 @@ public extension Forecast {
         request.sortDescriptors = [NSSortDescriptor(keyPath: \Forecast.date, ascending: ascending)]
         request.fetchLimit = 1
         request.predicate = predicate
-        request.returnsObjectsAsFaults = true
 
         return request
     }

+ 5 - 0
Model/Helper/PumpEvent+helper.swift

@@ -55,6 +55,11 @@ extension NSPredicate {
         let date = Date.oneDayAgo
         return NSPredicate(format: "timestamp >= %@", date as NSDate)
     }
+
+    static var recentPumpHistory: NSPredicate {
+        let date = Date.twentyMinutesAgo
+        return NSPredicate(format: "timestamp >= %@", date as NSDate)
+    }
 }
 
 // Declare helper structs ("data transfer objects" = DTO) to utilize parsing a flattened pump history