OverridePresetsIntentRequest.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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 throws -> [OverridePreset] {
  11. do {
  12. // Fetch all Override Presets via OverrideStorage
  13. let allOverridePresetsIDs = try await overrideStorage.fetchForOverridePresets()
  14. // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first
  15. return try await coredataContext.perform {
  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. }
  25. } catch {
  26. debug(
  27. .default,
  28. "\(DebuggingIdentifiers.failed) Error fetching/processing overrides: \(error.localizedDescription)"
  29. )
  30. throw error
  31. }
  32. }
  33. func fetchIDs(_ uuid: [OverridePreset.ID]) async throws -> [OverridePreset] {
  34. try await coredataContext.perform {
  35. let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
  36. fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid)
  37. do {
  38. let result = try self.coredataContext.fetch(fetchRequest)
  39. if result.isEmpty {
  40. debug(
  41. .default,
  42. "\(DebuggingIdentifiers.failed) No OverrideStored found for ids: \(uuid)"
  43. )
  44. throw overridePresetsError.noTempOverrideFound
  45. }
  46. return result.map { overrideStored in
  47. OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "")
  48. }
  49. } catch {
  50. debug(
  51. .default,
  52. "\(DebuggingIdentifiers.failed) Failed to fetch Override: \(error.localizedDescription)"
  53. )
  54. throw error
  55. }
  56. }
  57. }
  58. private func fetchOverrideID(_ preset: OverridePreset) async throws -> NSManagedObjectID {
  59. let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
  60. fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
  61. fetchRequest.fetchLimit = 1
  62. return try await coredataContext.perform {
  63. guard let objectID = try self.coredataContext.fetch(fetchRequest).first?.objectID else {
  64. debug(
  65. .default,
  66. "\(DebuggingIdentifiers.failed) No override found for preset: \(preset.name)"
  67. )
  68. throw overridePresetsError.noTempOverrideFound
  69. }
  70. return objectID
  71. }
  72. }
  73. @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
  74. // Start background task
  75. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  76. backgroundTaskID = startBackgroundTask(withName: "Override Upload")
  77. do {
  78. // Get NSManagedObjectID of Preset
  79. let overrideID = try await fetchOverrideID(preset)
  80. guard let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else {
  81. throw overridePresetsError.noTempOverrideFound
  82. }
  83. // Enable Override
  84. overrideObject.enabled = true
  85. overrideObject.date = Date()
  86. overrideObject.isUploadedToNS = false
  87. // Disable previous overrides if necessary
  88. await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
  89. if viewContext.hasChanges {
  90. try viewContext.save()
  91. Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
  92. await awaitNotification(.didUpdateOverrideConfiguration)
  93. return true
  94. }
  95. endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Upload")
  96. return false
  97. } catch {
  98. debug(
  99. .default,
  100. "\(DebuggingIdentifiers.failed) Failed to enact override: \(error.localizedDescription)"
  101. )
  102. endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Upload")
  103. return false
  104. }
  105. }
  106. func cancelOverride() async {
  107. await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true)
  108. }
  109. @MainActor func disableAllActiveOverrides(
  110. except overrideID: NSManagedObjectID? = nil,
  111. createOverrideRunEntry: Bool,
  112. shouldStartBackgroundTask: Bool = true
  113. ) async {
  114. var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
  115. if shouldStartBackgroundTask {
  116. // Start background task
  117. backgroundTaskID = startBackgroundTask(withName: "Override Cancel")
  118. }
  119. do {
  120. // Get NSManagedObjectID of all active overrides
  121. let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
  122. // Fetch existing OverrideStored objects
  123. let results = try ids.compactMap { id in
  124. try self.viewContext.existingObject(with: id) as? OverrideStored
  125. }
  126. // Return early if no results
  127. guard !results.isEmpty else { return }
  128. // Create OverrideRunStored entry if needed
  129. if createOverrideRunEntry, let canceledOverride = results.first {
  130. let newOverrideRunStored = OverrideRunStored(context: viewContext)
  131. newOverrideRunStored.id = UUID()
  132. newOverrideRunStored.name = canceledOverride.name
  133. newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
  134. newOverrideRunStored.endDate = Date()
  135. newOverrideRunStored.target = NSDecimalNumber(
  136. decimal: overrideStorage.calculateTarget(override: canceledOverride)
  137. )
  138. newOverrideRunStored.override = canceledOverride
  139. newOverrideRunStored.isUploadedToNS = false
  140. }
  141. // Disable all overrides except the one specified
  142. for overrideToCancel in results {
  143. if overrideToCancel.objectID != overrideID {
  144. overrideToCancel.enabled = false
  145. overrideToCancel.isUploadedToNS = false
  146. }
  147. }
  148. if viewContext.hasChanges {
  149. try viewContext.save()
  150. // Update State variables in OverrideView
  151. Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
  152. }
  153. // Await the notification
  154. print("Waiting for notification...")
  155. await awaitNotification(.didUpdateOverrideConfiguration)
  156. print("Notification received, continuing...")
  157. endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
  158. } catch {
  159. debugPrint(
  160. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
  161. )
  162. endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
  163. }
  164. }
  165. }