TempTargetsStorage.swift 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import CoreData
  2. import Foundation
  3. import SwiftDate
  4. import Swinject
  5. protocol TempTargetsObserver {
  6. func tempTargetsDidUpdate(_ targets: [TempTarget])
  7. }
  8. protocol TempTargetsStorage {
  9. func storeTempTarget(tempTarget: TempTarget) async
  10. func saveTempTargetsToStorage(_ targets: [TempTarget])
  11. func fetchForTempTargetPresets() async -> [NSManagedObjectID]
  12. func fetchScheduledTempTargets() async -> [NSManagedObjectID]
  13. func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID]
  14. func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
  15. func deleteOverridePreset(_ objectID: NSManagedObjectID) async
  16. func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
  17. func syncDate() -> Date
  18. func recent() -> [TempTarget]
  19. func getTempTargetsNotYetUploadedToNightscout() -> [NightscoutTreatment]
  20. func presets() -> [TempTarget]
  21. func current() -> TempTarget?
  22. }
  23. final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
  24. private let processQueue = DispatchQueue(label: "BaseTempTargetsStorage.processQueue")
  25. @Injected() private var storage: FileStorage!
  26. @Injected() private var broadcaster: Broadcaster!
  27. @Injected() private var settingsManager: SettingsManager!
  28. private let backgroundContext = CoreDataStack.shared.newTaskContext()
  29. private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  30. init(resolver: Resolver) {
  31. injectServices(resolver)
  32. }
  33. func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
  34. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  35. ofType: TempTargetStored.self,
  36. onContext: backgroundContext,
  37. predicate: NSPredicate.lastActiveTempTarget,
  38. key: "orderPosition",
  39. ascending: true,
  40. fetchLimit: fetchLimit
  41. )
  42. guard let fetchedResults = results as? [TempTargetStored] else { return [] }
  43. return await backgroundContext.perform {
  44. return fetchedResults.map(\.objectID)
  45. }
  46. }
  47. /// Returns the NSManagedObjectID of the Temp Target Presets
  48. func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
  49. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  50. ofType: TempTargetStored.self,
  51. onContext: backgroundContext,
  52. predicate: NSPredicate.allTempTargetPresets,
  53. key: "orderPosition",
  54. ascending: true
  55. )
  56. guard let fetchedResults = results as? [TempTargetStored] else { return [] }
  57. return await backgroundContext.perform {
  58. return fetchedResults.map(\.objectID)
  59. }
  60. }
  61. func fetchScheduledTempTargets() async -> [NSManagedObjectID] {
  62. let scheduledTempTargets = NSPredicate(format: "date > %@", Date() as NSDate)
  63. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  64. ofType: TempTargetStored.self,
  65. onContext: backgroundContext,
  66. predicate: scheduledTempTargets,
  67. key: "date",
  68. ascending: false
  69. )
  70. guard let fetchedResults = results as? [TempTargetStored] else { return [] }
  71. return await backgroundContext.perform {
  72. return fetchedResults.map(\.objectID)
  73. }
  74. }
  75. func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID] {
  76. let predicate = NSPredicate(format: "date == %@", targetDate as NSDate)
  77. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  78. ofType: TempTargetStored.self,
  79. onContext: backgroundContext,
  80. predicate: predicate,
  81. key: "date",
  82. ascending: false,
  83. fetchLimit: 1
  84. )
  85. guard let fetchedResults = results as? [TempTargetStored] else { return [] }
  86. return await backgroundContext.perform {
  87. fetchedResults.map(\.objectID)
  88. }
  89. }
  90. func storeTempTarget(tempTarget: TempTarget) async {
  91. var presetCount = -1
  92. if tempTarget.isPreset == true {
  93. let presets = await fetchForTempTargetPresets()
  94. presetCount = presets.count
  95. }
  96. await backgroundContext.perform {
  97. let newTempTarget = TempTargetStored(context: self.backgroundContext)
  98. newTempTarget.date = tempTarget.createdAt
  99. newTempTarget.id = UUID()
  100. newTempTarget.enabled = tempTarget.enabled ?? false
  101. newTempTarget.duration = tempTarget.duration as NSDecimalNumber
  102. newTempTarget.isUploadedToNS = false
  103. newTempTarget.name = tempTarget.name
  104. newTempTarget.target = NSDecimalNumber(decimal: tempTarget.targetTop ?? 0)
  105. newTempTarget.isPreset = tempTarget.isPreset ?? false
  106. // Nullify half basal target to ensure the latest HBT is used via OpenAPS Manager when sending TT data to oref
  107. newTempTarget.halfBasalTarget = nil
  108. if let halfBasalTarget = tempTarget.halfBasalTarget,
  109. halfBasalTarget != self.settingsManager.preferences.halfBasalExerciseTarget
  110. {
  111. newTempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalTarget)
  112. }
  113. if tempTarget.isPreset == true, presetCount > -1 {
  114. newTempTarget.orderPosition = Int16(presetCount + 1)
  115. }
  116. do {
  117. guard self.backgroundContext.hasChanges else { return }
  118. try self.backgroundContext.save()
  119. } catch let error as NSError {
  120. debugPrint(
  121. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Temp Target to Core Data with error: \(error.userInfo)"
  122. )
  123. }
  124. }
  125. }
  126. func saveTempTargetsToStorage(_ targets: [TempTarget]) {
  127. processQueue.async {
  128. let file = OpenAPS.Settings.tempTargets
  129. var uniqEvents: [TempTarget] = []
  130. self.storage.transaction { storage in
  131. storage.append(targets, to: file, uniqBy: \.createdAt)
  132. let retrievedTargets = storage.retrieve(file, as: [TempTarget].self) ?? []
  133. uniqEvents = retrievedTargets
  134. .filter { $0.isWithinLastDay }
  135. .sorted(by: { $0.createdAt > $1.createdAt })
  136. storage.save(uniqEvents, as: file)
  137. }
  138. self.broadcaster.notify(TempTargetsObserver.self, on: self.processQueue) {
  139. $0.tempTargetsDidUpdate(uniqEvents)
  140. }
  141. }
  142. }
  143. // Copy the current Temp Target if it is a RUNNING Preset
  144. /// otherwise we would edit the Preset
  145. @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
  146. let newTempTarget = TempTargetStored(context: viewContext)
  147. newTempTarget.date = tempTarget.date
  148. newTempTarget.id = tempTarget.id
  149. newTempTarget.enabled = tempTarget.enabled
  150. newTempTarget.duration = tempTarget.duration
  151. newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
  152. newTempTarget.name = tempTarget.name
  153. newTempTarget.target = tempTarget.target
  154. newTempTarget.isPreset = false // no Preset
  155. newTempTarget.halfBasalTarget = tempTarget.halfBasalTarget != 160 ? tempTarget.halfBasalTarget : nil
  156. await viewContext.perform {
  157. do {
  158. guard self.viewContext.hasChanges else { return }
  159. try self.viewContext.save()
  160. } catch let error as NSError {
  161. debugPrint(
  162. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
  163. )
  164. }
  165. }
  166. return newTempTarget.objectID
  167. }
  168. @MainActor func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
  169. await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
  170. }
  171. func syncDate() -> Date {
  172. Date().addingTimeInterval(-1.days.timeInterval)
  173. }
  174. func recent() -> [TempTarget] {
  175. storage.retrieve(OpenAPS.Settings.tempTargets, as: [TempTarget].self)?.reversed() ?? []
  176. }
  177. func current() -> TempTarget? {
  178. guard let last = recent().last else {
  179. return nil
  180. }
  181. guard last.createdAt.addingTimeInterval(Int(last.duration).minutes.timeInterval) > Date(), last.createdAt <= Date(),
  182. last.duration != 0
  183. else {
  184. return nil
  185. }
  186. return last
  187. }
  188. func getTempTargetsNotYetUploadedToNightscout() -> [NightscoutTreatment] {
  189. let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedTempTargets, as: [NightscoutTreatment].self) ?? []
  190. let eventsManual = recent().filter { $0.enteredBy == TempTarget.manual }
  191. let treatments = eventsManual.map {
  192. NightscoutTreatment(
  193. duration: Int($0.duration),
  194. rawDuration: nil,
  195. rawRate: nil,
  196. absolute: nil,
  197. rate: nil,
  198. eventType: .nsTempTarget,
  199. createdAt: $0.createdAt,
  200. enteredBy: TempTarget.manual,
  201. bolus: nil,
  202. insulin: nil,
  203. notes: nil,
  204. carbs: nil,
  205. targetTop: $0.targetTop,
  206. targetBottom: $0.targetBottom
  207. )
  208. }
  209. return Array(Set(treatments).subtracting(Set(uploaded)))
  210. }
  211. func presets() -> [TempTarget] {
  212. storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
  213. }
  214. }
  215. private extension TempTarget {
  216. var isActive: Bool {
  217. let expirationTime = createdAt.addingTimeInterval(Int(duration).minutes.timeInterval)
  218. return expirationTime > Date() && createdAt <= Date()
  219. }
  220. var isWithinLastDay: Bool {
  221. createdAt.addingTimeInterval(1.days.timeInterval) > Date()
  222. }
  223. }