Procházet zdrojové kódy

refactoring, preparation for removing glucose json

polscm32 před 2 roky
rodič
revize
79975d5258

+ 1 - 1
Dependencies/LoopKit/LoopKit/DeviceManager/CGMManager.swift

@@ -71,7 +71,7 @@ public protocol CGMManagerDelegate: DeviceManagerDelegate, CGMManagerStatusObser
     ///
     /// - Parameter manager: The manager instance
     /// - Returns: The date data occuring on or after which should be kept
-    func startDateToFilterNewData(for manager: CGMManager) -> Date?
+    func startDateToFilterNewData(for manager: CGMManager) async -> Date?
 
     /// Informs the delegate that the device has updated with a new result
     ///

+ 1 - 1
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -39,7 +39,7 @@
       },
       {
         "package": "SwiftCharts",
-        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git",
+        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
         "state": {
           "branch": "master",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",

+ 37 - 5
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -32,6 +32,9 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     private lazy var dexcomSourceG7 = DexcomSourceG7(glucoseStorage: glucoseStorage, glucoseManager: self)
     private lazy var simulatorSource = GlucoseSimulatorSource()
 
+    // TODO: - test if we need to use the viewContext here
+    private let context = CoreDataStack.shared.backgroundContext
+
     init(resolver: Resolver) {
         injectServices(resolver)
         updateGlucoseSource()
@@ -96,6 +99,36 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         .store(in: &lifetime)
     }
 
+    private func fetchAndProcessGlucose() -> [BloodGlucose] {
+        do {
+            let results = try context.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor30MinAgo,
+                ascending: false,
+                fetchLimit: 6
+            ))
+            debugPrint("Fetch Glucose Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+
+            var glucoseArray = [BloodGlucose]()
+
+            for result in results {
+                // TODO: - when parsing the CD object to JSON we currently don't have a direction
+                let glucose = BloodGlucose(
+                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                    dateString: result.date ?? Date(),
+                    unfiltered: Decimal(result.glucose),
+                    filtered: Decimal(result.glucose),
+                    noise: nil,
+                    type: ""
+                )
+                glucoseArray.append(glucose)
+            }
+            return glucoseArray
+        } catch {
+            debugPrint("Fetch Glucose Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
+    }
+
     private func glucoseStoreAndHeartDecision(syncDate: Date, glucose: [BloodGlucose], glucoseFromHealth: [BloodGlucose] = []) {
         let allGlucose = glucose + glucoseFromHealth
         var filteredByDate: [BloodGlucose] = []
@@ -132,11 +165,10 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
         // filter the data if it is the case
         if settingsManager.settings.smoothGlucose {
-            // limit to 30 minutes of previous BG Data
-            let oldGlucoses = glucoseStorage.recent().filter {
-                $0.dateString.addingTimeInterval(31 * 60) > Date()
-            }
-            var smoothedValues = oldGlucoses + filtered
+            // limited to 30 min of old glucose data
+            let oldGlucoseValues = fetchAndProcessGlucose()
+
+            var smoothedValues = oldGlucoseValues + filtered
             // smooth with 3 repeats
             for _ in 1 ... 3 {
                 smoothedValues.smoothSavitzkyGolayQuaDratic(withFilterWidth: 3)

+ 97 - 36
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -8,12 +8,10 @@ import Swinject
 protocol GlucoseStorage {
     func storeGlucose(_ glucose: [BloodGlucose])
     func removeGlucose(ids: [String])
-    func recent() -> [BloodGlucose]
     func syncDate() -> Date
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func lastGlucoseDate() -> Date
     func isGlucoseFresh() -> Bool
-    func isGlucoseNotFlat() -> Bool
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose]
     func nightscoutCGMStateNotUploaded() -> [NigtscoutTreatment]
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment]
@@ -177,36 +175,14 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         }
     }
 
-    // fetch glucose from core data
-    private func fetchGlucose() -> [GlucoseStored]? {
-        do {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
-            return try coredataContext.fetch(GlucoseStored.fetch(
-                NSPredicate.predicateForOneDayAgo,
-                ascending: false
-            ))
-        } catch {
-            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose with error: \(error)")
-            return []
-        }
-    }
-
     func syncDate() -> Date {
         //  TODO: - proof logic here!
         /// previously the Blood Glucose array was retrieved and the date of the first, i.e. oldest value was returned (called recent????)
-        /// so for now no logic changes here
-        guard let glucose = fetchGlucose(), let recentGlucose = glucose.last else {
-            return Date().addingTimeInterval(-1.days.timeInterval)
-        }
-        return recentGlucose.date ?? Date()
-    }
-
-    func recent() -> [BloodGlucose] {
-        storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)?.reversed() ?? []
+        fetchGlucose().last?.date ?? .distantPast
     }
 
     func lastGlucoseDate() -> Date {
-        recent().last?.dateString ?? .distantPast
+        fetchGlucose().first?.date ?? .distantPast
     }
 
     func isGlucoseFresh() -> Bool {
@@ -229,17 +205,99 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         return filtered
     }
 
-    func isGlucoseNotFlat() -> Bool {
-        let count = 3 // check last 3 readings
-        let lastReadings = Array(recent().suffix(count))
-        let filtered = lastReadings.compactMap(\.filtered).filter { $0 != 0 }
-        guard lastReadings.count == count, filtered.count == count else { return true }
-        return Array(filtered.uniqued()).count != 1
+    // MARK: - fetching non manual Glucose, manual Glucose and the last glucose value
+
+    // TODO: -optimize this bullshit here...I would love to use the async/await pattern, but its simply not possible because you would need to change all the calls of the following functions and make them async...same shit with the NSAsynchronousFetchRequest
+    /// its all done on a background thread and on a separate queue so hopefully its not too heavy
+    /// also tried this but here again you need to make everything asynchronous...
+    ///  let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
+    /// privateContext.parent = coredataContext /// merges changes to the core data context
+    private func fetchGlucose() -> [GlucoseStored] {
+        do {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateForOneDayAgo,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+        } catch {
+            debugPrint("OpenAPS: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
+    }
+
+    private func fetchLatestGlucose() -> GlucoseStored? {
+        do {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 1
+            )).first
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return nil
+        }
+    }
+
+    private func fetchAndProcessManualGlucose() -> [BloodGlucose] {
+        do {
+            let fetchedResults = try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.manualGlucose,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched manual glucose")
+            let glucoseArray = fetchedResults.map { result in
+                BloodGlucose(
+                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                    dateString: result.date ?? Date(),
+                    unfiltered: Decimal(result.glucose),
+                    filtered: Decimal(result.glucose),
+                    noise: nil,
+                    type: ""
+                )
+            }
+            return glucoseArray
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch manual glucose")
+            return []
+        }
+    }
+
+    private func fetchAndProcessGlucose() -> [BloodGlucose] {
+        do {
+            let results = try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateForOneDayAgo,
+                ascending: false,
+                fetchLimit: 288,
+                batchSize: 50
+            ))
+
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+
+            let glucoseArray = results.map { result in
+                BloodGlucose(
+                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                    dateString: result.date ?? Date(),
+                    unfiltered: Decimal(result.glucose),
+                    filtered: Decimal(result.glucose),
+                    noise: nil,
+                    type: ""
+                )
+            }
+            return glucoseArray
+        } catch {
+            debugPrint("Glucose Storage: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
     }
 
     func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
         let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
-        let recentGlucose = recent()
+        let recentGlucose = fetchAndProcessGlucose()
 
         return Array(Set(recentGlucose).subtracting(Set(uploaded)))
     }
@@ -253,7 +311,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     func nightscoutManualGlucoseNotUploaded() -> [NigtscoutTreatment] {
         let uploaded = (storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? [])
             .filter({ $0.type == GlucoseType.manual.rawValue })
-        let recent = recent().filter({ $0.type == GlucoseType.manual.rawValue })
+
+        let recent = fetchAndProcessManualGlucose()
         let filtered = Array(Set(recent).subtracting(Set(uploaded)))
         let manualReadings = filtered.map { item -> NigtscoutTreatment in
             NigtscoutTreatment(
@@ -271,8 +330,10 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     }
 
     var alarm: GlucoseAlarm? {
-        guard let glucose = recent().last, glucose.dateString.addingTimeInterval(20.minutes.timeInterval) > Date(),
-              let glucoseValue = glucose.glucose else { return nil }
+        /// glucose can not be older than 20 minutes due to the predicate in the fetch request
+        guard let glucose = fetchLatestGlucose() else { return nil }
+
+        let glucoseValue = glucose.glucose
 
         if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
             return .low

+ 24 - 8
FreeAPS/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -15,6 +15,9 @@ extension Calibrations {
 
         var units: GlucoseUnits = .mmolL
 
+        // TODO: - test if we need to use the viewContext here
+        private let context = CoreDataStack.shared.backgroundContext
+
         override func subscribe() {
             units = settingsManager.settings.units
             calibrate = calibrationService.calibrate
@@ -30,6 +33,20 @@ extension Calibrations {
             }
         }
 
+        private func fetchAndProcessGlucose() -> GlucoseStored? {
+            do {
+                debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+                return try context.fetch(GlucoseStored.fetch(
+                    NSPredicate.predicateFor20MinAgo,
+                    ascending: false,
+                    fetchLimit: 1
+                )).first
+            } catch {
+                debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+                return nil
+            }
+        }
+
         func addCalibration() {
             defer {
                 UIApplication.shared.endEditing()
@@ -41,17 +58,16 @@ extension Calibrations {
                 glucose = newCalibration.asMgdL
             }
 
-            guard let lastGlucose = glucoseStorage.recent().last,
-                  lastGlucose.dateString.addingTimeInterval(60 * 4.5) > Date(),
-                  let unfiltered = lastGlucose.unfiltered
-            else {
+            if let lastGlucose = fetchAndProcessGlucose() {
+                let unfiltered = lastGlucose.glucose
+
+                let calibration = Calibration(x: Double(unfiltered), y: Double(glucose))
+
+                calibrationService.addCalibration(calibration)
+            } else {
                 info(.service, "Glucose is stale for calibration")
                 return
             }
-
-            let calibration = Calibration(x: Double(unfiltered), y: Double(glucose))
-
-            calibrationService.addCalibration(calibration)
         }
 
         func removeLast() {

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

@@ -186,7 +186,7 @@ extension DataTable {
                 print("authentication error: \(error.localizedDescription)")
             }
         }
-        
+
         func addManualGlucose() {
             let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
             let glucoseAsInt = Int(glucose)

+ 28 - 19
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -7,7 +7,7 @@ protocol CalendarManager {
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never>
     func calendarIDs() -> [String]
     var currentCalendarID: String? { get set }
-    func createEvent(for glucose: BloodGlucose?, delta: Int?)
+    func createEvent(for glucose: GlucoseStored, delta: Int)
 }
 
 final class BaseCalendarManager: CalendarManager, Injectable {
@@ -88,14 +88,14 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         EKEventStore().calendars(for: .event).map(\.title)
     }
 
-    func createEvent(for glucose: BloodGlucose?, delta: Int?) {
+    func createEvent(for glucose: GlucoseStored, delta: Int) {
         guard settingsManager.settings.useCalendar else { return }
 
         guard let calendar = currentCalendar else { return }
 
         deleteAllEvents(in: calendar)
 
-        guard let glucose = glucose, let glucoseValue = glucose.glucose else { return }
+        let glucoseValue = glucose.glucose
 
         // create an event now
         let event = EKEvent(eventStore: eventStore)
@@ -127,15 +127,14 @@ final class BaseCalendarManager: CalendarManager, Injectable {
 
         let glucoseText = glucoseFormatter
             .string(from: Double(
-                settingsManager.settings.units == .mmolL ?glucoseValue
+                settingsManager.settings.units == .mmolL ? Int(glucoseValue)
                     .asMmolL : Decimal(glucoseValue)
             ) as NSNumber)!
-        let directionText = glucose.direction?.symbol ?? "↔︎"
-        let deltaText = delta
-            .map {
-                deltaFormatter
-                    .string(from: Double(settingsManager.settings.units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
-            } ?? "--"
+
+        let directionText = glucose.direction ?? "↔︎"
+
+        let deltaValue = settingsManager.settings.units == .mmolL ? Int(delta.asMmolL) : delta
+        let deltaText = deltaFormatter.string(from: NSNumber(value: deltaValue)) ?? "--"
 
         let iobText = iobFormatter.string(from: (lastLoop.first?.iob ?? 0) as NSNumber) ?? ""
         let cobText = cobFormatter.string(from: (lastLoop.first?.cob ?? 0) as NSNumber) ?? ""
@@ -230,16 +229,26 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         return formatter
     }
 
-    func setupGlucose() {
-        let glucose = glucoseStorage.recent()
-        let recentGlucose = glucose.last
-        let glucoseDelta: Int?
-        if glucose.count >= 2 {
-            glucoseDelta = (recentGlucose?.glucose ?? 0) - (glucose[glucose.count - 2].glucose ?? 0)
-        } else {
-            glucoseDelta = nil
+    private func fetchAndProcessGlucose() -> [GlucoseStored]? {
+        do {
+            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try coredataContext.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 3
+            ))
+        } catch {
+            debugPrint("Calendar Manager: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
         }
-        createEvent(for: recentGlucose, delta: glucoseDelta)
+    }
+
+    func setupGlucose() {
+        guard let glucose = fetchAndProcessGlucose(), let lastGlucose = glucose.first, let lastReading = glucose.first?.glucose,
+              let secondLastReading = glucose.dropFirst().first?.glucose else { return }
+
+        let glucoseDelta = lastReading - secondLastReading
+        createEvent(for: lastGlucose, delta: Int(glucoseDelta))
     }
 }
 

+ 27 - 7
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -52,6 +52,8 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private let center = UNUserNotificationCenter.current()
     private var lifetime = Lifetime()
 
+    private let context = CoreDataStack.shared.viewContext
+
     init(resolver: Resolver) {
         super.init()
         center.delegate = self
@@ -182,13 +184,27 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
     }
 
+    private func fetchAndProcessGlucose() -> [GlucoseStored]? {
+        do {
+            debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.succeeded) fetched glucose")
+            return try context.fetch(GlucoseStored.fetch(
+                NSPredicate.predicateFor20MinAgo,
+                ascending: false,
+                fetchLimit: 3
+            ))
+        } catch {
+            debugPrint("Calibrations State Model: \(#function) \(DebuggingIdentifiers.failed) failed to fetch glucose")
+            return []
+        }
+    }
+
     private func sendGlucoseNotification() {
         addAppBadge(glucose: nil)
 
-        let glucose = glucoseStorage.recent()
-        guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return }
+        guard let glucose = fetchAndProcessGlucose(), let lastValue = glucose.first, let lastReading = glucose.first?.glucose,
+              let secondLastReading = glucose.dropFirst().first?.glucose else { return }
 
-        addAppBadge(glucose: lastGlucose.glucose)
+        addAppBadge(glucose: (glucose.first?.glucose).map { Int($0) })
 
         guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else {
             return
@@ -209,8 +225,12 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
                 notificationAlarm = true
             }
 
-            let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
-            let body = self.glucoseText(glucoseValue: glucoseValue, delta: delta, direction: lastGlucose.direction) + self
+            let delta = glucose.count >= 2 ? lastReading - secondLastReading : nil
+            let body = self.glucoseText(
+                glucoseValue: (glucose.first?.glucose).map { Int($0) } ?? 0,
+                delta: Int(delta ?? 0),
+                direction: lastValue.direction
+            ) + self
                 .infoBody()
 
             if self.snoozeUntilDate > Date() {
@@ -233,14 +253,14 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
     }
 
-    private func glucoseText(glucoseValue: Int, delta: Int?, direction: BloodGlucose.Direction?) -> String {
+    private func glucoseText(glucoseValue: Int, delta: Int?, direction: String?) -> String {
         let units = settingsManager.settings.units
         let glucoseText = glucoseFormatter
             .string(from: Double(
                 units == .mmolL ? glucoseValue
                     .asMmolL : Decimal(glucoseValue)
             ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
-        let directionText = direction?.symbol ?? "↔︎"
+        let directionText = direction ?? "↔︎"
         let deltaText = delta
             .map {
                 self.deltaFormatter

+ 9 - 1
Model/Helper/GlucoseStored+helper.swift

@@ -2,13 +2,21 @@ import CoreData
 import Foundation
 
 extension GlucoseStored {
-    static func fetch(_ predicate: NSPredicate = .all, ascending: Bool, fetchLimit: Int? = nil) -> NSFetchRequest<GlucoseStored> {
+    static func fetch(
+        _ predicate: NSPredicate = .all,
+        ascending: Bool,
+        fetchLimit: Int? = nil,
+        batchSize: Int? = nil
+    ) -> NSFetchRequest<GlucoseStored> {
         let request = GlucoseStored.fetchRequest()
         request.sortDescriptors = [NSSortDescriptor(keyPath: \GlucoseStored.date, ascending: ascending)]
         request.predicate = predicate
         if let limit = fetchLimit {
             request.fetchLimit = limit
         }
+        if let batchSize = batchSize {
+            request.fetchBatchSize = batchSize
+        }
         return request
     }