import CoreData import Foundation @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 { do { guard let overrideID = await fetchOverrideID(preset), let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else { return false } overrideObject.enabled = true overrideObject.date = Date() overrideObject.isUploadedToNS = false // Disable previous Overrides /// do not create a OverrideRunEntry because we only want that if we cancel a running Override, not when enacting a Preset await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true) if viewContext.hasChanges { try viewContext.save() // Update State variables in OverrideView Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil) return true } } catch let error as NSError { debugPrint( "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override: \(error.localizedDescription)" ) } return false } func cancelOverride() async { await disableAllActiveOverrides(createOverrideRunEntry: true) } @MainActor func disableAllActiveOverrides( except overrideID: NSManagedObjectID? = nil, createOverrideRunEntry _: Bool ) async { // Get ALL NSManagedObject IDs of ALL active Overrides to cancel every single Override let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit await viewContext.perform { do { // Fetch the existing OverrideStored objects from the context let results = try ids.compactMap { id in try self.viewContext.existingObject(with: id) as? OverrideStored } // If there are no results, return early guard !results.isEmpty else { return } // Check if we also need to create a corresponding OverrideRunStored entry, i.e. when the User uses the Cancel Button in Override View // Auggie - commented out this if statment, we always need to do this for overrides // if createOverrideRunEntry { // Use the first override to create a new OverrideRunStored entry if let canceledOverride = results.first { let newOverrideRunStored = OverrideRunStored(context: self.viewContext) newOverrideRunStored.id = UUID() newOverrideRunStored.name = canceledOverride.name newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast newOverrideRunStored.endDate = Date() newOverrideRunStored .target = NSDecimalNumber( decimal: self.overrideStorage .calculateTarget(override: canceledOverride) ) newOverrideRunStored.override = canceledOverride newOverrideRunStored.isUploadedToNS = false } // } // Disable all override except the one with overrideID for overrideToCancel in results { if overrideToCancel.objectID != overrideID { overrideToCancel.enabled = false overrideToCancel.isUploadedToNS = false } } // Save the context if there are changes if self.viewContext.hasChanges { try self.viewContext.save() // Update State variables in OverrideView Foundation.NotificationCenter.default.post(name: .didUpdateOverrideConfiguration, object: nil) } } catch { debugPrint( "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)" ) } } } }