Ivan Valkou пре 5 година
родитељ
комит
68dfefe573

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -132,6 +132,7 @@
 		38BF021B25E7D06400579895 /* PumpSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BF021A25E7D06400579895 /* PumpSettingsView.swift */; };
 		38BF021D25E7E3AF00579895 /* Reservoir.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BF021C25E7E3AF00579895 /* Reservoir.swift */; };
 		38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */; };
+		38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FCF3D525E8FDF40078B0D1 /* MD5.swift */; };
 		38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826925CC82DB001FF17A /* NetworkService.swift */; };
 		38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
@@ -607,6 +608,7 @@
 		38BF021A25E7D06400579895 /* PumpSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpSettingsView.swift; sourceTree = "<group>"; };
 		38BF021C25E7E3AF00579895 /* Reservoir.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reservoir.swift; sourceTree = "<group>"; };
 		38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceDataManager.swift; sourceTree = "<group>"; };
+		38FCF3D525E8FDF40078B0D1 /* MD5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = "<group>"; };
 		38FE826925CC82DB001FF17A /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
 		38FE826C25CC8461001FF17A /* NightscoutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutAPI.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
@@ -1051,6 +1053,7 @@
 				3811DE5925C9D4D500A708ED /* ViewModifiers.swift */,
 				388E5A5B25B6F0770019842D /* JSON.swift */,
 				38B4F3AE25E2979F00E76A18 /* IndexedCollection.swift */,
+				38FCF3D525E8FDF40078B0D1 /* MD5.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -1638,6 +1641,7 @@
 				3811DE3325C9D49500A708ED /* HomeBuilder.swift in Sources */,
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
 				3811DE2125C9D48300A708ED /* MainBuilder.swift in Sources */,
+				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,
 				3811DE7925C9D6D300A708ED /* LoginViewModel.swift in Sources */,
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
 				3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */,

+ 3 - 2
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -64,7 +64,7 @@ final class DeviceDataManager {
 
     private func storePumpEvents(_ events: [NewPumpEvent]) {
         print(
-            "[DeviceDataManager] new pump events: \(events.compactMap(\.type))"
+            "[DeviceDataManager] new pump events: \(events.map(\.title))"
         )
 
         let numberFormatter = NumberFormatter()
@@ -76,6 +76,7 @@ final class DeviceDataManager {
                 guard let dose = event.dose else { return [] }
                 let decimal = Decimal(string: dose.unitsInDeliverableIncrements.description)
                 return [PumpHistoryEvent(
+                    id: event.raw.md5String,
                     type: .bolus,
                     timestamp: event.date,
                     amount: decimal,
@@ -90,7 +91,7 @@ final class DeviceDataManager {
         }
 
         do {
-            try storage.append(eventsToStore, to: OpenAPS.Monitor.pumpHistory)
+            try storage.append(eventsToStore, to: OpenAPS.Monitor.pumpHistory, uniqBy: \.id)
         } catch {
             try? storage.save(eventsToStore, as: OpenAPS.Monitor.pumpHistory)
         }

+ 1 - 0
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -29,6 +29,7 @@ extension OpenAPS {
 
     enum Monitor {
         static let pumpHistory = "monitor/pumphistory.json"
+        static let reservoir = "monitor/reservoir.json"
     }
 
     enum Function {

+ 17 - 0
FreeAPS/Sources/Helpers/MD5.swift

@@ -0,0 +1,17 @@
+import CryptoKit
+import Foundation
+
+extension Data {
+    var md5String: String {
+        let digest = Insecure.MD5.hash(data: self)
+        return digest.map {
+            String(format: "%02hhx", $0)
+        }.joined()
+    }
+}
+
+extension String {
+    var md5String: String {
+        (data(using: .utf8) ?? Data()).md5String
+    }
+}

+ 2 - 0
FreeAPS/Sources/Models/PumpHistoryEvent.swift

@@ -1,6 +1,7 @@
 import Foundation
 
 struct PumpHistoryEvent: JSON {
+    let id: String
     let type: PumpHistoryEventType
     let timestamp: Date
     let amount: Decimal?
@@ -31,6 +32,7 @@ enum PumpHistoryTempType: String, JSON {
 
 extension PumpHistoryEvent {
     private enum CodingKeys: String, CodingKey {
+        case id = "_id"
         case type = "_type"
         case timestamp
         case amount

+ 52 - 70
FreeAPS/Sources/Services/Storage/FileStorage.swift

@@ -1,18 +1,15 @@
-import Combine
 import Disk
 import Foundation
 
 protocol FileStorage {
     func save<Value: JSON>(_ value: Value, as name: String) throws
-    func savePublisher<Value: JSON>(_: Value, as name: String) -> AnyPublisher<Void, Error>
-
     func retrieve<Value: JSON>(_ name: String, as type: Value.Type) throws -> Value
-    func retrievePublisher<Value: JSON>(_: String, as type: Value.Type) -> AnyPublisher<Value, Error>
-
     func append<Value: JSON>(_ newValue: Value, to name: String) throws
-    func append<Value: JSON>(_ newValue: [Value], to name: String) throws
-    func appendPublisher<Value: JSON>(_: Value, to name: String) -> AnyPublisher<Void, Error>
-    func appendPublisher<Value: JSON>(_ newValue: [Value], to name: String) -> AnyPublisher<Void, Error>
+    func append<Value: JSON>(_ newValues: [Value], to name: String) throws
+    func append<Value: JSON, T: Equatable>(_ newValue: Value, to name: String, uniqBy keyPath: KeyPath<Value, T>) throws
+    func append<Value: JSON, T: Equatable>(_ newValues: [Value], to name: String, uniqBy keyPath: KeyPath<Value, T>) throws
+    func remove(_ name: String) throws
+    func rename(_ name: String, to newName: String) throws
 }
 
 final class BaseFileStorage: FileStorage {
@@ -31,86 +28,71 @@ final class BaseFileStorage: FileStorage {
     }
 
     func save<Value: JSON>(_ value: Value, as name: String) throws {
-        try Disk.save(value, to: .documents, as: name, encoder: encoder)
-    }
-
-    func savePublisher<Value: JSON>(_ value: Value, as name: String) -> AnyPublisher<Void, Error> {
-        Future { promise in
-            self.processQueue.async {
-                do {
-                    try self.save(value, as: name)
-                    promise(.success(()))
-                } catch {
-                    promise(.failure(error))
-                }
-            }
+        try processQueue.sync {
+            try Disk.save(value, to: .documents, as: name, encoder: self.encoder)
         }
-        .eraseToAnyPublisher()
     }
 
     func retrieve<Value: JSON>(_ name: String, as type: Value.Type) throws -> Value {
-        try Disk.retrieve(name, from: .documents, as: type, decoder: decoder)
-    }
-
-    func retrievePublisher<Value: JSON>(_ name: String, as type: Value.Type) -> AnyPublisher<Value, Error> {
-        Future { promise in
-            self.processQueue.async {
-                do {
-                    let value = try self.retrieve(name, as: type)
-                    promise(.success(value))
-                } catch {
-                    promise(.failure(error))
-                }
-            }
+        try processQueue.sync {
+            try Disk.retrieve(name, from: .documents, as: type, decoder: decoder)
         }
-        .eraseToAnyPublisher()
     }
 
     func append<Value: JSON>(_ newValue: Value, to name: String) throws {
-        try Disk.append(newValue, to: name, in: .documents, encoder: encoder)
+        try processQueue.sync {
+            try Disk.append(newValue, to: name, in: .documents, decoder: decoder, encoder: encoder)
+        }
     }
 
-    func append<Value: JSON>(_ newValue: [Value], to name: String) throws {
-        try Disk.append(newValue, to: name, in: .documents, encoder: encoder)
+    func append<Value: JSON>(_ newValues: [Value], to name: String) throws {
+        try processQueue.sync {
+            try Disk.append(newValues, to: name, in: .documents, decoder: decoder, encoder: encoder)
+        }
     }
 
-    func appendPublisher<Value: JSON>(_ newValue: Value, to name: String) -> AnyPublisher<Void, Error> {
-        Future { promise in
-            self.processQueue.async {
-                do {
-                    try self.append(newValue, to: name)
-                    promise(.success(()))
-                } catch {
-                    promise(.failure(error))
-                }
+    func append<Value: JSON, T: Equatable>(_ newValue: Value, to name: String, uniqBy keyPath: KeyPath<Value, T>) throws {
+        if let value = try? retrieve(name, as: Value.self) {
+            if value[keyPath: keyPath] != newValue[keyPath: keyPath] {
+                try append(newValue, to: name)
+            }
+        } else if let values = try? retrieve(name, as: [Value].self) {
+            guard values.first(where: { $0[keyPath: keyPath] == newValue[keyPath: keyPath] }) == nil else {
+                return
             }
+            try append(newValue, to: name)
+        } else {
+            try save(newValue, as: name)
         }
-        .eraseToAnyPublisher()
     }
 
-    func appendPublisher<Value: JSON>(_ newValue: [Value], to name: String) -> AnyPublisher<Void, Error> {
-        Future { promise in
-            self.processQueue.async {
-                do { func appendPublisher<Value: JSON>(_ newValue: Value, to name: String) -> AnyPublisher<Void, Error> {
-                    Future { promise in
-                        self.processQueue.async {
-                            do {
-                                try self.append(newValue, to: name)
-                                promise(.success(()))
-                            } catch {
-                                promise(.failure(error))
-                            }
-                        }
-                    }
-                    .eraseToAnyPublisher()
-                }
-                try self.append(newValue, to: name)
-                promise(.success(()))
-                } catch {
-                    promise(.failure(error))
+    func append<Value: JSON, T: Equatable>(_ newValues: [Value], to name: String, uniqBy keyPath: KeyPath<Value, T>) throws {
+        if let value = try? retrieve(name, as: Value.self) {
+            guard newValues.first(where: { $0[keyPath: keyPath] == value[keyPath: keyPath] }) == nil else {
+                return
+            }
+            try append(newValues, to: name)
+        } else if let values = try? retrieve(name, as: [Value].self) {
+            try newValues.forEach { newValue in
+                guard values.first(where: { $0[keyPath: keyPath] == newValue[keyPath: keyPath] }) == nil else {
+                    return
                 }
+                try append(newValue, to: name)
             }
+        } else {
+            try save(newValues, as: name)
+        }
+    }
+
+    func remove(_ name: String) throws {
+        try processQueue.sync {
+            try Disk.remove(name, from: .documents)
+        }
+    }
+
+    func rename(_ name: String, to newName: String) throws {
+        try processQueue.sync {
+            try Disk.rename(name, in: .documents, to: newName)
         }
-        .eraseToAnyPublisher()
     }
 }