GlucoseStorage.swift 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import Foundation
  2. import SwiftDate
  3. import Swinject
  4. protocol GlucoseStorage {
  5. func storeGlucose(_ glucose: [BloodGlucose])
  6. func recent() -> [BloodGlucose]
  7. func syncDate() -> Date
  8. func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
  9. func lastGlucoseDate() -> Date
  10. func isGlucoseFresh() -> Bool
  11. func isGlucoseNotFlat() -> Bool
  12. func nightscoutGlucoseNotUploaded() -> [BloodGlucose]
  13. }
  14. final class BaseGlucoseStorage: GlucoseStorage, Injectable {
  15. private let processQueue = DispatchQueue(label: "BaseGlucoseStorage.processQueue")
  16. @Injected() private var storage: FileStorage!
  17. @Injected() private var broadcaster: Broadcaster!
  18. private enum Config {
  19. static let filterTime: TimeInterval = 4.5 * 60
  20. }
  21. init(resolver: Resolver) {
  22. injectServices(resolver)
  23. }
  24. func storeGlucose(_ glucose: [BloodGlucose]) {
  25. processQueue.sync {
  26. let file = OpenAPS.Monitor.glucose
  27. self.storage.transaction { storage in
  28. storage.append(glucose, to: file, uniqBy: \.dateString)
  29. let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
  30. .filter { $0.dateString.addingTimeInterval(1.days.timeInterval) > Date() }
  31. .sorted { $0.dateString > $1.dateString } ?? []
  32. let glucose = Array(uniqEvents)
  33. storage.save(glucose, as: file)
  34. DispatchQueue.main.async {
  35. self.broadcaster.notify(GlucoseObserver.self, on: .main) {
  36. $0.glucoseDidUpdate(glucose.reversed())
  37. }
  38. }
  39. }
  40. }
  41. }
  42. func syncDate() -> Date {
  43. guard let events = storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self),
  44. let recent = events.first
  45. else {
  46. return Date().addingTimeInterval(-1.days.timeInterval)
  47. }
  48. return recent.dateString
  49. }
  50. func recent() -> [BloodGlucose] {
  51. storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)?.reversed() ?? []
  52. }
  53. func lastGlucoseDate() -> Date {
  54. recent().last?.dateString ?? .distantPast
  55. }
  56. func isGlucoseFresh() -> Bool {
  57. Date().timeIntervalSince(lastGlucoseDate()) <= Config.filterTime
  58. }
  59. func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at date: Date) -> [BloodGlucose] {
  60. var lastDate = date
  61. var filtered: [BloodGlucose] = []
  62. for entry in glucose.reversed() {
  63. guard entry.dateString.addingTimeInterval(-Config.filterTime) > lastDate else {
  64. continue
  65. }
  66. filtered.append(entry)
  67. lastDate = entry.dateString
  68. }
  69. return filtered
  70. }
  71. func isGlucoseNotFlat() -> Bool {
  72. let last3 = recent().suffix(3)
  73. guard last3.count == 3 else { return true }
  74. return Array(
  75. last3
  76. .compactMap { $0.filtered ?? 0 }
  77. .filter { $0 != 0 }
  78. .uniqued()
  79. ).count != 1
  80. }
  81. func nightscoutGlucoseNotUploaded() -> [BloodGlucose] {
  82. let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedGlucose, as: [BloodGlucose].self) ?? []
  83. let recentGlucose = recent()
  84. return Array(Set(recentGlucose).subtracting(Set(uploaded)))
  85. }
  86. }
  87. protocol GlucoseObserver {
  88. func glucoseDidUpdate(_ glucose: [BloodGlucose])
  89. }