CachedGlucoseObject+CoreDataClass.swift 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //
  2. // CachedGlucoseObject+CoreDataClass.swift
  3. // LoopKit
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. //
  8. import Foundation
  9. import CoreData
  10. import HealthKit
  11. class CachedGlucoseObject: NSManagedObject {
  12. var syncVersion: Int? {
  13. get {
  14. willAccessValue(forKey: "syncVersion")
  15. defer { didAccessValue(forKey: "syncVersion") }
  16. return primitiveSyncVersion?.intValue
  17. }
  18. set {
  19. willChangeValue(forKey: "syncVersion")
  20. defer { didChangeValue(forKey: "syncVersion") }
  21. primitiveSyncVersion = newValue.map { NSNumber(value: $0) }
  22. }
  23. }
  24. var device: HKDevice? {
  25. get {
  26. willAccessValue(forKey: "device")
  27. defer { didAccessValue(forKey: "device") }
  28. return primitiveDevice.flatMap { try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKDevice.self, from: $0) }
  29. }
  30. set {
  31. willChangeValue(forKey: "device")
  32. defer { didChangeValue(forKey: "device") }
  33. primitiveDevice = newValue.flatMap { try? NSKeyedArchiver.archivedData(withRootObject: $0, requiringSecureCoding: false) }
  34. }
  35. }
  36. var condition: GlucoseCondition? {
  37. get {
  38. willAccessValue(forKey: "condition")
  39. defer { didAccessValue(forKey: "condition") }
  40. return primitiveCondition.flatMap { GlucoseCondition(rawValue: $0) }
  41. }
  42. set {
  43. willChangeValue(forKey: "condition")
  44. defer { didChangeValue(forKey: "condition") }
  45. primitiveCondition = newValue.map { $0.rawValue }
  46. }
  47. }
  48. var trend: GlucoseTrend? {
  49. get {
  50. willAccessValue(forKey: "trend")
  51. defer { didAccessValue(forKey: "trend") }
  52. return primitiveTrend.flatMap { GlucoseTrend(rawValue: $0.intValue) }
  53. }
  54. set {
  55. willChangeValue(forKey: "trend")
  56. defer { didChangeValue(forKey: "trend") }
  57. primitiveTrend = newValue.map { NSNumber(value: $0.rawValue) }
  58. }
  59. }
  60. var hasUpdatedModificationCounter: Bool { changedValues().keys.contains("modificationCounter") }
  61. func updateModificationCounter() { setPrimitiveValue(managedObjectContext!.modificationCounter!, forKey: "modificationCounter") }
  62. override func awakeFromInsert() {
  63. super.awakeFromInsert()
  64. updateModificationCounter()
  65. }
  66. override func willSave() {
  67. if isUpdated && !hasUpdatedModificationCounter {
  68. updateModificationCounter()
  69. }
  70. super.willSave()
  71. }
  72. }
  73. // MARK: - Helpers
  74. extension CachedGlucoseObject {
  75. var quantity: HKQuantity { HKQuantity(unit: HKUnit(from: unitString), doubleValue: value) }
  76. var quantitySample: HKQuantitySample {
  77. var metadata: [String: Any] = [:]
  78. metadata[HKMetadataKeySyncIdentifier] = syncIdentifier
  79. metadata[HKMetadataKeySyncVersion] = syncVersion
  80. if isDisplayOnly {
  81. metadata[MetadataKeyGlucoseIsDisplayOnly] = true
  82. }
  83. if wasUserEntered {
  84. metadata[HKMetadataKeyWasUserEntered] = true
  85. }
  86. metadata[MetadataKeyGlucoseCondition] = condition?.rawValue
  87. metadata[MetadataKeyGlucoseTrend] = trend?.symbol
  88. metadata[MetadataKeyGlucoseTrendRateUnit] = trendRateUnit
  89. metadata[MetadataKeyGlucoseTrendRateValue] = trendRateValue
  90. return HKQuantitySample(
  91. type: HKQuantityType.quantityType(forIdentifier: .bloodGlucose)!,
  92. quantity: quantity,
  93. start: startDate,
  94. end: startDate,
  95. device: device,
  96. metadata: metadata
  97. )
  98. }
  99. var trendRate: HKQuantity? {
  100. get {
  101. guard let trendRateUnit = trendRateUnit, let trendRateValue = trendRateValue else {
  102. return nil
  103. }
  104. return HKQuantity(unit: HKUnit(from: trendRateUnit), doubleValue: trendRateValue.doubleValue)
  105. }
  106. set {
  107. if let newValue = newValue {
  108. let unit = HKUnit(from: unitString).unitDivided(by: .minute())
  109. trendRateUnit = unit.unitString
  110. trendRateValue = NSNumber(value: newValue.doubleValue(for: unit))
  111. } else {
  112. trendRateUnit = nil
  113. trendRateValue = nil
  114. }
  115. }
  116. }
  117. }
  118. // MARK: - Operations
  119. extension CachedGlucoseObject {
  120. /// Creates (initializes) a `CachedGlucoseObject` from a new CGM sample from Loop.
  121. /// - parameters:
  122. /// - sample: A new glucose (CGM) sample to copy data from.
  123. /// - provenanceIdentifier: A string uniquely identifying the provenance (origin) of the sample.
  124. /// - healthKitStorageDelay: The amount of time (seconds) to delay writing this sample to HealthKit. A `nil` here means this sample is not eligible (i.e. authorized) to be written to HealthKit.
  125. func create(from sample: NewGlucoseSample, provenanceIdentifier: String, healthKitStorageDelay: TimeInterval?) {
  126. self.uuid = nil
  127. self.provenanceIdentifier = provenanceIdentifier
  128. self.syncIdentifier = sample.syncIdentifier
  129. self.syncVersion = sample.syncVersion
  130. self.value = sample.quantity.doubleValue(for: .milligramsPerDeciliter)
  131. self.unitString = HKUnit.milligramsPerDeciliter.unitString
  132. self.startDate = sample.date
  133. self.isDisplayOnly = sample.isDisplayOnly
  134. self.wasUserEntered = sample.wasUserEntered
  135. self.device = sample.device
  136. self.condition = sample.condition
  137. self.trend = sample.trend
  138. self.trendRate = sample.trendRate
  139. self.healthKitEligibleDate = healthKitStorageDelay.map { sample.date.addingTimeInterval($0) }
  140. }
  141. // HealthKit
  142. func create(from sample: HKQuantitySample) {
  143. self.uuid = sample.uuid
  144. self.provenanceIdentifier = sample.provenanceIdentifier
  145. self.syncIdentifier = sample.syncIdentifier
  146. self.syncVersion = sample.syncVersion
  147. self.value = sample.quantity.doubleValue(for: .milligramsPerDeciliter)
  148. self.unitString = HKUnit.milligramsPerDeciliter.unitString
  149. self.startDate = sample.startDate
  150. self.isDisplayOnly = sample.isDisplayOnly
  151. self.wasUserEntered = sample.wasUserEntered
  152. self.device = sample.device
  153. self.condition = sample.condition
  154. self.trend = sample.trend
  155. self.trendRate = sample.trendRate
  156. // The assumption here is that if this is created from a HKQuantitySample, it is coming out of HealthKit, and
  157. // therefore does not need to be written to HealthKit.
  158. self.healthKitEligibleDate = nil
  159. }
  160. }
  161. // MARK: - Watch Synchronization
  162. extension CachedGlucoseObject {
  163. func update(from sample: StoredGlucoseSample) {
  164. self.uuid = sample.uuid
  165. self.provenanceIdentifier = sample.provenanceIdentifier
  166. self.syncIdentifier = sample.syncIdentifier
  167. self.syncVersion = sample.syncVersion
  168. self.value = sample.quantity.doubleValue(for: .milligramsPerDeciliter)
  169. self.unitString = HKUnit.milligramsPerDeciliter.unitString
  170. self.startDate = sample.startDate
  171. self.isDisplayOnly = sample.isDisplayOnly
  172. self.wasUserEntered = sample.wasUserEntered
  173. self.device = sample.device
  174. self.condition = sample.condition
  175. self.trend = sample.trend
  176. self.trendRate = sample.trendRate
  177. self.healthKitEligibleDate = sample.healthKitEligibleDate
  178. }
  179. }