Переглянути джерело

Refactor tempTarget upload to new structure and core data

Deniz Cengiz 1 рік тому
батько
коміт
decffb1adb

+ 75 - 23
FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift

@@ -18,7 +18,8 @@ protocol TempTargetsStorage {
     func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
     func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
     func syncDate() -> Date
     func syncDate() -> Date
     func recent() -> [TempTarget]
     func recent() -> [TempTarget]
-    func getTempTargetsNotYetUploadedToNightscout() -> [NightscoutTreatment]
+    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
+    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func presets() -> [TempTarget]
     func presets() -> [TempTarget]
     func current() -> TempTarget?
     func current() -> TempTarget?
 }
 }
@@ -224,29 +225,80 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         return last
         return last
     }
     }
 
 
-    func getTempTargetsNotYetUploadedToNightscout() -> [NightscoutTreatment] {
-        let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedTempTargets, as: [NightscoutTreatment].self) ?? []
-
-        let eventsManual = recent().filter { $0.enteredBy == TempTarget.manual }
-        let treatments = eventsManual.map {
-            NightscoutTreatment(
-                duration: Int($0.duration),
-                rawDuration: nil,
-                rawRate: nil,
-                absolute: nil,
-                rate: nil,
-                eventType: .nsTempTarget,
-                createdAt: $0.createdAt,
-                enteredBy: TempTarget.manual,
-                bolus: nil,
-                insulin: nil,
-                notes: nil,
-                carbs: nil,
-                targetTop: $0.targetTop,
-                targetBottom: $0.targetBottom
-            )
+    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout, // TODO: create adjustment predicate (OR+TT)
+            key: "date",
+            ascending: false
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedTempTargets = results as? [TempTargetStored] else { return [] }
+
+            return fetchedTempTargets.map { tempTarget in
+                NightscoutTreatment(
+                    duration: Int(truncating: tempTarget.duration ?? 60),
+                    rawDuration: nil,
+                    rawRate: nil,
+                    absolute: nil,
+                    rate: nil,
+                    eventType: .nsTempTarget,
+                    createdAt: tempTarget.date ?? Date(),
+                    enteredBy: TempTarget.manual,
+                    bolus: nil,
+                    insulin: nil,
+                    notes: tempTarget.name ?? "Custom Temporary Target",
+                    carbs: nil,
+                    targetTop: tempTarget
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL),
+                    targetBottom: tempTarget
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL)
+                )
+            }
+        }
+    }
+
+    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetRunStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate(
+                format: "startDate >= %@ AND isUploadedToNS == %@",
+                Date.oneDayAgo as NSDate,
+                false as NSNumber
+            ),
+            key: "startDate",
+            ascending: false
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedTempTargetRuns = results as? [TempTargetRunStored] else { return [] }
+
+            return fetchedTempTargetRuns.map { tempTargetRun in
+                var durationInMinutes = (tempTargetRun.endDate?.timeIntervalSince(tempTargetRun.startDate ?? Date()) ?? 1) / 60
+                durationInMinutes = durationInMinutes < 1 ? 1 : durationInMinutes
+                return NightscoutTreatment(
+                    duration: Int(durationInMinutes),
+                    rawDuration: nil,
+                    rawRate: nil,
+                    absolute: nil,
+                    rate: nil,
+                    eventType: .nsTempTarget,
+                    createdAt: (tempTargetRun.startDate ?? tempTargetRun.tempTarget?.date) ?? Date(),
+                    enteredBy: TempTarget.manual,
+                    bolus: nil,
+                    insulin: nil,
+                    notes: nil,
+                    carbs: nil,
+                    targetTop: tempTargetRun
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL),
+                    targetBottom: tempTargetRun
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL)
+                )
+            }
         }
         }
-        return Array(Set(treatments).subtracting(Set(uploaded)))
     }
     }
 
 
     func presets() -> [TempTarget] {
     func presets() -> [TempTarget] {

+ 126 - 28
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -21,9 +21,9 @@ protocol NightscoutManager: GlucoseSource {
     func uploadTempTargets() async
     func uploadTempTargets() async
     func uploadManualGlucose() async
     func uploadManualGlucose() async
     func uploadProfiles() async
     func uploadProfiles() async
+    func uploadNoteTreatment(note: String) async
     func importSettings() async -> ScheduledNightscoutProfile?
     func importSettings() async -> ScheduledNightscoutProfile?
     var cgmURL: URL? { get }
     var cgmURL: URL? { get }
-    func uploadNoteTreatment(note: String) async
 }
 }
 
 
 final class BaseNightscoutManager: NightscoutManager, Injectable {
 final class BaseNightscoutManager: NightscoutManager, Injectable {
@@ -116,7 +116,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
     }
 
 
     private func subscribe() {
     private func subscribe() {
-        broadcaster.register(TempTargetsObserver.self, observer: self)
+//        broadcaster.register(TempTargetsObserver.self, observer: self)
 
 
         _ = reachabilityManager.startListening(onQueue: processQueue) { status in
         _ = reachabilityManager.startListening(onQueue: processQueue) { status in
             debug(.nightscout, "Network status: \(status)")
             debug(.nightscout, "Network status: \(status)")
@@ -139,6 +139,20 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             self?.uploadOverridesSubject.send()
             self?.uploadOverridesSubject.send()
         }.store(in: &subscriptions)
         }.store(in: &subscriptions)
 
 
+        coreDataPublisher?.filterByEntityName("TempTargetStored").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadTempTargets()
+            }
+        }.store(in: &subscriptions)
+
+        coreDataPublisher?.filterByEntityName("TempTargetRunStored").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task.detached {
+                await self.uploadTempTargets()
+            }
+        }.store(in: &subscriptions)
+
         coreDataPublisher?.filterByEntityName("PumpEventStored").sink { [weak self] _ in
         coreDataPublisher?.filterByEntityName("PumpEventStored").sink { [weak self] _ in
             guard let self = self else { return }
             guard let self = self else { return }
             Task.detached {
             Task.detached {
@@ -539,7 +553,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 targetTop: nil,
                 targetTop: nil,
                 targetBottom: nil
                 targetBottom: nil
             )
             )
-            await uploadTreatments([siteTreatment], fileToSave: OpenAPS.Nightscout.uploadedPodAge)
+            await uploadNonCoreDataTreatments([siteTreatment])
         }
         }
     }
     }
 
 
@@ -698,10 +712,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
 
 
     func uploadGlucose() async {
     func uploadGlucose() async {
         await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToNightscout())
         await uploadGlucose(glucoseStorage.getGlucoseNotYetUploadedToNightscout())
-        await uploadTreatments(
-            glucoseStorage.getCGMStateNotYetUploadedToNightscout(),
-            fileToSave: OpenAPS.Nightscout.uploadedCGMState
-        )
+        await uploadNonCoreDataTreatments(glucoseStorage.getCGMStateNotYetUploadedToNightscout())
     }
     }
 
 
     func uploadManualGlucose() async {
     func uploadManualGlucose() async {
@@ -709,10 +720,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
     }
 
 
     func uploadPumpHistory() async {
     func uploadPumpHistory() async {
-        await uploadTreatments(
-            pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout(),
-            fileToSave: OpenAPS.Nightscout.uploadedPumphistory
-        )
+        await uploadPumpHistory(pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout())
     }
     }
 
 
     func uploadCarbs() async {
     func uploadCarbs() async {
@@ -726,10 +734,8 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     }
     }
 
 
     func uploadTempTargets() async {
     func uploadTempTargets() async {
-        await uploadTreatments(
-            tempTargetsStorage.getTempTargetsNotYetUploadedToNightscout(),
-            fileToSave: OpenAPS.Nightscout.uploadedTempTargets
-        )
+        await uploadTempTargets(await tempTargetsStorage.getTempTargetsNotYetUploadedToNightscout())
+        await uploadTempTargetRuns(await tempTargetsStorage.getTempTargetRunsNotYetUploadedToNightscout())
     }
     }
 
 
     private func uploadGlucose(_ glucose: [BloodGlucose]) async {
     private func uploadGlucose(_ glucose: [BloodGlucose]) async {
@@ -774,7 +780,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    private func uploadTreatments(_ treatments: [NightscoutTreatment], fileToSave _: String) async {
+    private func uploadNonCoreDataTreatments(_ treatments: [NightscoutTreatment]) async {
         guard !treatments.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
         guard !treatments.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
             return
             return
         }
         }
@@ -784,8 +790,23 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 try await nightscout.uploadTreatments(Array(chunk))
                 try await nightscout.uploadTreatments(Array(chunk))
             }
             }
 
 
-            // If successful, update the isUploadedToNS property of the PumpEventStored objects
-            await updateTreatmentsAsUploaded(treatments)
+            debug(.nightscout, "Treatments uploaded")
+        } catch {
+            debug(.nightscout, error.localizedDescription)
+        }
+    }
+
+    private func uploadPumpHistory(_ treatments: [NightscoutTreatment]) async {
+        guard !treatments.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
+            return
+        }
+
+        do {
+            for chunk in treatments.chunks(ofCount: 100) {
+                try await nightscout.uploadTreatments(Array(chunk))
+            }
+
+            await updatePumpEventStoredsAsUploaded(treatments)
 
 
             debug(.nightscout, "Treatments uploaded")
             debug(.nightscout, "Treatments uploaded")
         } catch {
         } catch {
@@ -793,7 +814,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    private func updateTreatmentsAsUploaded(_ treatments: [NightscoutTreatment]) async {
+    private func updatePumpEventStoredsAsUploaded(_ treatments: [NightscoutTreatment]) async {
         await backgroundContext.perform {
         await backgroundContext.perform {
             let ids = treatments.map(\.id) as NSArray
             let ids = treatments.map(\.id) as NSArray
             let fetchRequest: NSFetchRequest<PumpEventStored> = PumpEventStored.fetchRequest()
             let fetchRequest: NSFetchRequest<PumpEventStored> = PumpEventStored.fetchRequest()
@@ -979,6 +1000,89 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
+    private func uploadTempTargets(_ tempTargets: [NightscoutTreatment]) async {
+        guard !tempTargets.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
+            return
+        }
+
+        do {
+            for chunk in tempTargets.chunks(ofCount: 100) {
+                try await nightscout.uploadTreatments(Array(chunk))
+            }
+
+            // If successful, update the isUploadedToNS property of the TempTargetStored objects
+            await updateTempTargetsAsUploaded(tempTargets)
+
+            debug(.nightscout, "Temp Targets uploaded")
+        } catch {
+            debug(.nightscout, error.localizedDescription)
+        }
+    }
+
+    private func updateTempTargetsAsUploaded(_ tempTargets: [NightscoutTreatment]) async {
+        await backgroundContext.perform {
+            let ids = tempTargets.map(\.id) as NSArray
+            let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", ids)
+
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                for result in results {
+                    result.isUploadedToNS = true
+                }
+
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToNS for TempTargetStored: \(error.userInfo)"
+                )
+            }
+        }
+    }
+
+    private func uploadTempTargetRuns(_ tempTargetRuns: [NightscoutTreatment]) async {
+        guard !tempTargetRuns.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
+            return
+        }
+
+        do {
+            for chunk in tempTargetRuns.chunks(ofCount: 100) {
+                try await nightscout.uploadTreatments(Array(chunk))
+            }
+
+            // If successful, update the isUploadedToNS property of the TempTargetRunStored objects
+            await updateTempTargetRunsAsUploaded(tempTargetRuns)
+
+            debug(.nightscout, "Temp Target Runs uploaded")
+        } catch {
+            debug(.nightscout, error.localizedDescription)
+        }
+    }
+
+    private func updateTempTargetRunsAsUploaded(_ tempTargetRuns: [NightscoutTreatment]) async {
+        await backgroundContext.perform {
+            let ids = tempTargetRuns.map(\.id) as NSArray
+            let fetchRequest: NSFetchRequest<TempTargetRunStored> = TempTargetRunStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "id IN %@", ids)
+
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                for result in results {
+                    result.isUploadedToNS = true
+                }
+
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update isUploadedToNS for TempTargetRunStored: \(error.userInfo)"
+                )
+            }
+        }
+    }
+
+    // TODO: have this checked; this has never actually written anything to file; the entire logic of this function seems broken
     func uploadNoteTreatment(note: String) async {
     func uploadNoteTreatment(note: String) async {
         let uploadedNotes = storage.retrieve(OpenAPS.Nightscout.uploadedNotes, as: [NightscoutTreatment].self) ?? []
         let uploadedNotes = storage.retrieve(OpenAPS.Nightscout.uploadedNotes, as: [NightscoutTreatment].self) ?? []
         let now = Date()
         let now = Date()
@@ -992,7 +1096,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
                 targetTop: nil,
                 targetTop: nil,
                 targetBottom: nil
                 targetBottom: nil
             )
             )
-            await uploadTreatments([noteTreatment], fileToSave: OpenAPS.Nightscout.uploadedNotes)
+            await uploadNonCoreDataTreatments([noteTreatment])
+            // TODO: fix/adjust, if necessary
+//            await uploadTreatments([noteTreatment], fileToSave: OpenAPS.Nightscout.uploadedNotes)
         }
         }
     }
     }
 }
 }
@@ -1005,14 +1111,6 @@ extension Array {
     }
     }
 }
 }
 
 
-extension BaseNightscoutManager: TempTargetsObserver {
-    func tempTargetsDidUpdate(_: [TempTarget]) {
-        Task.detached {
-            await self.uploadTempTargets()
-        }
-    }
-}
-
 extension BaseNightscoutManager {
 extension BaseNightscoutManager {
     /**
     /**
      Converts glucose-related values in the given `reason` string to mmol/L, including ranges (e.g., `ISF: 54→54`), comparisons (e.g., `maxDelta 37 > 20% of BG 95`), and both positive and negative values (e.g., `Dev: -36`).
      Converts glucose-related values in the given `reason` string to mmol/L, including ranges (e.g., `ISF: 54→54`), comparisons (e.g., `maxDelta 37 > 20% of BG 95`), and both positive and negative values (e.g., `Dev: -36`).

+ 0 - 10
FreeAPS/Sources/Services/Network/TidepoolManager.swift

@@ -66,8 +66,6 @@ final class BaseTidepoolManager: TidepoolManager, Injectable {
             .store(in: &subscriptions)
             .store(in: &subscriptions)
 
 
         registerHandlers()
         registerHandlers()
-
-        subscribe()
     }
     }
 
 
     /// Loads the Tidepool service from saved state
     /// Loads the Tidepool service from saved state
@@ -134,10 +132,6 @@ final class BaseTidepoolManager: TidepoolManager, Injectable {
         }.store(in: &subscriptions)
         }.store(in: &subscriptions)
     }
     }
 
 
-    private func subscribe() {
-        broadcaster.register(TempTargetsObserver.self, observer: self)
-    }
-
     func sourceInfo() -> [String: Any]? {
     func sourceInfo() -> [String: Any]? {
         nil
         nil
     }
     }
@@ -152,10 +146,6 @@ final class BaseTidepoolManager: TidepoolManager, Injectable {
     }
     }
 }
 }
 
 
-extension BaseTidepoolManager: TempTargetsObserver {
-    func tempTargetsDidUpdate(_: [TempTarget]) {}
-}
-
 extension BaseTidepoolManager: ServiceDelegate {
 extension BaseTidepoolManager: ServiceDelegate {
     var hostIdentifier: String {
     var hostIdentifier: String {
         // TODO: shouldn't this rather be `org.nightscout.Trio` ?
         // TODO: shouldn't this rather be `org.nightscout.Trio` ?