FetchGlucoseManager.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import Combine
  2. import Foundation
  3. import SwiftDate
  4. import Swinject
  5. protocol FetchGlucoseManager {}
  6. final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
  7. private let processQueue = DispatchQueue(label: "BaseGlucoseManager.processQueue")
  8. @Injected() var glucoseStorage: GlucoseStorage!
  9. @Injected() var nightscoutManager: NightscoutManager!
  10. @Injected() var apsManager: APSManager!
  11. private var lifetime = Set<AnyCancellable>()
  12. private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
  13. init(resolver: Resolver) {
  14. injectServices(resolver)
  15. subscribe()
  16. }
  17. private func subscribe() {
  18. timer.publisher
  19. .receive(on: processQueue)
  20. .flatMap { date -> AnyPublisher<(Date, Date, [BloodGlucose]), Never> in
  21. debug(.nightscout, "FetchGlucoseManager heartbeat")
  22. debug(.nightscout, "Start fetching glucose")
  23. return Publishers.CombineLatest3(
  24. Just(date),
  25. Just(self.glucoseStorage.syncDate()),
  26. Publishers.CombineLatest(
  27. self.nightscoutManager.fetchGlucose(),
  28. self.fetchGlucoseFromSharedGroup()
  29. )
  30. .map { [$0, $1].flatMap { $0 } }
  31. .eraseToAnyPublisher()
  32. )
  33. .eraseToAnyPublisher()
  34. }
  35. .sink { date, syncDate, glucose in
  36. // Because of Spike dosn't respect a date query
  37. let filteredByDate = glucose.filter { $0.dateString > syncDate }
  38. let filtered = self.glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
  39. if !filtered.isEmpty {
  40. debug(.nightscout, "New glucose found")
  41. self.glucoseStorage.storeGlucose(filtered)
  42. self.apsManager.heartbeat(date: date, force: true)
  43. }
  44. }
  45. .store(in: &lifetime)
  46. timer.resume()
  47. }
  48. private func fetchGlucoseFromSharedGroup() -> AnyPublisher<[BloodGlucose], Never> {
  49. guard let suiteName = Bundle.main.appGroupSuiteName,
  50. let sharedDefaults = UserDefaults(suiteName: suiteName)
  51. else {
  52. return Just([]).eraseToAnyPublisher()
  53. }
  54. return Just(fetchLastBGs(60, sharedDefaults)).eraseToAnyPublisher()
  55. }
  56. private func fetchLastBGs(_ count: Int, _ sharedDefaults: UserDefaults) -> [BloodGlucose] {
  57. guard let sharedData = sharedDefaults.data(forKey: "latestReadings") else {
  58. return []
  59. }
  60. let decoded = try? JSONSerialization.jsonObject(with: sharedData, options: [])
  61. guard let sgvs = decoded as? [AnyObject] else {
  62. return []
  63. }
  64. var results: [BloodGlucose] = []
  65. for sgv in sgvs.prefix(count) {
  66. guard
  67. let glucose = sgv["Value"] as? Int,
  68. let direction = sgv["direction"] as? String,
  69. let timestamp = sgv["DT"] as? String,
  70. let date = parseDate(timestamp)
  71. else { continue }
  72. results.append(
  73. BloodGlucose(
  74. _id: UUID().uuidString,
  75. sgv: glucose,
  76. direction: BloodGlucose.Direction(rawValue: direction),
  77. date: Decimal(Int(date.timeIntervalSince1970 * 1000)),
  78. dateString: date,
  79. filtered: nil,
  80. noise: nil,
  81. glucose: glucose
  82. )
  83. )
  84. }
  85. return results
  86. }
  87. private func parseDate(_ timestamp: String) -> Date? {
  88. // timestamp looks like "/Date(1462404576000)/"
  89. guard let re = try? NSRegularExpression(pattern: "\\((.*)\\)"),
  90. let match = re.firstMatch(in: timestamp, range: NSMakeRange(0, timestamp.count))
  91. else {
  92. return nil
  93. }
  94. let matchRange = match.range(at: 1)
  95. let epoch = Double((timestamp as NSString).substring(with: matchRange))! / 1000
  96. return Date(timeIntervalSince1970: epoch)
  97. }
  98. }
  99. public extension Bundle {
  100. var appGroupSuiteName: String? {
  101. object(forInfoDictionaryKey: "AppGroupID") as? String
  102. }
  103. }