Преглед изворни кода

Merge pull request #364 from nightscout/bgtask-defer

Replace All `defer` Blocks for Background Task Ending with Explicit Calls
Andreas Stokholm пре 1 година
родитељ
комит
f20d491057

+ 4 - 4
Trio.xcodeproj/project.pbxproj

@@ -212,8 +212,6 @@
 		491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */; };
 		491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */; };
 		491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */; };
 		491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */; };
 		491D6FC02D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */; };
 		491D6FC02D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */; };
-		49249B1C2D46E45E000F4866 /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */; };
-		49249B382D46E76A000F4866 /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B372D46E76A000F4866 /* TDD.swift */; };
 		49B9B57F2D5768D2009C6B59 /* AdjustmentStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */; };
 		49B9B57F2D5768D2009C6B59 /* AdjustmentStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
 		53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */; };
@@ -506,6 +504,7 @@
 		DD6B7CBB2C7FBBFA00B75029 /* ReviewInsulinActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */; };
 		DD6B7CBB2C7FBBFA00B75029 /* ReviewInsulinActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */; };
 		DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */; };
 		DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */; };
 		DD6F63CC2D27F615007D94CF /* TreatmentMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */; };
 		DD6F63CC2D27F615007D94CF /* TreatmentMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */; };
+		DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */; };
 		DD8262CB2D289297009F6F62 /* BolusConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */; };
 		DD8262CB2D289297009F6F62 /* BolusConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */; };
 		DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD88C8E12C50420800F2D558 /* DefinitionRow.swift */; };
 		DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD88C8E12C50420800F2D558 /* DefinitionRow.swift */; };
 		DD940BAA2CA7585D000830A5 /* GlucoseColorScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */; };
 		DD940BAA2CA7585D000830A5 /* GlucoseColorScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */; };
@@ -921,8 +920,6 @@
 		491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
-		49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
-		49249B372D46E76A000F4866 /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentStored+Helper.swift"; sourceTree = "<group>"; };
 		49B9B57E2D5768D2009C6B59 /* AdjustmentStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentStored+Helper.swift"; sourceTree = "<group>"; };
 		4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorRootView.swift; sourceTree = "<group>"; };
 		4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorRootView.swift; sourceTree = "<group>"; };
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
 		505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorStateModel.swift; sourceTree = "<group>"; };
@@ -1216,6 +1213,7 @@
 		DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewInsulinActionView.swift; sourceTree = "<group>"; };
 		DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewInsulinActionView.swift; sourceTree = "<group>"; };
 		DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSchemeOption.swift; sourceTree = "<group>"; };
 		DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSchemeOption.swift; sourceTree = "<group>"; };
 		DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentMenuView.swift; sourceTree = "<group>"; };
 		DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentMenuView.swift; sourceTree = "<group>"; };
+		DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BackgroundTask+Helper.swift"; sourceTree = "<group>"; };
 		DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusConfirmationView.swift; sourceTree = "<group>"; };
 		DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusConfirmationView.swift; sourceTree = "<group>"; };
 		DD88C8E12C50420800F2D558 /* DefinitionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionRow.swift; sourceTree = "<group>"; };
 		DD88C8E12C50420800F2D558 /* DefinitionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionRow.swift; sourceTree = "<group>"; };
 		DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseColorScheme.swift; sourceTree = "<group>"; };
 		DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseColorScheme.swift; sourceTree = "<group>"; };
@@ -2126,6 +2124,7 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */,
 				CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */,
 				CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */,
 				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
@@ -3839,6 +3838,7 @@
 				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
 				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
+				DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */,
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,

+ 9 - 9
Trio/Sources/APS/APSManager.swift

@@ -81,7 +81,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
 
     private var lifetime = Lifetime()
     private var lifetime = Lifetime()
 
 
-    private var backGroundTaskID: UIBackgroundTaskIdentifier?
+    private var backgroundTaskID: UIBackgroundTaskIdentifier?
 
 
     var pumpManager: PumpManagerUI? {
     var pumpManager: PumpManagerUI? {
         get { deviceDataManager.pumpManager }
         get { deviceDataManager.pumpManager }
@@ -224,7 +224,7 @@ final class BaseAPSManager: APSManager, Injectable {
             // Cleanup background task
             // Cleanup background task
             if let backgroundTask = backgroundTask {
             if let backgroundTask = backgroundTask {
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
-                self.backGroundTaskID = .invalid
+                self.backgroundTaskID = .invalid
             }
             }
         }
         }
     }
     }
@@ -250,13 +250,13 @@ final class BaseAPSManager: APSManager, Injectable {
     private func setupLoop() async -> (LoopStats, UIBackgroundTaskIdentifier?) {
     private func setupLoop() async -> (LoopStats, UIBackgroundTaskIdentifier?) {
         // Start background task
         // Start background task
         let backgroundTask = await UIApplication.shared.beginBackgroundTask(withName: "Loop starting") { [weak self] in
         let backgroundTask = await UIApplication.shared.beginBackgroundTask(withName: "Loop starting") { [weak self] in
-            guard let self, let backgroundTask = self.backGroundTaskID else { return }
+            guard let self, let backgroundTask = self.backgroundTaskID else { return }
             Task {
             Task {
                 UIApplication.shared.endBackgroundTask(backgroundTask)
                 UIApplication.shared.endBackgroundTask(backgroundTask)
             }
             }
-            self.backGroundTaskID = .invalid
+            self.backgroundTaskID = .invalid
         }
         }
-        backGroundTaskID = backgroundTask
+        backgroundTaskID = backgroundTask
 
 
         // Set loop start time
         // Set loop start time
         lastLoopStartDate = Date()
         lastLoopStartDate = Date()
@@ -325,9 +325,9 @@ final class BaseAPSManager: APSManager, Injectable {
 
 
         if let error = error {
         if let error = error {
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
-            if let backgroundTask = backGroundTaskID {
+            if let backgroundTask = backgroundTaskID {
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
-                backGroundTaskID = .invalid
+                backgroundTaskID = .invalid
             }
             }
             processError(error)
             processError(error)
         } else {
         } else {
@@ -343,9 +343,9 @@ final class BaseAPSManager: APSManager, Injectable {
         }
         }
 
 
         // End of the BG tasks
         // End of the BG tasks
-        if let backgroundTask = backGroundTaskID {
+        if let backgroundTask = backgroundTaskID {
             await UIApplication.shared.endBackgroundTask(backgroundTask)
             await UIApplication.shared.endBackgroundTask(backgroundTask)
-            backGroundTaskID = .invalid
+            backgroundTaskID = .invalid
         }
         }
     }
     }
 
 

+ 12 - 17
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -248,29 +248,22 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         var filteredByDate: [BloodGlucose] = []
         var filteredByDate: [BloodGlucose] = []
         var filtered: [BloodGlucose] = []
         var filtered: [BloodGlucose] = []
 
 
-        // start background time extension
-        var backGroundFetchBGTaskID: UIBackgroundTaskIdentifier?
-        backGroundFetchBGTaskID = await UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
-            guard let bg = backGroundFetchBGTaskID else { return }
-            UIApplication.shared.endBackgroundTask(bg)
-            backGroundFetchBGTaskID = .invalid
-        }
+        // Start background task
+        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+        backgroundTaskID = startBackgroundTask(withName: "Glucose Store and Heartbeat Decision")
 
 
-        defer {
-            if let backgroundTask = backGroundFetchBGTaskID {
-                Task {
-                    await UIApplication.shared.endBackgroundTask(backgroundTask)
-                }
-                backGroundFetchBGTaskID = .invalid
-            }
+        guard newGlucose.isNotEmpty else {
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
+            return
         }
         }
 
 
-        guard newGlucose.isNotEmpty else { return }
-
         filteredByDate = newGlucose.filter { $0.dateString > syncDate }
         filteredByDate = newGlucose.filter { $0.dateString > syncDate }
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
 
 
-        guard filtered.isNotEmpty else { return }
+        guard filtered.isNotEmpty else {
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
+            return
+        }
         debug(.deviceManager, "New glucose found")
         debug(.deviceManager, "New glucose found")
 
 
         // filter the data if it is the case
         // filter the data if it is the case
@@ -289,6 +282,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
 
         try await glucoseStorage.storeGlucose(filtered)
         try await glucoseStorage.storeGlucose(filtered)
         deviceDataManager.heartbeat(date: Date())
         deviceDataManager.heartbeat(date: Date())
+
+        endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
     }
     }
 
 
     func sourceInfo() -> [String: Any]? {
     func sourceInfo() -> [String: Any]? {

+ 29 - 0
Trio/Sources/Helpers/BackgroundTask+Helper.swift

@@ -0,0 +1,29 @@
+import UIKit
+
+/// Ends a background task safely and ensures it is not called multiple times.
+///
+/// - Parameter taskID: The background task identifier to be ended.
+func endBackgroundTaskSafely(_ taskID: inout UIBackgroundTaskIdentifier, taskName: String = "Unnamed Task") {
+    if taskID != .invalid {
+        UIApplication.shared.endBackgroundTask(taskID)
+        debug(.default, "Background task '\(taskName)' ended successfully.")
+        taskID = .invalid
+    } else {
+        debug(.default, "Background task '\(taskName)' was already invalid or ended.")
+    }
+}
+
+/// Starts a background task and handles its expiration safely.
+///
+/// - Parameter name: The background task name.
+func startBackgroundTask(withName name: String) -> UIBackgroundTaskIdentifier {
+    var taskID = UIBackgroundTaskIdentifier.invalid
+
+    taskID = UIApplication.shared.beginBackgroundTask(withName: name) {
+        Task { @MainActor in
+            endBackgroundTaskSafely(&taskID, taskName: name)
+        }
+    }
+
+    return taskID
+}

+ 3 - 16
Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntentRequest.swift

@@ -11,18 +11,9 @@ import UIKit
     /// - Throws: An error if the restart process fails.
     /// - Throws: An error if the restart process fails.
     /// - Returns: Void upon successful restart.
     /// - Returns: Void upon successful restart.
     @MainActor func performRestart() async throws {
     @MainActor func performRestart() async throws {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-
         // Start background task
         // Start background task
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Restart Live Activity") {
-            Task { @MainActor in
-                if backgroundTaskID != .invalid {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                    backgroundTaskID = .invalid
-                    debug(.default, "Background task expired and ended.")
-                }
-            }
-        }
+        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+        backgroundTaskID = startBackgroundTask(withName: "Restart Live Activity")
 
 
         guard backgroundTaskID != .invalid else {
         guard backgroundTaskID != .invalid else {
             debug(.default, "Failed to start background task.")
             debug(.default, "Failed to start background task.")
@@ -34,10 +25,6 @@ import UIKit
         await liveActivityManager.restartActivityFromLiveActivityIntent()
         await liveActivityManager.restartActivityFromLiveActivityIntent()
 
 
         // Ensure background task ends properly
         // Ensure background task ends properly
-        if backgroundTaskID != .invalid {
-            UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            debug(.default, "Background task ended successfully.")
-            backgroundTaskID = .invalid
-        }
+        endBackgroundTaskSafely(&backgroundTaskID, taskName: "Restart Live Activity")
     }
     }
 }
 }

+ 15 - 2
Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift

@@ -1,24 +1,28 @@
 import AppIntents
 import AppIntents
 import Foundation
 import Foundation
 
 
+/// An App Intent that allows users to activate an override preset through the Shortcuts app.
 struct ApplyOverridePresetIntent: AppIntent {
 struct ApplyOverridePresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title = LocalizedStringResource("Activate an override", table: "ShortcutsDetail")
     static var title = LocalizedStringResource("Activate an override", table: "ShortcutsDetail")
 
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription(.init("Activate an override", table: "ShortcutsDetail"))
     static var description = IntentDescription(.init("Activate an override", table: "ShortcutsDetail"))
 
 
+    /// The override preset to be applied.
     @Parameter(
     @Parameter(
         title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
         title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
         description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
         description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
     ) var preset: OverridePreset?
     ) var preset: OverridePreset?
 
 
+    /// A boolean parameter that determines whether confirmation is required before applying the override.
     @Parameter(
     @Parameter(
         title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
         title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
         description: LocalizedStringResource("If toggled, you will need to confirm before applying", table: "ShortcutsDetail"),
         description: LocalizedStringResource("If toggled, you will need to confirm before applying", table: "ShortcutsDetail"),
         default: true
         default: true
     ) var confirmBeforeApplying: Bool
     ) var confirmBeforeApplying: Bool
 
 
+    /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     static var parameterSummary: some ParameterSummary {
     static var parameterSummary: some ParameterSummary {
         When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
         When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
             Summary("Applying \(\.$preset) override", table: "ShortcutsDetail") {
             Summary("Applying \(\.$preset) override", table: "ShortcutsDetail") {
@@ -31,12 +35,18 @@ struct ApplyOverridePresetIntent: AppIntent {
         })
         })
     }
     }
 
 
+    /// Executes the intent to apply the selected override preset.
+    ///
+    /// - Returns: A dialog indicating whether the override was successfully applied or failed.
+    /// - Throws: An error if an issue occurs during execution.
     @MainActor func perform() async throws -> some ProvidesDialog {
     @MainActor func perform() async throws -> some ProvidesDialog {
         do {
         do {
+            // Determine which preset to apply
             let presetToApply: OverridePreset
             let presetToApply: OverridePreset
             if let preset = preset {
             if let preset = preset {
                 presetToApply = preset
                 presetToApply = preset
             } else {
             } else {
+                // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                 presetToApply = try await $preset.requestDisambiguation(
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
                     dialog: IntentDialog(LocalizedStringResource("Select override", table: "ShortcutsDetail"))
                     dialog: IntentDialog(LocalizedStringResource("Select override", table: "ShortcutsDetail"))
@@ -44,6 +54,8 @@ struct ApplyOverridePresetIntent: AppIntent {
             }
             }
 
 
             let displayName: String = presetToApply.name
             let displayName: String = presetToApply.name
+
+            // Request confirmation before applying if required
             if confirmBeforeApplying {
             if confirmBeforeApplying {
                 try await requestConfirmation(
                 try await requestConfirmation(
                     result: .result(
                     result: .result(
@@ -55,6 +67,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 )
                 )
             }
             }
 
 
+            // Apply the override and return the appropriate dialog message
             if await OverridePresetsIntentRequest().enactOverride(presetToApply) {
             if await OverridePresetsIntentRequest().enactOverride(presetToApply) {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(

+ 11 - 8
Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift

@@ -1,19 +1,22 @@
 import AppIntents
 import AppIntents
 import Foundation
 import Foundation
 
 
+/// An App Intent that allows users to cancel an active override through the Shortcuts app.
 struct CancelOverrideIntent: AppIntent {
 struct CancelOverrideIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title = LocalizedStringResource("Cancel override", table: "ShortcutsDetail")
     static var title = LocalizedStringResource("Cancel override", table: "ShortcutsDetail")
 
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription(.init("Cancel an active override", table: "ShortcutsDetail"))
     static var description = IntentDescription(.init("Cancel an active override", table: "ShortcutsDetail"))
 
 
+    /// Performs the intent action to cancel an active override.
+    ///
+    /// - Returns: A confirmation dialog indicating the override has been canceled.
+    /// - Throws: An error if the cancellation process fails.
     @MainActor func perform() async throws -> some ProvidesDialog {
     @MainActor func perform() async throws -> some ProvidesDialog {
-        do {
-            await OverridePresetsIntentRequest().cancelOverride()
-            return .result(
-                dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
-            )
-        }
+        await OverridePresetsIntentRequest().cancelOverride()
+        return .result(
+            dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
+        )
     }
     }
 }
 }

+ 17 - 0
Trio/Sources/Shortcuts/Override/OverridePresetEntity.swift

@@ -3,24 +3,41 @@ import Foundation
 import Intents
 import Intents
 import Swinject
 import Swinject
 
 
+/// Represents an override preset that can be used in the app.
 struct OverridePreset: AppEntity, Identifiable {
 struct OverridePreset: AppEntity, Identifiable {
+    /// Default query instance for fetching override presets.
     static var defaultQuery = OverridePresetsQuery()
     static var defaultQuery = OverridePresetsQuery()
 
 
+    /// Unique identifier for the override preset.
     var id: String
     var id: String
+
+    /// Name of the override preset.
     var name: String
     var name: String
 
 
+    /// Provides a display representation for the override preset.
     var displayRepresentation: DisplayRepresentation {
     var displayRepresentation: DisplayRepresentation {
         DisplayRepresentation(title: "\(name)")
         DisplayRepresentation(title: "\(name)")
     }
     }
 
 
+    /// Representation for the entity type when displayed in UI.
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Override"
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Override"
 }
 }
 
 
+/// Query structure for fetching override presets in an App Intent.
 struct OverridePresetsQuery: EntityQuery {
 struct OverridePresetsQuery: EntityQuery {
+    /// Fetches a list of override presets matching the given identifiers.
+    ///
+    /// - Parameter identifiers: A list of override preset IDs to fetch.
+    /// - Returns: An array of `OverridePreset` objects matching the given IDs.
+    /// - Throws: An error if the fetch operation fails.
     func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
     func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
         try await OverridePresetsIntentRequest().fetchIDs(identifiers)
         try await OverridePresetsIntentRequest().fetchIDs(identifiers)
     }
     }
 
 
+    /// Fetches a list of suggested override presets.
+    ///
+    /// - Returns: An array of available `OverridePreset` objects.
+    /// - Throws: An error if fetching fails.
     func suggestedEntities() async throws -> [OverridePreset] {
     func suggestedEntities() async throws -> [OverridePreset] {
         try await OverridePresetsIntentRequest().fetchAndProcessOverrides()
         try await OverridePresetsIntentRequest().fetchAndProcessOverrides()
     }
     }

+ 88 - 65
Trio/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift

@@ -9,6 +9,14 @@ import UIKit
         case noActiveOverride
         case noActiveOverride
     }
     }
 
 
+    private var intentSuccess: Bool = false
+
+    /**
+     Fetches and processes override presets from Core Data.
+
+     - Returns: An array of `OverridePreset` objects.
+     - Throws: An error if fetching fails or Core Data operations fail.
+     */
     func fetchAndProcessOverrides() async throws -> [OverridePreset] {
     func fetchAndProcessOverrides() async throws -> [OverridePreset] {
         do {
         do {
             // Fetch all Override Presets via OverrideStorage
             // Fetch all Override Presets via OverrideStorage
@@ -35,6 +43,13 @@ import UIKit
         }
         }
     }
     }
 
 
+    /**
+     Fetches override presets by their IDs.
+
+     - Parameter uuid: An array of `OverridePreset.ID` values to fetch.
+     - Returns: An array of `OverridePreset` objects matching the provided IDs.
+     - Throws: `overridePresetsError.noTempOverrideFound` if no presets are found.
+     */
     func fetchIDs(_ uuid: [OverridePreset.ID]) async throws -> [OverridePreset] {
     func fetchIDs(_ uuid: [OverridePreset.ID]) async throws -> [OverridePreset] {
         try await coredataContext.perform {
         try await coredataContext.perform {
             let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
             let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
@@ -64,6 +79,13 @@ import UIKit
         }
         }
     }
     }
 
 
+    /**
+     Fetches the Core Data `NSManagedObjectID` for a given `OverridePreset`.
+
+     - Parameter preset: The `OverridePreset` for which to fetch the object ID.
+     - Returns: The corresponding `NSManagedObjectID`.
+     - Throws: `overridePresetsError.noTempOverrideFound` if the preset is not found.
+     */
     private func fetchOverrideID(_ preset: OverridePreset) async throws -> NSManagedObjectID {
     private func fetchOverrideID(_ preset: OverridePreset) async throws -> NSManagedObjectID {
         let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
         let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
@@ -81,103 +103,96 @@ import UIKit
         }
         }
     }
     }
 
 
+    /**
+     Enacts an override preset by enabling it in Core Data and notifying the system.
+
+     - Parameter preset: The `OverridePreset` to enact.
+     - Returns: A boolean indicating whether the override was successfully enacted.
+     */
     @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
     @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
-        // Start background task
+        debug(.default, "Enacting override: \(preset.name)")
+        intentSuccess = false
+
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
-            guard backgroundTaskID != .invalid else { return }
-            Task {
-                UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            }
-            backgroundTaskID = .invalid
-        }
+        backgroundTaskID = startBackgroundTask(withName: "Override Enact")
 
 
-        defer {
-            if backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
+        await disableAllActiveOverrides(shouldStartBackgroundTask: false)
 
 
         do {
         do {
-            // Get NSManagedObjectID of Preset
             let overrideID = try await fetchOverrideID(preset)
             let overrideID = try await fetchOverrideID(preset)
             guard let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else {
             guard let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else {
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
                 throw overridePresetsError.noTempOverrideFound
                 throw overridePresetsError.noTempOverrideFound
             }
             }
 
 
-            // Enable Override
             overrideObject.enabled = true
             overrideObject.enabled = true
             overrideObject.date = Date()
             overrideObject.date = Date()
             overrideObject.isUploadedToNS = false
             overrideObject.isUploadedToNS = false
 
 
-            // Disable previous overrides if necessary
-            await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
-
             if viewContext.hasChanges {
             if viewContext.hasChanges {
+                debug(.default, "Saving changes...")
                 try viewContext.save()
                 try viewContext.save()
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
                 await awaitNotification(.didUpdateOverrideConfiguration)
                 await awaitNotification(.didUpdateOverrideConfiguration)
-                return true
+                debug(.default, "Notification received, continuing...")
+                intentSuccess = true
             }
             }
-            return false
+
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
+            debug(.default, "Finished. Override enacted via Shortcut.")
+            return intentSuccess
         } catch {
         } catch {
             debug(
             debug(
                 .default,
                 .default,
                 "\(DebuggingIdentifiers.failed) Failed to enact override: \(error.localizedDescription)"
                 "\(DebuggingIdentifiers.failed) Failed to enact override: \(error.localizedDescription)"
             )
             )
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
             return false
             return false
         }
         }
     }
     }
 
 
+    /**
+     Cancels all active overrides asynchronously.
+     */
     func cancelOverride() async {
     func cancelOverride() async {
-        await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true)
+        await disableAllActiveOverrides(shouldStartBackgroundTask: true)
     }
     }
 
 
-    @MainActor func disableAllActiveOverrides(
-        except overrideID: NSManagedObjectID? = nil,
-        createOverrideRunEntry: Bool,
-        shouldStartBackgroundTask: Bool = true
-    ) async {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+    /**
+     Disables all active overrides and optionally starts a background task.
 
 
-        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
-            }
-        }
+     - Parameter shouldStartBackgroundTask: A boolean indicating whether to start a background task.
+     */
+    @MainActor func disableAllActiveOverrides(shouldStartBackgroundTask: Bool) async {
+        debug(.default, "Disabling all active overrides")
+        var backgroundTaskID: UIBackgroundTaskIdentifier?
 
 
-        // 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
-            }
+        if shouldStartBackgroundTask {
+            debug(.default, "Starting background task for override cancel")
+            backgroundTaskID = .invalid
+            backgroundTaskID = startBackgroundTask(withName: "Override Cancel")
         }
         }
 
 
         do {
         do {
             // Get NSManagedObjectID of all active overrides
             // Get NSManagedObjectID of all active overrides
-            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
-            // Fetch existing OverrideStored objects
+            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0)
             let results = try ids.compactMap { id in
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? OverrideStored
                 try self.viewContext.existingObject(with: id) as? OverrideStored
             }
             }
 
 
             // Return early if no results
             // Return early if no results
-            guard !results.isEmpty else { return }
+            guard !results.isEmpty else {
+                debug(.default, "No active overrides to cancel… returning early")
+                if var backgroundTaskID = backgroundTaskID {
+                    debug(.default, "Ending background task for override cancel")
+                    endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+                }
+                return
+            }
 
 
             // Create OverrideRunStored entry if needed
             // Create OverrideRunStored entry if needed
-            if createOverrideRunEntry, let canceledOverride = results.first {
+            if let canceledOverride = results.first {
                 let newOverrideRunStored = OverrideRunStored(context: viewContext)
                 let newOverrideRunStored = OverrideRunStored(context: viewContext)
                 newOverrideRunStored.id = UUID()
                 newOverrideRunStored.id = UUID()
                 newOverrideRunStored.name = canceledOverride.name
                 newOverrideRunStored.name = canceledOverride.name
@@ -190,30 +205,38 @@ import UIKit
                 newOverrideRunStored.isUploadedToNS = false
                 newOverrideRunStored.isUploadedToNS = false
             }
             }
 
 
-            // Disable all overrides except the one specified
+            // Disable all active overrides
             for overrideToCancel in results {
             for overrideToCancel in results {
-                if overrideToCancel.objectID != overrideID {
-                    overrideToCancel.enabled = false
-                    overrideToCancel.isUploadedToNS = false
-                }
+                let endTime = overrideToCancel.date?
+                    .addingTimeInterval(TimeInterval(truncating: overrideToCancel.duration ?? 0))
+
+                debugPrint(
+                    "Disabling override: \(overrideToCancel.name ?? "Unnamed") with end time: \(endTime?.description ?? "Unknown")"
+                )
+                overrideToCancel.enabled = false
+                overrideToCancel.isUploadedToNS = false
             }
             }
 
 
             if viewContext.hasChanges {
             if viewContext.hasChanges {
                 try viewContext.save()
                 try viewContext.save()
-
-                // Update State variables in OverrideView
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
+                await awaitNotification(.didUpdateOverrideConfiguration)
+                debug(.default, "Notification received, continuing...")
             }
             }
 
 
-            // Await the notification
-            print("Waiting for notification...")
-            await awaitNotification(.didUpdateOverrideConfiguration)
-            print("Notification received, continuing...")
-
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for override cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+            }
         } catch {
         } catch {
             debugPrint(
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
             )
             )
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for override cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+            }
         }
         }
     }
     }
 }
 }

+ 22 - 4
Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -1,21 +1,25 @@
 import AppIntents
 import AppIntents
 import Foundation
 import Foundation
 
 
+/// An App Intent that allows users to apply a temporary target preset through the Shortcuts app.
 struct ApplyTempPresetIntent: AppIntent {
 struct ApplyTempPresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title: LocalizedStringResource = "Apply a Temporary Target"
     static var title: LocalizedStringResource = "Apply a Temporary Target"
 
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription("Enable a Temporary Target")
     static var description = IntentDescription("Enable a Temporary Target")
 
 
+    /// The temporary target preset to be applied.
     @Parameter(title: "Preset") var preset: TempPreset?
     @Parameter(title: "Preset") var preset: TempPreset?
 
 
+    /// A boolean parameter that determines whether confirmation is required before applying the temporary target.
     @Parameter(
     @Parameter(
         title: "Confirm Before applying",
         title: "Confirm Before applying",
         description: "If toggled, you will need to confirm before applying",
         description: "If toggled, you will need to confirm before applying",
         default: true
         default: true
     ) var confirmBeforeApplying: Bool
     ) var confirmBeforeApplying: Bool
 
 
+    /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     static var parameterSummary: some ParameterSummary {
     static var parameterSummary: some ParameterSummary {
         When(\ApplyTempPresetIntent.$confirmBeforeApplying, .equalTo, true, {
         When(\ApplyTempPresetIntent.$confirmBeforeApplying, .equalTo, true, {
             Summary("Applying \(\.$preset)") {
             Summary("Applying \(\.$preset)") {
@@ -28,23 +32,34 @@ struct ApplyTempPresetIntent: AppIntent {
         })
         })
     }
     }
 
 
+    /// Converts a decimal duration value into a formatted time string.
+    ///
+    /// - Parameter decimal: The duration value in decimal format.
+    /// - Returns: A string representing the formatted time in hours and minutes.
     private func decimalToTimeFormattedString(decimal: Decimal) -> String {
     private func decimalToTimeFormattedString(decimal: Decimal) -> String {
-        let timeInterval = TimeInterval(decimal * 60) // seconds
+        let timeInterval = TimeInterval(decimal * 60) // Convert minutes to seconds
 
 
         let formatter = DateComponentsFormatter()
         let formatter = DateComponentsFormatter()
         formatter.allowedUnits = [.hour, .minute]
         formatter.allowedUnits = [.hour, .minute]
-        formatter.unitsStyle = .brief // example: 1h 10 min
+        formatter.unitsStyle = .brief // Example: "1h 10m"
 
 
         return formatter.string(from: timeInterval) ?? ""
         return formatter.string(from: timeInterval) ?? ""
     }
     }
 
 
+    /// Executes the intent to apply the selected temporary target preset.
+    ///
+    /// - Returns: A dialog indicating whether the temporary target was successfully applied or failed.
+    /// - Throws: An error if an issue occurs during execution.
     @MainActor func perform() async throws -> some ProvidesDialog {
     @MainActor func perform() async throws -> some ProvidesDialog {
         do {
         do {
             let intentRequest = TempPresetsIntentRequest()
             let intentRequest = TempPresetsIntentRequest()
             let presetToApply: TempPreset
             let presetToApply: TempPreset
+
+            // Determine which preset to apply
             if let preset = preset {
             if let preset = preset {
                 presetToApply = preset
                 presetToApply = preset
             } else {
             } else {
+                // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                 presetToApply = try await $preset.requestDisambiguation(
                     among: intentRequest.fetchAndProcessTempTargets(),
                     among: intentRequest.fetchAndProcessTempTargets(),
                     dialog: "Select Temporary Target"
                     dialog: "Select Temporary Target"
@@ -52,12 +67,15 @@ struct ApplyTempPresetIntent: AppIntent {
             }
             }
 
 
             let displayName: String = presetToApply.name
             let displayName: String = presetToApply.name
+
+            // Request confirmation before applying if required
             if confirmBeforeApplying {
             if confirmBeforeApplying {
                 try await requestConfirmation(
                 try await requestConfirmation(
                     result: .result(dialog: "Confirm to apply Temporary Target '\(displayName)'")
                     result: .result(dialog: "Confirm to apply Temporary Target '\(displayName)'")
                 )
                 )
             }
             }
 
 
+            // Apply the temporary target and return the appropriate dialog message
             if await intentRequest.enactTempTarget(presetToApply) {
             if await intentRequest.enactTempTarget(presetToApply) {
                 return .result(
                 return .result(
                     dialog: IntentDialog(
                     dialog: IntentDialog(

+ 7 - 2
Trio/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift

@@ -1,13 +1,18 @@
 import AppIntents
 import AppIntents
 import Foundation
 import Foundation
 
 
+/// An App Intent that allows users to cancel an active temporary target through the Shortcuts app.
 struct CancelTempPresetIntent: AppIntent {
 struct CancelTempPresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title: LocalizedStringResource = "Cancel a Temporary Target"
     static var title: LocalizedStringResource = "Cancel a Temporary Target"
 
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription("Cancel Temporary Target.")
     static var description = IntentDescription("Cancel Temporary Target.")
 
 
+    /// Performs the intent action to cancel an active temporary target.
+    ///
+    /// - Returns: A confirmation dialog indicating that the temporary target has been canceled.
+    /// - Throws: An error if the cancellation process fails.
     @MainActor func perform() async throws -> some ProvidesDialog {
     @MainActor func perform() async throws -> some ProvidesDialog {
         await TempPresetsIntentRequest().cancelTempTarget()
         await TempPresetsIntentRequest().cancelTempTarget()
         return .result(
         return .result(

+ 23 - 0
Trio/Sources/Shortcuts/TempPresets/TempPresetIntent.swift

@@ -3,27 +3,50 @@ import Foundation
 import Intents
 import Intents
 import Swinject
 import Swinject
 
 
+/// Represents a temporary target preset that can be used in the app.
 struct TempPreset: AppEntity, Identifiable {
 struct TempPreset: AppEntity, Identifiable {
+    /// Default query instance for fetching temporary presets.
     static var defaultQuery = TempPresetsQuery()
     static var defaultQuery = TempPresetsQuery()
 
 
+    /// Unique identifier for the temporary preset.
     var id: UUID
     var id: UUID
+
+    /// Name of the temporary preset.
     var name: String
     var name: String
+
+    /// The upper target value for the preset, if applicable.
     var targetTop: Decimal?
     var targetTop: Decimal?
+
+    /// The lower target value for the preset, if applicable.
     var targetBottom: Decimal?
     var targetBottom: Decimal?
+
+    /// The duration of the temporary preset in minutes.
     var duration: Decimal
     var duration: Decimal
 
 
+    /// Provides a display representation for the temporary preset.
     var displayRepresentation: DisplayRepresentation {
     var displayRepresentation: DisplayRepresentation {
         DisplayRepresentation(title: "\(name)")
         DisplayRepresentation(title: "\(name)")
     }
     }
 
 
+    /// Representation for the entity type when displayed in UI.
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Presets"
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Presets"
 }
 }
 
 
+/// Query structure for fetching temporary target presets in an App Intent.
 struct TempPresetsQuery: EntityQuery {
 struct TempPresetsQuery: EntityQuery {
+    /// Fetches a list of temporary target presets matching the given identifiers.
+    ///
+    /// - Parameter identifiers: A list of preset IDs to fetch.
+    /// - Returns: An array of `TempPreset` objects matching the given IDs.
+    /// - Throws: An error if the fetch operation fails.
     func entities(for identifiers: [TempPreset.ID]) async throws -> [TempPreset] {
     func entities(for identifiers: [TempPreset.ID]) async throws -> [TempPreset] {
         await TempPresetsIntentRequest().fetchIDs(identifiers)
         await TempPresetsIntentRequest().fetchIDs(identifiers)
     }
     }
 
 
+    /// Fetches a list of suggested temporary target presets.
+    ///
+    /// - Returns: An array of available `TempPreset` objects.
+    /// - Throws: An error if fetching fails.
     func suggestedEntities() async throws -> [TempPreset] {
     func suggestedEntities() async throws -> [TempPreset] {
         try await TempPresetsIntentRequest().fetchAndProcessTempTargets()
         try await TempPresetsIntentRequest().fetchAndProcessTempTargets()
     }
     }

+ 94 - 92
Trio/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -2,12 +2,21 @@ import CoreData
 import Foundation
 import Foundation
 import UIKit
 import UIKit
 
 
+/// Handles intent requests related to temporary presets, such as fetching, enacting, and canceling temp targets.
 final class TempPresetsIntentRequest: BaseIntentsRequest {
 final class TempPresetsIntentRequest: BaseIntentsRequest {
+    /// Enum representing possible errors related to temporary presets.
     enum TempPresetsError: Error {
     enum TempPresetsError: Error {
         case noTempTargetFound
         case noTempTargetFound
         case noDurationDefined
         case noDurationDefined
     }
     }
 
 
+    /// Tracks whether the intent execution was successful.
+    private var intentSuccess: Bool = false
+
+    /// Fetches and processes all available temporary target presets.
+    ///
+    /// - Returns: An array of `TempPreset` objects.
+    /// - Throws: An error if fetching or processing fails.
     func fetchAndProcessTempTargets() async throws -> [TempPreset] {
     func fetchAndProcessTempTargets() async throws -> [TempPreset] {
         // Fetch all Temp Target Presets via TempTargetStorage
         // Fetch all Temp Target Presets via TempTargetStorage
         let allTempTargetPresetsIDs = try await tempTargetsStorage.fetchForTempTargetPresets()
         let allTempTargetPresetsIDs = try await tempTargetsStorage.fetchForTempTargetPresets()
@@ -38,6 +47,10 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
         }
     }
     }
 
 
+    /// Fetches temporary target presets based on the given identifiers.
+    ///
+    /// - Parameter uuid: An array of preset IDs to fetch.
+    /// - Returns: An array of `TempPreset` objects.
     func fetchIDs(_ uuid: [TempPreset.ID]) async -> [TempPreset] {
     func fetchIDs(_ uuid: [TempPreset.ID]) async -> [TempPreset] {
         await coredataContext.perform {
         await coredataContext.perform {
             let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
             let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
@@ -67,6 +80,10 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
         }
     }
     }
 
 
+    /// Fetches the `NSManagedObjectID` for a given `TempPreset`.
+    ///
+    /// - Parameter preset: The `TempPreset` to find.
+    /// - Returns: The `NSManagedObjectID` of the temp target if found, otherwise `nil`.
     private func fetchTempTargetID(_ preset: TempPreset) async -> NSManagedObjectID? {
     private func fetchTempTargetID(_ preset: TempPreset) async -> NSManagedObjectID? {
         let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
         let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id.uuidString)
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id.uuidString)
@@ -84,55 +101,43 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
         }
     }
     }
 
 
+    /// Enacts a temporary target preset by updating Core Data and notifying relevant components.
+    ///
+    /// - Parameter preset: The `TempPreset` to apply.
+    /// - Returns: `true` if successfully enacted, otherwise `false`.
     @MainActor func enactTempTarget(_ preset: TempPreset) async -> Bool {
     @MainActor func enactTempTarget(_ preset: TempPreset) async -> Bool {
+        debug(.default, "Enacting Temp Target: \(preset.name)")
+        intentSuccess = false
+
         // Start background task
         // Start background task
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget Upload") {
-            guard backgroundTaskID != .invalid else { return }
-            Task {
-                // End background task when the time is about to expire
-                UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            }
-            backgroundTaskID = .invalid
-        }
+        backgroundTaskID = startBackgroundTask(withName: "TempTarget Enact")
 
 
-        // Defer block to end background task when function exits
-        defer {
-            if backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
+        // Disable previous temp targets if necessary, without starting a background task
+        await disableAllActiveTempTargets(shouldStartBackgroundTask: false)
 
 
         do {
         do {
             // Get NSManagedObjectID of Preset
             // Get NSManagedObjectID of Preset
             guard let tempTargetID = await fetchTempTargetID(preset),
             guard let tempTargetID = await fetchTempTargetID(preset),
                   let tempTargetObject = try viewContext.existingObject(with: tempTargetID) as? TempTargetStored
                   let tempTargetObject = try viewContext.existingObject(with: tempTargetID) as? TempTargetStored
-            else { return false }
+            else {
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+                throw TempPresetsError.noTempTargetFound
+            }
 
 
             // Enable TempTarget
             // Enable TempTarget
             tempTargetObject.enabled = true
             tempTargetObject.enabled = true
             tempTargetObject.date = Date()
             tempTargetObject.date = Date()
             tempTargetObject.isUploadedToNS = false
             tempTargetObject.isUploadedToNS = false
 
 
-            // Disable previous overrides if necessary, without starting a background task
-            await disableAllActiveTempTargets(
-                except: tempTargetID,
-                createTempTargetRunEntry: true,
-                shouldStartBackgroundTask: false
-            )
-
             if viewContext.hasChanges {
             if viewContext.hasChanges {
+                debug(.default, "Saving changes...")
                 try viewContext.save()
                 try viewContext.save()
-
+                debug(.default, "Waiting for notification...")
                 // Update State variables in TempTargetView
                 // Update State variables in TempTargetView
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
 
 
-                // Await the notification
-                print("Waiting for notification...")
-
+                // Prepare JSON for oref
                 guard let tempTargetDate = tempTargetObject.date, let tempTarget = tempTargetObject.target,
                 guard let tempTargetDate = tempTargetObject.date, let tempTarget = tempTargetObject.target,
                       let tempTargetDuration = tempTargetObject.duration else { return false }
                       let tempTargetDuration = tempTargetObject.duration else { return false }
 
 
@@ -148,108 +153,105 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
                     enabled: tempTargetObject.enabled,
                     enabled: tempTargetObject.enabled,
                     halfBasalTarget: tempTargetObject.halfBasalTarget as Decimal?
                     halfBasalTarget: tempTargetObject.halfBasalTarget as Decimal?
                 )
                 )
-
                 // Save the temp targets to JSON so that they get used by oref
                 // Save the temp targets to JSON so that they get used by oref
                 tempTargetsStorage.saveTempTargetsToStorage([tempTargetToStoreAsJSON])
                 tempTargetsStorage.saveTempTargetsToStorage([tempTargetToStoreAsJSON])
 
 
                 await awaitNotification(.didUpdateTempTargetConfiguration)
                 await awaitNotification(.didUpdateTempTargetConfiguration)
-                print("Notification received, continuing...")
 
 
-                return true
+                debug(.default, "Notification received, continuing...")
+                intentSuccess = true
             }
             }
+
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+
+            debug(.default, "Finished. Temp Target enacted via Shortcut.")
+
+            return intentSuccess
         } catch {
         } catch {
-            // Handle error and ensure background task is ended
-            debugPrint("Failed to enact TempTarget: \(error.localizedDescription)")
-        }
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Temp Target with error: \(error.localizedDescription)"
+            )
 
 
-        return false
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+
+            intentSuccess = false
+            return intentSuccess
+        }
     }
     }
 
 
+    /// Cancels an active temporary target.
     func cancelTempTarget() async {
     func cancelTempTarget() async {
-        await disableAllActiveTempTargets(createTempTargetRunEntry: true, shouldStartBackgroundTask: true)
+        await disableAllActiveTempTargets(shouldStartBackgroundTask: true)
         tempTargetsStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
         tempTargetsStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
     }
     }
 
 
-    @MainActor func disableAllActiveTempTargets(
-        except tempTargetID: NSManagedObjectID? = nil,
-        createTempTargetRunEntry: Bool,
-        shouldStartBackgroundTask: Bool = true
-    ) async {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+    /// Disables all active temporary targets.
+    ///
+    /// - Parameter shouldStartBackgroundTask: A flag indicating whether a background task should be started.
+    @MainActor func disableAllActiveTempTargets(shouldStartBackgroundTask: Bool) async {
+        var backgroundTaskID: UIBackgroundTaskIdentifier?
 
 
         if shouldStartBackgroundTask {
         if shouldStartBackgroundTask {
-            // Start background task
-            backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget 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
-            }
+            debug(.default, "Starting background task for temp target cancel")
+            backgroundTaskID = .invalid
+            backgroundTaskID = startBackgroundTask(withName: "TempTarget Cancel")
         }
         }
 
 
         do {
         do {
-            // Get NSManagedObjectID of all active temp Targets
+            // Fetch active temp targets
             let ids = try await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
             let ids = try await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
-            // Fetch existing OverrideStored objects
             let results = try ids.compactMap { id in
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? TempTargetStored
                 try self.viewContext.existingObject(with: id) as? TempTargetStored
             }
             }
 
 
-            // Return early if no results
-            guard !results.isEmpty else { return }
-
-            // Create TempTargetRunStored entry if needed
-            if createTempTargetRunEntry {
-                // Use the first temp target to create a new TempTargetRunStored entry
-                if let canceledTempTarget = results.first {
-                    let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
-                    newTempTargetRunStored.id = UUID()
-                    newTempTargetRunStored.name = canceledTempTarget.name
-                    newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
-                    newTempTargetRunStored.endDate = Date()
-                    newTempTargetRunStored
-                        .target = canceledTempTarget.target ?? 0
-                    newTempTargetRunStored.tempTarget = canceledTempTarget
-                    newTempTargetRunStored.isUploadedToNS = false
+            guard !results.isEmpty else {
+                debug(.default, "No active temp targets to cancel... returning early")
+
+                if var backgroundTaskID = backgroundTaskID {
+                    debug(.default, "Ending background task for temp target cancel")
+                    endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
                 }
                 }
+                return
+            }
+
+            // Create a new `TempTargetRunStored` entry
+            if let canceledTempTarget = results.first {
+                let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
+                newTempTargetRunStored.id = UUID()
+                newTempTargetRunStored.name = canceledTempTarget.name
+                newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
+                newTempTargetRunStored.endDate = Date()
+                newTempTargetRunStored.target = canceledTempTarget.target ?? 0
+                newTempTargetRunStored.tempTarget = canceledTempTarget
+                newTempTargetRunStored.isUploadedToNS = false
             }
             }
 
 
-            // Disable all override except the one with overrideID
+            // Disable all temp targets
             for tempTargetToCancel in results {
             for tempTargetToCancel in results {
-                if tempTargetToCancel.objectID != tempTargetID {
-                    tempTargetToCancel.enabled = false
-                    tempTargetToCancel.isUploadedToNS = false
-                }
+                tempTargetToCancel.enabled = false
+                tempTargetToCancel.isUploadedToNS = false
             }
             }
 
 
             if viewContext.hasChanges {
             if viewContext.hasChanges {
                 try viewContext.save()
                 try viewContext.save()
-
-                // Update State variables in OverrideView
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
+                await awaitNotification(.didUpdateTempTargetConfiguration)
+                debug(.default, "Notification received, continuing...")
             }
             }
 
 
-            // Await the notification
-            print("Waiting for notification...")
-            await awaitNotification(.didUpdateTempTargetConfiguration)
-            print("Notification received, continuing...")
-
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for temp target cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
+            }
         } catch {
         } catch {
             debugPrint(
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Temp Targets with error: \(error.localizedDescription)"
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Temp Targets with error: \(error.localizedDescription)"
             )
             )
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for temp target cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
+            }
         }
         }
     }
     }
 }
 }