polscm32 %!s(int64=2) %!d(string=hai) anos
pai
achega
9f006071b0

+ 45 - 0
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -20,6 +20,24 @@
         }
       },
       {
+        "package": "swift-algorithms",
+        "repositoryURL": "https://github.com/apple/swift-algorithms",
+        "state": {
+          "branch": null,
+          "revision": "2327673b0e9c7e90e6b1826376526ec3627210e4",
+          "version": "0.2.1"
+        }
+      },
+      {
+        "package": "swift-numerics",
+        "repositoryURL": "https://github.com/apple/swift-numerics",
+        "state": {
+          "branch": null,
+          "revision": "6583ac70c326c3ee080c1d42d9ca3361dca816cd",
+          "version": "0.1.0"
+        }
+      },
+      {
         "package": "SwiftCharts",
         "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
         "state": {
@@ -27,6 +45,33 @@
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",
           "version": null
         }
+      },
+      {
+        "package": "SwiftDate",
+        "repositoryURL": "https://github.com/malcommac/SwiftDate",
+        "state": {
+          "branch": null,
+          "revision": "6190d0cefff3013e77ed567e6b074f324e5c5bf5",
+          "version": "6.3.1"
+        }
+      },
+      {
+        "package": "SwiftMessages",
+        "repositoryURL": "https://github.com/SwiftKickMobile/SwiftMessages",
+        "state": {
+          "branch": null,
+          "revision": "b29dd21090b708aa0ae9ecbaf6e2d0487028dc3f",
+          "version": "9.0.6"
+        }
+      },
+      {
+        "package": "Swinject",
+        "repositoryURL": "https://github.com/Swinject/Swinject",
+        "state": {
+          "branch": null,
+          "revision": "8bc503e60965298984fb58cf47b71c541449fe2a",
+          "version": "2.8.3"
+        }
       }
     ]
   },

+ 2 - 2
FreeAPS/Sources/APS/APSManager.swift

@@ -647,7 +647,7 @@ final class BaseAPSManager: APSManager, Injectable {
     }
 
     private func fetchDetermination() -> OrefDetermination? {
-        CoreDataStack.shared.fetchEntities2(
+        CoreDataStack.shared.fetchEntities(
             ofType: OrefDetermination.self,
             onContext: privateContext,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
@@ -934,7 +934,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
     // fetch glucose for time interval
     func fetchGlucose(predicate: NSPredicate, fetchLimit: Int? = nil, batchSize: Int? = nil) -> [GlucoseStored] {
-        CoreDataStack.shared.fetchEntities2(
+        CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: privateContext,
             predicate: predicate,

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

@@ -99,7 +99,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     }
 
     private func fetchGlucose() -> [GlucoseStored]? {
-        CoreDataStack.shared.fetchEntities2(
+        CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: context,
             predicate: NSPredicate.predicateFor30MinAgo,

+ 3 - 3
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -95,7 +95,7 @@ final class OpenAPS {
         var glucoseAsJSON: String?
 
         context.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities2(
+            let results = CoreDataStack.shared.fetchEntities(
                 ofType: GlucoseStored.self,
                 onContext: context,
                 predicate: NSPredicate.predicateForSixHoursAgo,
@@ -118,7 +118,7 @@ final class OpenAPS {
         var carbsAsJSON: String?
 
         context.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities2(
+            let results = CoreDataStack.shared.fetchEntities(
                 ofType: CarbEntryStored.self,
                 onContext: context,
                 predicate: NSPredicate.predicateForOneDayAgo,
@@ -135,7 +135,7 @@ final class OpenAPS {
 
     private func fetchPumpHistoryObjectIDs() -> [NSManagedObjectID]? {
         context.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities2(
+            let results = CoreDataStack.shared.fetchEntities(
                 ofType: PumpEventStored.self,
                 onContext: context,
                 predicate: NSPredicate.pumpHistoryLast24h,

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

@@ -231,7 +231,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     ///
     func fetchGlucose() -> [GlucoseStored] {
         let predicate = NSPredicate.predicateForOneDayAgo
-        return CoreDataStack.shared.fetchEntities2(
+        return CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             predicate: predicate,
@@ -244,7 +244,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
     func fetchManualGlucose() -> [GlucoseStored] {
         let predicate = NSPredicate.manualGlucose
-        return CoreDataStack.shared.fetchEntities2(
+        return CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             predicate: predicate,
@@ -257,7 +257,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
     func fetchLatestGlucose() -> GlucoseStored? {
         let predicate = NSPredicate.predicateFor20MinAgo
-        return CoreDataStack.shared.fetchEntities2(
+        return CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             predicate: predicate,

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

@@ -39,7 +39,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                     // Fetch to filter out duplicates
                     // TODO: - move this to the Core Data Class
 
-                    let existingEvents: [PumpEventStored] = CoreDataStack.shared.fetchEntities2(
+                    let existingEvents: [PumpEventStored] = CoreDataStack.shared.fetchEntities(
                         ofType: PumpEventStored.self,
                         onContext: self.context,
                         predicate: NSPredicate.duplicateInLastFourLoops(event.date),

+ 6 - 5
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -91,7 +91,6 @@ extension Bolus {
 
         @Published var externalInsulin: Bool = false
         @Published var showInfo: Bool = false
-
         @Published var glucoseFromPersistence: [GlucoseStored] = []
         @Published var determination: [OrefDetermination] = []
 
@@ -396,7 +395,6 @@ extension Bolus {
             context.perform {
                 // create pump event
                 let newPumpEvent = PumpEventStored(context: self.context)
-//                newPumpEvent.id = UUID().uuidString
                 newPumpEvent.timestamp = Date()
                 newPumpEvent.type = PumpEvent.bolus.rawValue
 
@@ -408,7 +406,8 @@ extension Bolus {
                 newBolusEntry.isSMB = false
 
                 do {
-                    try CoreDataStack.shared.saveContext(useViewContext: true)
+                    guard self.context.hasChanges else { return }
+                    try self.context.save()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -478,7 +477,8 @@ extension Bolus {
                 newBolusEntry.isSMB = false
 
                 do {
-                    try CoreDataStack.shared.saveContext(useViewContext: true)
+                    guard self.context.hasChanges else { return }
+                    try self.context.save()
                 } catch {
                     print(error.localizedDescription)
                 }
@@ -525,7 +525,8 @@ extension Bolus {
                 context.delete(selection!)
 
                 do {
-                    try CoreDataStack.shared.saveContext(useViewContext: true)
+                    guard context.hasChanges else { return }
+                    try context.save()
                 } catch {
                     print(error.localizedDescription)
                 }

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

@@ -180,7 +180,8 @@ extension DataTable {
                 newItem.isManual = true
 
                 do {
-                    try CoreDataStack.shared.saveContext(useViewContext: true)
+                    guard self.coredataContext.hasChanges else { return }
+                    try self.coredataContext.save()
                 } catch {
                     print(error.localizedDescription)
                 }

+ 0 - 103
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -23,11 +23,6 @@ extension Home {
         @Published var autotunedBasalProfile: [BasalProfileEntry] = []
         @Published var basalProfile: [BasalProfileEntry] = []
         @Published var tempTargets: [TempTarget] = []
-        @Published var glucoseFromPersistence: [GlucoseStored] = []
-        @Published var determinationsFromCoreData: [OrefDetermination] = []
-        @Published var mostRecentDetermination: OrefDetermination?
-        @Published var carbsFromPersistence: [CarbEntryStored] = []
-        @Published var fpusFromPersistence: [CarbEntryStored] = []
         @Published var timerDate = Date()
         @Published var closedLoop = false
         @Published var pumpSuspended = false
@@ -85,11 +80,6 @@ extension Home {
             setupReservoir()
             setupAnnouncements()
             setupCurrentPumpTimezone()
-            setupNotification()
-            updateGlucose()
-            updateDetermination()
-            updateCarbs()
-            updateFPUs()
 
             uploadStats = settingsManager.settings.uploadStats
             units = settingsManager.settings.units
@@ -202,99 +192,6 @@ extension Home {
                 .store(in: &lifetime)
         }
 
-        /// listens for the notifications sent when the managedObjectContext has changed
-        func setupNotification() {
-            Foundation.NotificationCenter.default.addObserver(
-                self,
-                selector: #selector(contextDidSave(_:)),
-                name: Notification.Name.NSManagedObjectContextObjectsDidChange,
-                object: context
-            )
-        }
-
-        /// determine the actions when the context has changed
-        ///
-        /// its done on a background thread and after that the UI gets updated on the main thread
-        @objc private func contextDidSave(_ notification: Notification) {
-            guard let userInfo = notification.userInfo else { return }
-
-            Task { [weak self] in
-                await self?.processUpdates(userInfo: userInfo)
-            }
-        }
-
-        private func processUpdates(userInfo: [AnyHashable: Any]) async {
-            var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
-            objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
-            objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
-
-            let glucoseUpdates = objects.filter { $0 is GlucoseStored }
-            let determinationUpdates = objects.filter { $0 is OrefDetermination }
-            let carbUpdates = objects.filter { $0 is CarbEntryStored }
-
-            if glucoseUpdates.isNotEmpty {
-                updateGlucose()
-            }
-            if determinationUpdates.isNotEmpty {
-                updateDetermination()
-            }
-            if carbUpdates.isNotEmpty {
-                updateCarbs()
-                updateFPUs()
-            }
-        }
-
-        private func updateGlucose() {
-            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
-                ofType: GlucoseStored.self,
-                predicate: NSPredicate.predicateForOneDayAgo,
-                key: "date",
-                ascending: false,
-                fetchLimit: 288,
-                batchSize: 50
-            ) { fetchedValues in
-                self.glucoseFromPersistence = fetchedValues
-            }
-        }
-
-        private func updateDetermination() {
-            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
-                ofType: OrefDetermination.self,
-                predicate: NSPredicate.enactedDetermination,
-                key: "deliverAt",
-                ascending: false,
-                fetchLimit: 1
-            ) { fetchedValues in
-                guard let latestDetermination = fetchedValues.first else { return }
-                self.determinationsFromCoreData = fetchedValues
-                self.mostRecentDetermination = latestDetermination
-            }
-        }
-
-        private func updateCarbs() {
-            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
-                ofType: CarbEntryStored.self,
-                predicate: NSPredicate.carbsForChart,
-                key: "date",
-                ascending: false,
-                batchSize: 20
-            ) { fetchedValues in
-                self.carbsFromPersistence = fetchedValues
-            }
-        }
-
-        private func updateFPUs() {
-            CoreDataStack.shared.fetchEntitiesAndUpdateUI(
-                ofType: CarbEntryStored.self,
-                predicate: NSPredicate.fpusForChart,
-                key: "date",
-                ascending: false,
-                batchSize: 20
-            ) { fetchedValues in
-                self.fpusFromPersistence = fetchedValues
-            }
-        }
-
         func runLoop() {
             provider.heartbeatNow()
         }

+ 14 - 12
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -229,18 +229,20 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         return formatter
     }
 
-    func setupGlucose() {
-        CoreDataStack.shared.fetchEntitiesAndUpdateUI(
-            ofType: GlucoseStored.self,
-            predicate: NSPredicate.predicateFor30MinAgo,
-            key: "date",
-            ascending: false
-        ) { glucose in
-            guard glucose.count >= 2 else { return }
-            debug(.default, "setup Glucose func on thread: \(Thread.current)")
-            // Safely unwrapping glucose readings
-            if let lastGlucose = glucose.first,
-               let secondLastReading = glucose.dropFirst().first?.glucose
+    private func setupGlucose() {
+        coredataContext.performAndWait {
+            let results = CoreDataStack.shared.fetchEntities(
+                ofType: GlucoseStored.self,
+                onContext: coredataContext,
+                predicate: NSPredicate.predicateFor30MinAgo,
+                key: "date",
+                ascending: false
+            )
+
+            guard results.count >= 2 else { return }
+
+            if let lastGlucose = results.first,
+               let secondLastReading = results.dropFirst().first?.glucose
             {
                 let glucoseDelta = lastGlucose.glucose - secondLastReading
                 self.createEvent(for: lastGlucose, delta: Int(glucoseDelta))

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

@@ -185,7 +185,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     }
 
     private func fetchGlucose() -> [GlucoseStored]? {
-        CoreDataStack.shared.fetchEntities2(
+        CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             onContext: context,
             predicate: NSPredicate.predicateFor20MinAgo,

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

@@ -56,7 +56,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
     private func fetchLastDeterminationDate() -> Date? {
         let predicate = NSPredicate.enactedDetermination
 
-        return CoreDataStack.shared.fetchEntities2(
+        return CoreDataStack.shared.fetchEntities(
             ofType: OrefDetermination.self,
             onContext: context,
             predicate: predicate,
@@ -68,7 +68,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
     }
 
     private func fetchLatestOverride() -> Override? {
-        CoreDataStack.shared.fetchEntities2(
+        CoreDataStack.shared.fetchEntities(
             ofType: Override.self,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgo,
@@ -89,7 +89,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
 
         context.performAndWait {
             let predicate = NSPredicate.predicateFor120MinAgo
-            let fetchedGlucose = CoreDataStack.shared.fetchEntities2(
+            let fetchedGlucose = CoreDataStack.shared.fetchEntities(
                 ofType: GlucoseStored.self,
                 onContext: context,
                 predicate: predicate,

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

@@ -18,7 +18,7 @@ import Foundation
 
     @MainActor func perform() async throws -> some ReturnsValue<StateiAPSResults> & ShowsSnippetView {
         let context = CoreDataStack.shared.persistentContainer.viewContext
-        
+
         let glucoseValues = try? stateIntent.getLastGlucose(onContext: context)
         let iob_cob_value = try? stateIntent.getIobAndCob(onContext: context)
 

+ 5 - 3
FreeAPS/Sources/Shortcuts/State/StateIntentRequest.swift

@@ -57,9 +57,11 @@ enum StateIntentError: Error {
 @available(iOS 16.0, *) final class StateIntentRequest: BaseIntentsRequest {
     let moc = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
-    func getLastGlucose(onContext: NSManagedObjectContext) throws -> (dateGlucose: Date, glucose: String, trend: String, delta: String) {
+    func getLastGlucose(onContext: NSManagedObjectContext) throws
+        -> (dateGlucose: Date, glucose: String, trend: String, delta: String)
+    {
         do {
-            let results = CoreDataStack.shared.fetchEntities2(
+            let results = CoreDataStack.shared.fetchEntities(
                 ofType: GlucoseStored.self,
                 onContext: onContext,
                 predicate: NSPredicate.predicateFor30MinAgo,
@@ -100,7 +102,7 @@ enum StateIntentError: Error {
     }
 
     func getIobAndCob(onContext: NSManagedObjectContext) throws -> (iob: Double, cob: Double) {
-        let results = CoreDataStack.shared.fetchEntities2(
+        let results = CoreDataStack.shared.fetchEntities(
             ofType: OrefDetermination.self,
             onContext: onContext,
             predicate: NSPredicate.enactedDetermination,

+ 36 - 84
Model/CoreDataStack.swift

@@ -122,63 +122,10 @@ class CoreDataStack: ObservableObject {
 
     // MARK: - Fetch Requests
 
-    //
-    // the first I define here is for background work...I decided to pass a parameter context to the function to execute it on the viewContext if necessary, but for updating the UI I've decided to rather create a second generic fetch function with a completion handler which results are returned on the main thread
-    //
-    // first fetch function
-    // fetch on the thread of the backgroundContext
+    // Fetch in background thread
+    /// - Tag: backgroundFetch
     func fetchEntities<T: NSManagedObject>(
         ofType type: T.Type,
-        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
-        }
-
-        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
-        taskContext.performAndWait {
-            do {
-                debugPrint(
-                    "Fetching \(T.self) in \(callingFunction) from \(callingClass): \(DebuggingIdentifiers.succeeded) on Thread: \(Thread.current)"
-                )
-                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)"
-                )
-            }
-        }
-
-        return result ?? []
-    }
-
-    func fetchEntities2<T: NSManagedObject>(
-        ofType type: T.Type,
         onContext context: NSManagedObjectContext,
         predicate: NSPredicate,
         key: String,
@@ -227,8 +174,10 @@ class CoreDataStack: ObservableObject {
         return result ?? []
     }
 
-    // second fetch function
-    // fetch and update UI
+    // TODO: -refactor this, currently only the BolusStateModel uses this because we need to fetch in the background, then do calculations and after this update the UI
+    
+    // Fetch and update UI
+    /// - Tag: uiFetch
     func fetchEntitiesAndUpdateUI<T: NSManagedObject>(
         ofType type: T.Type,
         predicate: NSPredicate,
@@ -294,7 +243,9 @@ class CoreDataStack: ObservableObject {
         }
     }
 
-    // fetch and only return a NSManagedObjectID
+    // Fetch the NSManagedObjectIDs
+    // Useful if we need to pass the NSManagedObject to another thread as the objectID is thread safe
+    /// - Tag: fetchIDs
     func fetchNSManagedObjectID<T: NSManagedObject>(
         ofType type: T.Type,
         predicate: NSPredicate,
@@ -344,35 +295,10 @@ class CoreDataStack: ObservableObject {
         }
     }
 
-    // MARK: - Save
-
-    //
-    // 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 ? CoreDataStack.shared.persistentContainer.viewContext : newTaskContext()
-
-        try contextToUse.performAndWait {
-            if contextToUse.hasChanges {
-                do {
-                    try contextToUse.save()
-                    debugPrint(
-                        "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
-                    )
-                } catch let error as NSError {
-                    debugPrint(
-                        "Saving to Core Data failed in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.failed) with error \(error), \(error.userInfo)"
-                    )
-                    throw error
-                }
-            }
-        }
-    }
-
     // MARK: - Delete
 
-    //
     /// Synchronously delete entries with specified object IDs
+    ///  - Tag: synchronousDelete
     func deleteObject(identifiedBy objectIDs: [NSManagedObjectID]) {
         let viewContext = persistentContainer.viewContext
         debugPrint("Start deleting data from the store ...\(DebuggingIdentifiers.inProgress)")
@@ -388,6 +314,7 @@ class CoreDataStack: ObservableObject {
     }
 
     /// Asynchronously deletes records
+    ///  - Tag: batchDelete
 //    func batchDelete<T: NSManagedObject>(_ objects: [T]) async throws {
 //        let objectIDs = objects.map(\.objectID)
 //        let taskContext = newTaskContext()
@@ -411,3 +338,28 @@ class CoreDataStack: ObservableObject {
 //        debugPrint("Successfully deleted data. \(DebuggingIdentifiers.succeeded)")
 //    }
 }
+
+// MARK: - Save
+
+extension NSManagedObjectContext {
+    // takes a context as a parameter to be executed either on the main thread or on a background thread
+    /// - Tag: save
+    func saveContext(
+        onContext: NSManagedObjectContext,
+        callingFunction: String = #function,
+        callingClass: String = #fileID
+    ) throws {
+        do {
+            guard onContext.hasChanges else { return }
+            try onContext.save()
+            debugPrint(
+                "Saving to Core Data successful in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.succeeded)"
+            )
+        } catch let error as NSError {
+            debugPrint(
+                "Saving to Core Data failed in \(callingFunction) in \(callingClass): \(DebuggingIdentifiers.failed) with error \(error), \(error.userInfo)"
+            )
+            throw error
+        }
+    }
+}