import CoreData import Foundation import UIKit @available(iOS 16.0, *) final class OverridePresetsIntentRequest: BaseIntentsRequest { enum overridePresetsError: Error { case noTempOverrideFound case noDurationDefined case noActiveOverride } func fetchAndProcessOverrides() async -> [OverridePreset] { // Fetch all Override Presets via OverrideStorage let allOverridePresetsIDs = await overrideStorage.fetchForOverridePresets() // Since we are fetching on a different background Thread we need to unpack the NSManagedObjectID on the correct Thread first return await coredataContext.perform { do { let overrideObjects = try allOverridePresetsIDs.compactMap { id in try self.coredataContext.existingObject(with: id) as? OverrideStored } return overrideObjects.map { object in guard let id = object.id, let name = object.name else { return OverridePreset(id: UUID().uuidString, name: "") } return OverridePreset(id: id, name: name) } } catch { debugPrint( "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while fetching/ processing the overrides Array: \(error.localizedDescription)" ) return [OverridePreset(id: UUID().uuidString, name: "")] } } } func fetchIDs(_ uuid: [OverridePreset.ID]) async -> [OverridePreset] { await coredataContext.perform { let fetchRequest: NSFetchRequest = OverrideStored.fetchRequest() fetchRequest.predicate = NSPredicate(format: "id IN %@", uuid) do { let result = try self.coredataContext.fetch(fetchRequest) if result.isEmpty { debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) No OverrideStored found for ids: \(uuid)") return [OverridePreset(id: UUID().uuidString, name: "")] } return result.map { overrideStored in OverridePreset(id: overrideStored.id ?? UUID().uuidString, name: overrideStored.name ?? "") } } catch let error as NSError { debugPrint( "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)" ) return [OverridePreset(id: UUID().uuidString, name: "")] } } } private func fetchOverrideID(_ preset: OverridePreset) async -> NSManagedObjectID? { let fetchRequest: NSFetchRequest = OverrideStored.fetchRequest() fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id) fetchRequest.fetchLimit = 1 return await coredataContext.perform { do { return try self.coredataContext.fetch(fetchRequest).first?.objectID } catch { debugPrint( "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Override: \(error.localizedDescription)" ) return nil } } } @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool { // Start background task var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") { guard backgroundTaskID != .invalid else { return } Task { // End background task when the time is about to expire UIApplication.shared.endBackgroundTask(backgroundTaskID) } backgroundTaskID = .invalid } // Defer block to end background task when function exits defer { if backgroundTaskID != .invalid { Task { UIApplication.shared.endBackgroundTask(backgroundTaskID) } backgroundTaskID = .invalid } } do { // Get NSManagedObjectID of Preset guard let overrideID = await fetchOverrideID(preset), let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else { return false } // Enable Override overrideObject.enabled = true overrideObject.date = Date() overrideObject.isUploadedToNS = false // Disable previous overrides if necessary, without starting a background task await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false) if viewContext.hasChanges { try viewContext.save() // Update State variables in OverrideView Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil) // Await the notification print("Waiting for notification...") await awaitNotification(.didUpdateOverrideConfiguration) print("Notification received, continuing...") return true } } catch { // Handle error and ensure background task is ended debugPrint("Failed to enact Override: \(error.localizedDescription)") } return false } func cancelOverride() async { await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true) } @MainActor func disableAllActiveOverrides( except overrideID: NSManagedObjectID? = nil, createOverrideRunEntry: Bool, shouldStartBackgroundTask: Bool = true ) async { var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid if shouldStartBackgroundTask { // Start background task backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Cancel") { guard backgroundTaskID != .invalid else { return } Task { // End background task when the time is about to expire UIApplication.shared.endBackgroundTask(backgroundTaskID) } backgroundTaskID = .invalid } } // Defer block to end background task when function exits, only if it was started defer { if shouldStartBackgroundTask, backgroundTaskID != .invalid { Task { UIApplication.shared.endBackgroundTask(backgroundTaskID) } backgroundTaskID = .invalid } } // Get NSManagedObjectID of all active overrides let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit do { // Fetch existing OverrideStored objects let results = try ids.compactMap { id in try self.viewContext.existingObject(with: id) as? OverrideStored } // Return early if no results guard !results.isEmpty else { return } // Create OverrideRunStored entry if needed if createOverrideRunEntry, let canceledOverride = results.first { let newOverrideRunStored = OverrideRunStored(context: viewContext) newOverrideRunStored.id = UUID() newOverrideRunStored.name = canceledOverride.name newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast newOverrideRunStored.endDate = Date() newOverrideRunStored.target = NSDecimalNumber( decimal: overrideStorage.calculateTarget(override: canceledOverride) ) newOverrideRunStored.override = canceledOverride newOverrideRunStored.isUploadedToNS = false } // Disable all overrides except the one specified for overrideToCancel in results { if overrideToCancel.objectID != overrideID { overrideToCancel.enabled = false overrideToCancel.isUploadedToNS = false } } if viewContext.hasChanges { try viewContext.save() // Update State variables in OverrideView Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil) } // Await the notification print("Waiting for notification...") await awaitNotification(.didUpdateOverrideConfiguration) print("Notification received, continuing...") } catch { debugPrint( "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)" ) } } }