OverridePresetsIntentRequest.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import CoreData
  2. import Foundation
  3. import UIKit
  4. @available(iOS 16.0, *) final class OverridePresetsIntentRequest: BaseIntentsRequest {
  5. enum overridePresetsError: Error {
  6. case noTempOverrideFound
  7. case noDurationDefined
  8. case noActiveOverride
  9. }
  10. func fetchAndProcessOverrides() async -> [OverridePreset] {
  11. // Fetch all Override Presets via OverrideStorage
  12. let allOverridePresetsIDs = await overrideStorage.fetchForOverridePresets()
  13. // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first
  14. return await coredataContext.perform {
  15. do {
  16. let overrideObjects = try allOverridePresetsIDs.compactMap { id in
  17. try self.coredataContext.existingObject(with: id) as? OverrideStored
  18. }
  19. return overrideObjects.map { object in
  20. guard let id = object.id,
  21. let name = object.name else { return OverridePreset(id: UUID().uuidString, name: "") }
  22. return OverridePreset(id: id, name: name)
  23. }
  24. } catch {
  25. debugPrint(
  26. "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while fetching/ processing the overrides Array: \(error.localizedDescription)"
  27. )
  28. return [OverridePreset(id: UUID().uuidString, name: "")]
  29. }
  30. }
  31. }
  32. func fetchIDs(_ uuid: [OverridePreset.ID]) async -> [OverridePreset] {
  33. await coredataContext.perform {
  34. let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
  35. fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
  36. do {
  37. let result = try self.coredataContext.fetch(fetchRequest)
  38. if result.isEmpty {
  39. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) No OverrideStored found for ids: \(uuid)")
  40. return [OverridePreset(id: UUID().uuidString, name: "")]
  41. }
  42. return result.map { overrideStored in
  43. OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "")
  44. }
  45. } catch let error as NSError {
  46. debugPrint(
  47. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
  48. )
  49. return [OverridePreset(id: UUID().uuidString, name: "")]
  50. }
  51. }
  52. }
  53. private func fetchOverrideID(_ preset: OverridePreset) async -> NSManagedObjectID? {
  54. let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
  55. fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
  56. fetchRequest.fetchLimit = 1
  57. return await coredataContext.perform {
  58. do {
  59. return try self.coredataContext.fetch(fetchRequest).first?.objectID
  60. } catch {
  61. debugPrint(
  62. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)"
  63. )
  64. return nil
  65. }
  66. }
  67. }
  68. @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
  69. // Start background task
  70. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  71. backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
  72. guard backgroundTaskID != .invalid else { return }
  73. Task {
  74. // End background task when the time is about to expire
  75. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  76. }
  77. backgroundTaskID = .invalid
  78. }
  79. // Defer block to end background task when function exits
  80. defer {
  81. if backgroundTaskID != .invalid {
  82. Task {
  83. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  84. }
  85. backgroundTaskID = .invalid
  86. }
  87. }
  88. do {
  89. // Get NSManagedObjectID of Preset
  90. guard let overrideID = await fetchOverrideID(preset),
  91. let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored
  92. else { return false }
  93. // Enable Override
  94. overrideObject.enabled = true
  95. overrideObject.date = Date()
  96. overrideObject.isUploadedToNS = false
  97. // Disable previous overrides if necessary, without starting a background task
  98. await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
  99. if viewContext.hasChanges {
  100. try viewContext.save()
  101. // Update State variables in OverrideView
  102. Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
  103. // Await the notification
  104. print("Waiting for notification...")
  105. await awaitNotification(.didUpdateOverrideConfiguration)
  106. print("Notification received, continuing...")
  107. return true
  108. }
  109. } catch {
  110. // Handle error and ensure background task is ended
  111. debugPrint("Failed to enact Override: \(error.localizedDescription)")
  112. }
  113. return false
  114. }
  115. func cancelOverride() async {
  116. await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true)
  117. }
  118. @MainActor func disableAllActiveOverrides(
  119. except overrideID: NSManagedObjectID? = nil,
  120. createOverrideRunEntry: Bool,
  121. shouldStartBackgroundTask: Bool = true
  122. ) async {
  123. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  124. if shouldStartBackgroundTask {
  125. // Start background task
  126. backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Cancel") {
  127. guard backgroundTaskID != .invalid else { return }
  128. Task {
  129. // End background task when the time is about to expire
  130. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  131. }
  132. backgroundTaskID = .invalid
  133. }
  134. }
  135. // Defer block to end background task when function exits, only if it was started
  136. defer {
  137. if shouldStartBackgroundTask, backgroundTaskID != .invalid {
  138. Task {
  139. UIApplication.shared.endBackgroundTask(backgroundTaskID)
  140. }
  141. backgroundTaskID = .invalid
  142. }
  143. }
  144. // Get NSManagedObjectID of all active overrides
  145. let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
  146. do {
  147. // Fetch existing OverrideStored objects
  148. let results = try ids.compactMap { id in
  149. try self.viewContext.existingObject(with: id) as? OverrideStored
  150. }
  151. // Return early if no results
  152. guard !results.isEmpty else { return }
  153. // Create OverrideRunStored entry if needed
  154. if createOverrideRunEntry, let canceledOverride = results.first {
  155. let newOverrideRunStored = OverrideRunStored(context: viewContext)
  156. newOverrideRunStored.id = UUID()
  157. newOverrideRunStored.name = canceledOverride.name
  158. newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
  159. newOverrideRunStored.endDate = Date()
  160. newOverrideRunStored.target = NSDecimalNumber(
  161. decimal: overrideStorage.calculateTarget(override: canceledOverride)
  162. )
  163. newOverrideRunStored.override = canceledOverride
  164. newOverrideRunStored.isUploadedToNS = false
  165. }
  166. // Disable all overrides except the one specified
  167. for overrideToCancel in results {
  168. if overrideToCancel.objectID != overrideID {
  169. overrideToCancel.enabled = false
  170. overrideToCancel.isUploadedToNS = false
  171. }
  172. }
  173. if viewContext.hasChanges {
  174. try viewContext.save()
  175. // Update State variables in OverrideView
  176. Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
  177. }
  178. // Await the notification
  179. print("Waiting for notification...")
  180. await awaitNotification(.didUpdateOverrideConfiguration)
  181. print("Notification received, continuing...")
  182. } catch {
  183. debugPrint(
  184. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
  185. )
  186. }
  187. }
  188. }