Browse Source

Fix manual glucose upload nightscout/Trio#1099 and glucose deletion nightscout/Trio##980

Deniz Cengiz 3 weeks ago
parent
commit
bf02b33a7d

+ 0 - 10
Model/Helper/GlucoseStored+helper.swift

@@ -93,16 +93,6 @@ extension NSPredicate {
         return NSPredicate(format: "date >= %@ AND isUploadedToTidepool == %@", date as NSDate, false as NSNumber)
         return NSPredicate(format: "date >= %@ AND isUploadedToTidepool == %@", date as NSDate, false as NSNumber)
     }
     }
 
 
-    static var manualGlucoseNotYetUploadedToNightscout: NSPredicate {
-        let date = Date.oneDayAgo
-        return NSPredicate(
-            format: "date >= %@ AND isUploadedToNS == %@ AND isManual == %@",
-            date as NSDate,
-            false as NSNumber,
-            true as NSNumber
-        )
-    }
-
     static var manualGlucoseNotYetUploadedToHealth: NSPredicate {
     static var manualGlucoseNotYetUploadedToHealth: NSPredicate {
         let date = Date.oneDayAgo
         let date = Date.oneDayAgo
         return NSPredicate(
         return NSPredicate(

+ 22 - 59
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -19,7 +19,6 @@ protocol GlucoseStorage {
     func isGlucoseFresh() -> Bool
     func isGlucoseFresh() -> Bool
     func getGlucoseNotYetUploadedToNightscout() async throws -> [BloodGlucose]
     func getGlucoseNotYetUploadedToNightscout() async throws -> [BloodGlucose]
     func getCGMStateNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
     func getCGMStateNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
-    func getManualGlucoseNotYetUploadedToNightscout() async throws -> [NightscoutTreatment]
     func getGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
     func getGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
     func getManualGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
     func getManualGlucoseNotYetUploadedToHealth() async throws -> [BloodGlucose]
     func getGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample]
     func getGlucoseNotYetUploadedToTidepool() async throws -> [StoredGlucoseSample]
@@ -411,64 +410,28 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
             }
             }
 
 
             return fetchedResults.map { result in
             return fetchedResults.map { result in
-                BloodGlucose(
-                    id: result.id?.uuidString ?? UUID().uuidString,
-                    sgv: Int(result.glucose),
-                    direction: BloodGlucose.Direction(from: result.direction ?? ""),
-                    date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
-                    dateString: result.date ?? Date(),
-                    unfiltered: Decimal(result.glucose),
-                    filtered: Decimal(result.glucose),
-                    noise: nil,
-                    glucose: Int(result.glucose),
-                    type: "sgv"
-                )
-            }
-        }
-    }
-
-    // Fetch manual glucose that is not uploaded to Nightscout yet
-    /// - Returns: Array of NightscoutTreatment to ensure the correct format for the NS Upload
-    func getManualGlucoseNotYetUploadedToNightscout() async throws -> [NightscoutTreatment] {
-        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: GlucoseStored.self,
-            onContext: context,
-            predicate: NSPredicate.manualGlucoseNotYetUploadedToNightscout,
-            key: "date",
-            ascending: false
-        )
-
-        return try await context.perform {
-            guard let fetchedResults = results as? [GlucoseStored] else {
-                throw CoreDataError.fetchError(function: #function, file: #file)
-            }
-
-            return fetchedResults.map { result in
-                NightscoutTreatment(
-                    duration: nil,
-                    rawDuration: nil,
-                    rawRate: nil,
-                    absolute: nil,
-                    rate: nil,
-                    eventType: .capillaryGlucose,
-                    createdAt: result.date,
-                    enteredBy: CarbsEntry.local,
-                    bolus: nil,
-                    insulin: nil,
-                    notes: "Trio User",
-                    carbs: nil,
-                    fat: nil,
-                    protein: nil,
-                    foodType: nil,
-                    targetTop: nil,
-                    targetBottom: nil,
-                    glucoseType: "Manual",
-                    glucose: self.settingsManager.settings
-                        .units == .mgdL ? (self.glucoseFormatter.string(from: Int(result.glucose) as NSNumber) ?? "")
-                        : (self.glucoseFormatter.string(from: Decimal(result.glucose).asMmolL as NSNumber) ?? ""),
-                    units: self.settingsManager.settings.units == .mmolL ? "mmol" : "mg/dl",
-                    id: result.id?.uuidString
-                )
+                if result.isManual {
+                    BloodGlucose(
+                        id: result.id?.uuidString ?? UUID().uuidString,
+                        mbg: Int(result.glucose),
+                        date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                        dateString: result.date ?? Date(),
+                        type: "mbg"
+                    )
+                } else {
+                    BloodGlucose(
+                        id: result.id?.uuidString ?? UUID().uuidString,
+                        sgv: Int(result.glucose),
+                        direction: BloodGlucose.Direction(from: result.direction ?? ""),
+                        date: Decimal(result.date?.timeIntervalSince1970 ?? Date().timeIntervalSince1970) * 1000,
+                        dateString: result.date ?? Date(),
+                        unfiltered: Decimal(result.glucose),
+                        filtered: Decimal(result.glucose),
+                        noise: nil,
+                        glucose: Int(result.glucose),
+                        type: "sgv"
+                    )
+                }
             }
             }
         }
         }
     }
     }

+ 12 - 0
Trio/Sources/Models/BloodGlucose.swift

@@ -63,6 +63,7 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
         case legacyId = "_id"
         case legacyId = "_id"
         case id
         case id
         case sgv
         case sgv
+        case mbg
         case direction
         case direction
         case date
         case date
         case dateString
         case dateString
@@ -93,6 +94,14 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
             }
             }
             // If both attempts fail, sgv remains nil
             // If both attempts fail, sgv remains nil
         }
         }
+        mbg = try? container.decodeIfPresent(Int.self, forKey: .mbg)
+        if mbg == nil {
+            // The nightscout API might return a double instead of an int, or the key might be missing
+            if let doubleValue = try? container.decodeIfPresent(Double.self, forKey: .mbg) {
+                mbg = Int(doubleValue)
+            }
+            // If both attempts fail, sgv remains nil
+        }
 
 
         direction = try container.decodeIfPresent(Direction.self, forKey: .direction)
         direction = try container.decodeIfPresent(Direction.self, forKey: .direction)
         date = try container.decode(Decimal.self, forKey: .date)
         date = try container.decode(Decimal.self, forKey: .date)
@@ -111,6 +120,7 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
         id: String = UUID().uuidString,
         id: String = UUID().uuidString,
         legacyId: String? = nil,
         legacyId: String? = nil,
         sgv: Int? = nil,
         sgv: Int? = nil,
+        mbg: Int? = nil,
         direction: Direction? = nil,
         direction: Direction? = nil,
         date: Decimal,
         date: Decimal,
         dateString: Date,
         dateString: Date,
@@ -126,6 +136,7 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
         self.id = id
         self.id = id
         self.legacyId = legacyId
         self.legacyId = legacyId
         self.sgv = sgv
         self.sgv = sgv
+        self.mbg = mbg
         self.direction = direction
         self.direction = direction
         self.date = date
         self.date = date
         self.dateString = dateString
         self.dateString = dateString
@@ -142,6 +153,7 @@ struct BloodGlucose: JSON, Identifiable, Hashable, Codable {
     let legacyId: String?
     let legacyId: String?
     var id: String
     var id: String
     var sgv: Int?
     var sgv: Int?
+    var mbg: Int?
     var direction: Direction?
     var direction: Direction?
     let date: Decimal
     let date: Decimal
     let dateString: Date
     let dateString: Date

+ 0 - 1
Trio/Sources/Modules/History/HistoryDataFlow.swift

@@ -245,7 +245,6 @@ protocol HistoryProvider: Provider {
     func deleteCarbsFromNightscout(withID id: String)
     func deleteCarbsFromNightscout(withID id: String)
     func deleteInsulinFromNightscout(withID id: String)
     func deleteInsulinFromNightscout(withID id: String)
     func deleteGlucoseFromNightscout(withID id: String, withDate date: Date)
     func deleteGlucoseFromNightscout(withID id: String, withDate date: Date)
-    func deleteManualGlucoseFromNightscout(withID id: String, withDate date: Date)
     func deleteGlucoseFromHealth(withSyncID id: String)
     func deleteGlucoseFromHealth(withSyncID id: String)
     func deleteMealDataFromHealth(byID id: String, sampleType: HKSampleType)
     func deleteMealDataFromHealth(byID id: String, sampleType: HKSampleType)
     func deleteInsulinFromHealth(withSyncID id: String)
     func deleteInsulinFromHealth(withSyncID id: String)

+ 0 - 7
Trio/Sources/Modules/History/HistoryProvider.swift

@@ -42,13 +42,6 @@ extension History {
             }
             }
         }
         }
 
 
-        func deleteManualGlucoseFromNightscout(withID id: String, withDate date: Date) {
-            Task.detached { [weak self] in
-                guard let self = self else { return }
-                await self.nightscoutManager.deleteManualGlucose(withID: id, withDate: date)
-            }
-        }
-
         func deleteGlucoseFromHealth(withSyncID id: String) {
         func deleteGlucoseFromHealth(withSyncID id: String) {
             Task.detached { [weak self] in
             Task.detached { [weak self] in
                 guard let self = self else { return }
                 guard let self = self else { return }

+ 1 - 5
Trio/Sources/Modules/History/HistoryStateModel.swift

@@ -74,11 +74,7 @@ extension History {
 
 
                     // Delete from Nightscout
                     // Delete from Nightscout
                     if let id = glucoseToDelete.id?.uuidString, let date = glucoseToDelete.date {
                     if let id = glucoseToDelete.id?.uuidString, let date = glucoseToDelete.date {
-                        if glucoseToDelete.isManual {
-                            self.provider.deleteManualGlucoseFromNightscout(withID: id, withDate: date)
-                        } else {
-                            self.provider.deleteGlucoseFromNightscout(withID: id, withDate: date)
-                        }
+                        self.provider.deleteGlucoseFromNightscout(withID: id, withDate: date)
                     }
                     }
 
 
                     // Delete from Apple Health
                     // Delete from Apple Health

+ 0 - 1
Trio/Sources/Services/Network/Nightscout/BaseNightscoutManager+Subscribers.swift

@@ -73,7 +73,6 @@ extension BaseNightscoutManager {
             .filteredByEntityName("GlucoseStored")
             .filteredByEntityName("GlucoseStored")
             .sink { [weak self] _ in
             .sink { [weak self] _ in
                 self?.requestUpload(.glucose)
                 self?.requestUpload(.glucose)
-                self?.requestUpload(.manualGlucose)
             }
             }
             .store(in: &subscriptions)
             .store(in: &subscriptions)
     }
     }

+ 9 - 34
Trio/Sources/Services/Network/Nightscout/NightscoutAPI.swift

@@ -188,44 +188,21 @@ extension NightscoutAPI {
         return
         return
     }
     }
 
 
-    func deleteGlucose(withId _: String, withDate date: Date) async throws {
+    func deleteGlucose(withId id: String, withDate date: Date) async throws {
         var components = URLComponents()
         var components = URLComponents()
         components.scheme = url.scheme
         components.scheme = url.scheme
         components.host = url.host
         components.host = url.host
         components.port = url.port
         components.port = url.port
         components.path = Config.uploadEntriesPath
         components.path = Config.uploadEntriesPath
         components.queryItems = [
         components.queryItems = [
-            URLQueryItem(name: "find[dateString][$eq]", value: Formatter.iso8601withFractionalSeconds.string(from: date))
-        ]
-
-        guard let url = components.url else {
-            throw URLError(.badURL)
-        }
-
-        var request = URLRequest(url: url)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.httpMethod = "DELETE"
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-
-        let (_, response) = try await URLSession.shared.data(for: request)
-
-        guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
-            throw URLError(.badServerResponse)
-        }
-    }
-
-    func deleteManualGlucose(withId id: String, withDate date: Date) async throws {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.uploadEntriesPath
-        components.queryItems = [
-            URLQueryItem(name: "find[dateString][$eq]", value: Formatter.iso8601withFractionalSeconds.string(from: date))
+            URLQueryItem(
+                name: "find[$or][0][id][$eq]",
+                value: id
+            ),
+            URLQueryItem(
+                name: "find[$or][1][dateString][$eq]",
+                value: Formatter.iso8601withFractionalSeconds.string(from: date)
+            )
         ]
         ]
 
 
         guard let url = components.url else {
         guard let url = components.url else {
@@ -246,8 +223,6 @@ extension NightscoutAPI {
         guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
         guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
             throw URLError(.badServerResponse)
             throw URLError(.badServerResponse)
         }
         }
-
-        debugPrint("Delete successful for ID \(id) at \(date)")
     }
     }
 
 
     func deleteInsulin(withId id: String) async throws {
     func deleteInsulin(withId id: String) async throws {

+ 1 - 69
Trio/Sources/Services/Network/Nightscout/NightscoutManager.swift

@@ -12,14 +12,12 @@ protocol NightscoutManager: GlucoseSource {
     func deleteCarbs(withID id: String) async
     func deleteCarbs(withID id: String) async
     func deleteInsulin(withID id: String) async
     func deleteInsulin(withID id: String) async
     func deleteGlucose(withID id: String, withDate date: Date) async
     func deleteGlucose(withID id: String, withDate date: Date) async
-    func deleteManualGlucose(withID id: String, withDate date: Date) async
     func uploadDeviceStatus() async throws
     func uploadDeviceStatus() async throws
     func uploadGlucose() async
     func uploadGlucose() async
     func uploadCarbs() async
     func uploadCarbs() async
     func uploadPumpHistory() async
     func uploadPumpHistory() async
     func uploadOverrides() async
     func uploadOverrides() async
     func uploadTempTargets() async
     func uploadTempTargets() async
-    func uploadManualGlucose() async
     func uploadProfiles() async throws
     func uploadProfiles() async throws
     func uploadNoteTreatment(note: String) async
     func uploadNoteTreatment(note: String) async
     func importSettings() async -> ScheduledNightscoutProfile?
     func importSettings() async -> ScheduledNightscoutProfile?
@@ -55,7 +53,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     /// coalesce into a single upload run for that pipeline.
     /// coalesce into a single upload run for that pipeline.
     let uploadPipelineInterval: [NightscoutUploadPipeline: TimeInterval] = [
     let uploadPipelineInterval: [NightscoutUploadPipeline: TimeInterval] = [
         .carbs: 2, .pumpHistory: 2, .overrides: 2, .tempTargets: 2,
         .carbs: 2, .pumpHistory: 2, .overrides: 2, .tempTargets: 2,
-        .glucose: 2, .manualGlucose: 2, .deviceStatus: 2
+        .glucose: 2, .deviceStatus: 2
     ]
     ]
 
 
     /// Subjects used to request an upload pipeline. The pipeline applies a throttle so
     /// Subjects used to request an upload pipeline. The pipeline applies a throttle so
@@ -96,7 +94,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         case .overrides: await uploadOverrides()
         case .overrides: await uploadOverrides()
         case .tempTargets: await uploadTempTargets()
         case .tempTargets: await uploadTempTargets()
         case .glucose: await uploadGlucose()
         case .glucose: await uploadGlucose()
-        case .manualGlucose: await uploadManualGlucose()
         case .deviceStatus:
         case .deviceStatus:
             do { try await uploadDeviceStatus() }
             do { try await uploadDeviceStatus() }
             catch { debug(.nightscout, "deviceStatus upload failed: \(error)") }
             catch { debug(.nightscout, "deviceStatus upload failed: \(error)") }
@@ -354,19 +351,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func deleteManualGlucose(withID id: String, withDate date: Date) async {
-        guard let nightscout = nightscoutAPI, isUploadEnabled else { return }
-
-        do {
-            try await nightscout.deleteManualGlucose(withId: id, withDate: date)
-        } catch {
-            debug(
-                .nightscout,
-                "\(DebuggingIdentifiers.failed) Failed to delete Manual Glucose from Nightscout with error: \(error)"
-            )
-        }
-    }
-
     private func fetchBattery() async -> Battery {
     private func fetchBattery() async -> Battery {
         await backgroundContext.perform {
         await backgroundContext.perform {
             do {
             do {
@@ -818,17 +802,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    func uploadManualGlucose() async {
-        do {
-            try await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToNightscout())
-        } catch {
-            debug(
-                .nightscout,
-                "\(DebuggingIdentifiers.failed) failed to upload manual glucose with error: \(error)"
-            )
-        }
-    }
-
     func uploadPumpHistory() async {
     func uploadPumpHistory() async {
         do {
         do {
             try await uploadPumpHistory(pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout())
             try await uploadPumpHistory(pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout())
@@ -974,47 +947,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
         }
     }
     }
 
 
-    private func uploadManualGlucose(_ 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))
-            }
-
-            // If successful, update the isUploadedToNS property of the GlucoseStored objects
-            await updateManualGlucoseAsUploaded(treatments)
-
-            debug(.nightscout, "Treatments uploaded")
-        } catch {
-            debug(.nightscout, String(describing: error))
-        }
-    }
-
-    private func updateManualGlucoseAsUploaded(_ treatments: [NightscoutTreatment]) async {
-        await backgroundContext.perform {
-            let ids = treatments.map(\.id) as NSArray
-            let fetchRequest: NSFetchRequest<GlucoseStored> = GlucoseStored.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: \(error.userInfo)"
-                )
-            }
-        }
-    }
-
     private func uploadCarbs(_ treatments: [NightscoutTreatment]) async {
     private func uploadCarbs(_ treatments: [NightscoutTreatment]) async {
         guard !treatments.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
         guard !treatments.isEmpty, let nightscout = nightscoutAPI, isUploadEnabled else {
             return
             return

+ 0 - 1
Trio/Sources/Services/Network/Nightscout/NightscoutUploadPipeline.swift

@@ -9,7 +9,6 @@ public enum NightscoutUploadPipeline: String, CaseIterable {
     case overrides
     case overrides
     case tempTargets
     case tempTargets
     case glucose
     case glucose
-    case manualGlucose
     case deviceStatus
     case deviceStatus
 }
 }