diff --git a/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift b/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift index 7ef2b852..bc8f65a9 100644 --- a/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift +++ b/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift @@ -32,6 +32,7 @@ final class GopeedContinuedProcessingManager: NSObject { private var expiringTasks: [String: ExpiringTaskState] = [:] private var taskGenerations: [String: UUID] = [:] + private var progressRecoveryTokens: [String: UUID] = [:] private var activeTasks: [String: BGContinuedProcessingTask] = [:] @@ -224,6 +225,10 @@ final class GopeedContinuedProcessingManager: NSObject { switch type { case "task.start": + progressRecoveryTokens.removeValue( + forKey: taskID + ) + beginTask( taskID: taskID, name: name @@ -237,6 +242,11 @@ final class GopeedContinuedProcessingManager: NSObject { generation: generation, force: false ) + } else { + recoverTaskFromProgress( + taskID: taskID, + name: name + ) } case "task.pause": @@ -285,6 +295,72 @@ final class GopeedContinuedProcessingManager: NSObject { } + // MARK: - Recover active task after setting changes + + private func recoverTaskFromProgress( + taskID: String, + name: String + ) { + guard + currentEnabledSnapshot(), + taskGenerations[taskID] == nil, + !pendingTaskIDs.contains(taskID), + activeTasks[taskID] == nil, + expiringTasks[taskID] == nil, + progressRecoveryTokens[taskID] == nil + else { + return + } + + let recoveryToken = UUID() + + progressRecoveryTokens[taskID] = + recoveryToken + + getRuntimeStatus( + taskID: taskID + ) { [weak self] runtime in + guard + let self, + self.progressRecoveryTokens[taskID] + == recoveryToken + else { + return + } + + // The status request has completed. Remove the token so + // another progress event may retry if recovery is still + // necessary. + self.progressRecoveryTokens.removeValue( + forKey: taskID + ) + + guard + self.currentEnabledSnapshot(), + let runtime, + runtime.status == "running", + self.taskGenerations[taskID] == nil, + !self.pendingTaskIDs.contains(taskID), + self.activeTasks[taskID] == nil, + self.expiringTasks[taskID] == nil + else { + return + } + + print( + "ContinuedProcessing:", + "recovering active task:", + taskID + ) + + self.beginTask( + taskID: taskID, + name: name + ) + } + } + + // MARK: - Used by Objective-C forwarder @objc(isHandlingTaskId:) @@ -825,6 +901,10 @@ final class GopeedContinuedProcessingManager: NSObject { success: Bool, finalSubtitle: String ) { + progressRecoveryTokens.removeValue( + forKey: taskID + ) + taskGenerations.removeValue( forKey: taskID ) @@ -1063,6 +1143,7 @@ final class GopeedContinuedProcessingManager: NSObject { pendingTaskIDs.removeAll() expiringTasks.removeAll() taskGenerations.removeAll() + progressRecoveryTokens.removeAll() taskIdentifiers.removeAll() taskNames.removeAll() lastProgressUpdate.removeAll() diff --git a/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift b/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift index 84f965ba..6889dec9 100644 --- a/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift +++ b/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift @@ -108,6 +108,39 @@ final class GopeedLiveActivityManager: NSObject { return generation } + private func generationForProgress( + taskID: String + ) -> UUID? { + stateLock.lock() + defer { stateLock.unlock() } + + guard + !suppressedTaskIDs.contains(taskID) + else { + return nil + } + + if let generation = + taskGenerations[taskID] { + return generation + } + + // Progress may be the first event after Continued Processing + // releases ownership. Establish a fresh lifecycle generation. + // refreshActivity still verifies that Gopeed reports the task + // as running before it can create a Live Activity. + let generation = UUID() + + taskGenerations[taskID] = + generation + + lastUpdateTime.removeValue( + forKey: taskID + ) + + return generation + } + @discardableResult private func invalidateGeneration( taskID: String @@ -252,7 +285,7 @@ final class GopeedLiveActivityManager: NSObject { case "task.progress": guard let generation = - currentGeneration( + generationForProgress( taskID: taskID ) else {