TempPresetsIntentRequest.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import CoreData
  2. import Foundation
  3. import UIKit
  4. final class TempPresetsIntentRequest: BaseIntentsRequest {
  5. enum TempPresetsError: Error {
  6. case noTempTargetFound
  7. case noDurationDefined
  8. }
  9. func fetchAndProcessTempTargets() async -> [TempPreset] {
  10. // Fetch all Temp Target Presets via TempTargetStorage
  11. let allTempTargetPresetsIDs = await tempTargetsStorage.fetchForTempTargetPresets()
  12. // Perform the fetch and process on the Core Data context's thread
  13. return await coredataContext.perform {
  14. // Fetch existing TempTargetStored objects based on their NSManagedObjectIDs
  15. let tempTargetObjects: [TempTargetStored] = allTempTargetPresetsIDs.compactMap { id in
  16. guard let object = try? self.coredataContext.existingObject(with: id) as? TempTargetStored else {
  17. debugPrint("\(#file) \(#function) Failed to fetch object for ID: \(id)")
  18. return nil
  19. }
  20. return object
  21. }
  22. // Map fetched TempTargetStored objects to TempPreset
  23. return tempTargetObjects.compactMap { object in
  24. guard let id = object.id,
  25. let name = object.name,
  26. let target = object.target?.decimalValue,
  27. let duration = object.duration?.decimalValue
  28. else {
  29. debugPrint("\(#file) \(#function) Missing data for TempTargetStored object.")
  30. return TempPreset(id: UUID(), name: "", duration: 0)
  31. }
  32. return TempPreset(id: id, name: name, targetTop: target, duration: duration)
  33. }
  34. }
  35. }
  36. func fetchIDs(_ uuid: [TempPreset.ID]) async -> [TempPreset] {
  37. await coredataContext.perform {
  38. let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
  39. fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
  40. do {
  41. let result = try self.coredataContext.fetch(fetchRequest)
  42. if result.isEmpty {
  43. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) No TempTargetStored found for ids: \(uuid)")
  44. return [TempPreset(id: UUID(), name: "", duration: 0)]
  45. }
  46. return result.map { tempTargetStored in
  47. TempPreset(
  48. id: tempTargetStored.id ?? UUID(),
  49. name: tempTargetStored.name ?? "",
  50. duration: tempTargetStored.duration as? Decimal ?? 0
  51. )
  52. }
  53. } catch let error as NSError {
  54. debugPrint(
  55. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch TempTarget: \(error.localizedDescription)"
  56. )
  57. return [TempPreset(id: UUID(), name: "", duration: 0)]
  58. }
  59. }
  60. }
  61. private func fetchTempTargetID(_ preset: TempPreset) async -> NSManagedObjectID? {
  62. let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
  63. fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id.uuidString)
  64. fetchRequest.fetchLimit = 1
  65. return await coredataContext.perform {
  66. do {
  67. return try self.coredataContext.fetch(fetchRequest).first?.objectID
  68. } catch {
  69. debugPrint(
  70. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
  71. )
  72. return nil
  73. }
  74. }
  75. }
  76. @MainActor func enactTempTarget(_ preset: TempPreset) async -> Bool {
  77. // Start background task
  78. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  79. backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
  80. guard backgroundTaskID != .invalid else { return }
  81. Task {
  82. // End background task when the time is about to expire
  83. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  84. }
  85. backgroundTaskID = .invalid
  86. }
  87. // Defer block to end background task when function exits
  88. defer {
  89. if backgroundTaskID != .invalid {
  90. Task {
  91. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  92. }
  93. backgroundTaskID = .invalid
  94. }
  95. }
  96. do {
  97. // Get NSManagedObjectID of Preset
  98. guard let tempTargetID = await fetchTempTargetID(preset),
  99. let tempTargetObject = try viewContext.existingObject(with: tempTargetID) as? TempTargetStored
  100. else { return false }
  101. // Enable TempTarget
  102. tempTargetObject.enabled = true
  103. tempTargetObject.date = Date()
  104. tempTargetObject.isUploadedToNS = false
  105. // Disable previous overrides if necessary, without starting a background task
  106. await disableAllActiveTempTargets(
  107. except: tempTargetID,
  108. createTempTargetRunEntry: true,
  109. shouldStartBackgroundTask: false
  110. )
  111. if viewContext.hasChanges {
  112. try viewContext.save()
  113. // Update State variables in TempTargetView
  114. Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
  115. // Await the notification
  116. print("Waiting for notification...")
  117. guard let tempTargetDate = tempTargetObject.date, let tempTarget = tempTargetObject.target,
  118. let tempTargetDuration = tempTargetObject.duration else { return false }
  119. let tempTargetToStoreAsJSON = TempTarget(
  120. name: tempTargetObject.name,
  121. createdAt: tempTargetDate,
  122. targetTop: tempTarget as Decimal,
  123. targetBottom: tempTarget as Decimal,
  124. duration: tempTargetDuration as Decimal,
  125. enteredBy: TempTarget.local,
  126. reason: TempTarget.custom,
  127. isPreset: tempTargetObject.isPreset,
  128. enabled: tempTargetObject.enabled,
  129. halfBasalTarget: tempTargetObject.halfBasalTarget as Decimal?
  130. )
  131. // Save the temp targets to JSON so that they get used by oref
  132. tempTargetsStorage.saveTempTargetsToStorage([tempTargetToStoreAsJSON])
  133. await awaitNotification(.didUpdateTempTargetConfiguration)
  134. print("Notification received, continuing...")
  135. return true
  136. }
  137. } catch {
  138. // Handle error and ensure background task is ended
  139. debugPrint("Failed to enact TempTarget: \(error.localizedDescription)")
  140. }
  141. return false
  142. }
  143. func cancelTempTarget() async {
  144. await disableAllActiveTempTargets(createTempTargetRunEntry: true, shouldStartBackgroundTask: true)
  145. }
  146. @MainActor func disableAllActiveTempTargets(
  147. except tempTargetID: NSManagedObjectID? = nil,
  148. createTempTargetRunEntry: Bool,
  149. shouldStartBackgroundTask: Bool = true
  150. ) async {
  151. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  152. if shouldStartBackgroundTask {
  153. // Start background task
  154. backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget Cancel") {
  155. guard backgroundTaskID != .invalid else { return }
  156. Task {
  157. // End background task when the time is about to expire
  158. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  159. }
  160. backgroundTaskID = .invalid
  161. }
  162. }
  163. // Defer block to end background task when function exits, only if it was started
  164. defer {
  165. if shouldStartBackgroundTask, backgroundTaskID != .invalid {
  166. Task {
  167. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  168. }
  169. backgroundTaskID = .invalid
  170. }
  171. }
  172. // Get NSManagedObjectID of all active temp Targets
  173. let ids = await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
  174. do {
  175. // Fetch existing OverrideStored objects
  176. let results = try ids.compactMap { id in
  177. try self.viewContext.existingObject(with: id) as? TempTargetStored
  178. }
  179. // Return early if no results
  180. guard !results.isEmpty else { return }
  181. // Create TempTargetRunStored entry if needed
  182. if createTempTargetRunEntry {
  183. // Use the first temp target to create a new TempTargetRunStored entry
  184. if let canceledTempTarget = results.first {
  185. let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
  186. newTempTargetRunStored.id = UUID()
  187. newTempTargetRunStored.name = canceledTempTarget.name
  188. newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
  189. newTempTargetRunStored.endDate = Date()
  190. newTempTargetRunStored
  191. .target = canceledTempTarget.target ?? 0
  192. newTempTargetRunStored.tempTarget = canceledTempTarget
  193. newTempTargetRunStored.isUploadedToNS = false
  194. }
  195. }
  196. // Disable all override except the one with overrideID
  197. for tempTargetToCancel in results {
  198. if tempTargetToCancel.objectID != tempTargetID {
  199. tempTargetToCancel.enabled = false
  200. tempTargetToCancel.isUploadedToNS = false
  201. }
  202. }
  203. if viewContext.hasChanges {
  204. try viewContext.save()
  205. // Update State variables in OverrideView
  206. Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
  207. }
  208. // Await the notification
  209. print("Waiting for notification...")
  210. await awaitNotification(.didUpdateTempTargetConfiguration)
  211. print("Notification received, continuing...")
  212. } catch {
  213. debugPrint(
  214. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Temp Targets with error: \(error.localizedDescription)"
  215. )
  216. }
  217. }
  218. }