TempPresetsIntentRequest.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. await awaitNotification(.didUpdateTempTargetConfiguration)
  118. print("Notification received, continuing...")
  119. return true
  120. }
  121. } catch {
  122. // Handle error and ensure background task is ended
  123. debugPrint("Failed to enact TempTarget: \(error.localizedDescription)")
  124. }
  125. return false
  126. }
  127. func cancelTempTarget() async {
  128. await disableAllActiveTempTargets(createTempTargetRunEntry: true, shouldStartBackgroundTask: true)
  129. }
  130. @MainActor func disableAllActiveTempTargets(
  131. except tempTargetID: NSManagedObjectID? = nil,
  132. createTempTargetRunEntry: Bool,
  133. shouldStartBackgroundTask: Bool = true
  134. ) async {
  135. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  136. if shouldStartBackgroundTask {
  137. // Start background task
  138. backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget Cancel") {
  139. guard backgroundTaskID != .invalid else { return }
  140. Task {
  141. // End background task when the time is about to expire
  142. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  143. }
  144. backgroundTaskID = .invalid
  145. }
  146. }
  147. // Defer block to end background task when function exits, only if it was started
  148. defer {
  149. if shouldStartBackgroundTask, backgroundTaskID != .invalid {
  150. Task {
  151. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  152. }
  153. backgroundTaskID = .invalid
  154. }
  155. }
  156. // Get NSManagedObjectID of all active temp Targets
  157. let ids = await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
  158. do {
  159. // Fetch existing OverrideStored objects
  160. let results = try ids.compactMap { id in
  161. try self.viewContext.existingObject(with: id) as? TempTargetStored
  162. }
  163. // Return early if no results
  164. guard !results.isEmpty else { return }
  165. // Create TempTargetRunStored entry if needed
  166. if createTempTargetRunEntry {
  167. // Use the first temp target to create a new TempTargetRunStored entry
  168. if let canceledTempTarget = results.first {
  169. let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
  170. newTempTargetRunStored.id = UUID()
  171. newTempTargetRunStored.name = canceledTempTarget.name
  172. newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
  173. newTempTargetRunStored.endDate = Date()
  174. newTempTargetRunStored
  175. .target = canceledTempTarget.target ?? 0
  176. newTempTargetRunStored.tempTarget = canceledTempTarget
  177. newTempTargetRunStored.isUploadedToNS = false
  178. }
  179. }
  180. // Disable all override except the one with overrideID
  181. for tempTargetToCancel in results {
  182. if tempTargetToCancel.objectID != tempTargetID {
  183. tempTargetToCancel.enabled = false
  184. tempTargetToCancel.isUploadedToNS = false
  185. }
  186. }
  187. if viewContext.hasChanges {
  188. try viewContext.save()
  189. // Update State variables in OverrideView
  190. Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
  191. }
  192. // Await the notification
  193. print("Waiting for notification...")
  194. await awaitNotification(.didUpdateTempTargetConfiguration)
  195. print("Notification received, continuing...")
  196. } catch {
  197. debugPrint(
  198. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Temp Targets with error: \(error.localizedDescription)"
  199. )
  200. }
  201. }
  202. }