mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 08:10:55 +00:00
refactor(ios): clean up continued processing task handling (#1547)
This commit is contained in:
1 parent
363153fa33
commit
8d1ae93b03
4 files changed
+172
-67
No files matched your search
@@ -18,6 +18,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
3EF29839EA13551EB8144269 /* GopeedLiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 76ED41E0163DFA97F018D1AC /* GopeedLiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
5BF69191A426106A38B62F9F /* GopeedContinuedProcessingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */; };
|
||||
B30000000000000000000001 /* GopeedTaskEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B30000000000000000000002 /* GopeedTaskEvent.swift */; };
|
||||
6085D504396791024CEC496B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8823E31DA135C410E362683E /* Pods_RunnerTests.framework */; };
|
||||
6243BE8F995F5284DBDD7DE3 /* GopeedLiveActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C9F8DE4B991051758F2BE1 /* GopeedLiveActivityWidget.swift */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@@ -102,6 +103,7 @@
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
35C9F8DE4B991051758F2BE1 /* GopeedLiveActivityWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedLiveActivityWidget.swift; sourceTree = "<group>"; };
|
||||
3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedContinuedProcessingManager.swift; sourceTree = "<group>"; };
|
||||
B30000000000000000000002 /* GopeedTaskEvent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GopeedTaskEvent.swift; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
52A13633B52EDC9D3F479DFD /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
55356FCC7E573C3FEEC2DF4D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -286,6 +288,7 @@
|
||||
09AC32C4BF90EAC54D048584 /* GopeedDownloadAttributes.swift */,
|
||||
EB8C142A455C80367DDD68AE /* GopeedLiveActivityManager.swift */,
|
||||
3879B6DB5398A19281BA5F8F /* GopeedContinuedProcessingManager.swift */,
|
||||
B30000000000000000000002 /* GopeedTaskEvent.swift */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -607,6 +610,7 @@
|
||||
1E29080079AAFD1CF2C71262 /* GopeedDownloadAttributes.swift in Sources */,
|
||||
032447D610CFAF8A75642212 /* GopeedLiveActivityManager.swift in Sources */,
|
||||
5BF69191A426106A38B62F9F /* GopeedContinuedProcessingManager.swift in Sources */,
|
||||
B30000000000000000000001 /* GopeedTaskEvent.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -22,8 +22,8 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
|
||||
// MARK: - BGCPT state
|
||||
|
||||
private var registeredIdentifiers = Set<String>()
|
||||
private var pendingTaskIDs = Set<String>()
|
||||
private var foregroundStartTokens: [String: UUID] = [:]
|
||||
private struct ExpiringTaskState {
|
||||
let identifier: String
|
||||
let generation: UUID
|
||||
@@ -167,16 +167,11 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
// Pre-mark task.start so the Objective-C forwarder can
|
||||
// immediately suppress the custom ActivityKit Live Activity.
|
||||
if
|
||||
let data = payload.data(using: .utf8),
|
||||
let json = try? JSONSerialization.jsonObject(
|
||||
with: data
|
||||
) as? [String: Any],
|
||||
let type = json["type"] as? String,
|
||||
type == "task.start",
|
||||
let taskID = json["taskId"] as? String
|
||||
let event = GopeedTaskEvent.decode(payload),
|
||||
event.type == .start
|
||||
{
|
||||
setTaskOwnership(
|
||||
taskID: taskID,
|
||||
taskID: event.taskID,
|
||||
handled: true
|
||||
)
|
||||
}
|
||||
@@ -198,33 +193,19 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
guard
|
||||
let data = payload.data(
|
||||
using: .utf8
|
||||
),
|
||||
let json =
|
||||
try? JSONSerialization
|
||||
.jsonObject(
|
||||
with: data
|
||||
) as? [String: Any],
|
||||
let type =
|
||||
json["type"] as? String,
|
||||
let taskID =
|
||||
json["taskId"] as? String
|
||||
else {
|
||||
guard let event = GopeedTaskEvent.decode(payload) else {
|
||||
print(
|
||||
"ContinuedProcessing: invalid event"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let name =
|
||||
json["name"] as? String
|
||||
?? "Download"
|
||||
let taskID = event.taskID
|
||||
let name = event.name
|
||||
|
||||
switch type {
|
||||
switch event.type {
|
||||
|
||||
case "task.start":
|
||||
case .start:
|
||||
progressRecoveryTokens.removeValue(
|
||||
forKey: taskID
|
||||
)
|
||||
@@ -234,7 +215,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
name: name
|
||||
)
|
||||
|
||||
case "task.progress":
|
||||
case .progress:
|
||||
if let generation =
|
||||
taskGenerations[taskID] {
|
||||
updateProgress(
|
||||
@@ -249,7 +230,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
)
|
||||
}
|
||||
|
||||
case "task.pause":
|
||||
case .pause:
|
||||
if let expiring =
|
||||
expiringTasks[taskID] {
|
||||
finishExpirationCleanup(
|
||||
@@ -265,7 +246,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
)
|
||||
}
|
||||
|
||||
case "task.done":
|
||||
case .done:
|
||||
finishTask(
|
||||
taskID: taskID,
|
||||
success: true,
|
||||
@@ -273,7 +254,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
"Download complete"
|
||||
)
|
||||
|
||||
case "task.error":
|
||||
case .error:
|
||||
finishTask(
|
||||
taskID: taskID,
|
||||
success: false,
|
||||
@@ -281,7 +262,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
"Download failed"
|
||||
)
|
||||
|
||||
case "task.delete":
|
||||
case .delete:
|
||||
finishTask(
|
||||
taskID: taskID,
|
||||
success: true,
|
||||
@@ -289,12 +270,11 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
"Download removed"
|
||||
)
|
||||
|
||||
default:
|
||||
case .other:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Recover active task after setting changes
|
||||
|
||||
private func recoverTaskFromProgress(
|
||||
@@ -307,6 +287,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
!pendingTaskIDs.contains(taskID),
|
||||
activeTasks[taskID] == nil,
|
||||
expiringTasks[taskID] == nil,
|
||||
foregroundStartTokens[taskID] == nil,
|
||||
progressRecoveryTokens[taskID] == nil
|
||||
else {
|
||||
return
|
||||
@@ -395,7 +376,8 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
|
||||
guard
|
||||
!pendingTaskIDs.contains(taskID),
|
||||
activeTasks[taskID] == nil
|
||||
activeTasks[taskID] == nil,
|
||||
foregroundStartTokens[taskID] == nil
|
||||
else {
|
||||
setTaskOwnership(
|
||||
taskID: taskID,
|
||||
@@ -404,20 +386,57 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
// BGCPT should originate from a foreground user action.
|
||||
// This main-thread check happens only once per task start.
|
||||
let appIsActive: Bool
|
||||
// BGCPT must originate from a foreground user action.
|
||||
// Never synchronously hop from workerQueue to the main queue:
|
||||
// doing so can deadlock if the main thread is waiting on work
|
||||
// that eventually needs workerQueue.
|
||||
let foregroundToken = UUID()
|
||||
foregroundStartTokens[taskID] =
|
||||
foregroundToken
|
||||
|
||||
if Thread.isMainThread {
|
||||
appIsActive =
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
let appIsActive =
|
||||
UIApplication.shared
|
||||
.applicationState == .active
|
||||
} else {
|
||||
appIsActive =
|
||||
DispatchQueue.main.sync {
|
||||
UIApplication.shared
|
||||
.applicationState == .active
|
||||
}
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.workerQueue.async {
|
||||
self.continueBeginTaskAfterForegroundCheck(
|
||||
taskID: taskID,
|
||||
name: name,
|
||||
foregroundToken: foregroundToken,
|
||||
appIsActive: appIsActive
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func continueBeginTaskAfterForegroundCheck(
|
||||
taskID: String,
|
||||
name: String,
|
||||
foregroundToken: UUID,
|
||||
appIsActive: Bool
|
||||
) {
|
||||
guard
|
||||
foregroundStartTokens[taskID]
|
||||
== foregroundToken
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
foregroundStartTokens.removeValue(
|
||||
forKey: taskID
|
||||
)
|
||||
|
||||
guard currentEnabledSnapshot() else {
|
||||
setTaskOwnership(
|
||||
taskID: taskID,
|
||||
handled: false
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
guard appIsActive else {
|
||||
@@ -434,6 +453,20 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
// Re-check after the asynchronous main-thread hop. A terminal
|
||||
// event, disable, or another valid start may have changed the
|
||||
// lifecycle while the foreground state was being read.
|
||||
guard
|
||||
!pendingTaskIDs.contains(taskID),
|
||||
activeTasks[taskID] == nil
|
||||
else {
|
||||
setTaskOwnership(
|
||||
taskID: taskID,
|
||||
handled: true
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let generation = UUID()
|
||||
taskGenerations[taskID] = generation
|
||||
|
||||
@@ -498,8 +531,6 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
registeredIdentifiers.insert(identifier)
|
||||
|
||||
let request =
|
||||
BGContinuedProcessingTaskRequest(
|
||||
identifier: identifier,
|
||||
@@ -558,7 +589,6 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - System launched task
|
||||
|
||||
private func activateTask(
|
||||
@@ -901,6 +931,10 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
success: Bool,
|
||||
finalSubtitle: String
|
||||
) {
|
||||
foregroundStartTokens.removeValue(
|
||||
forKey: taskID
|
||||
)
|
||||
|
||||
progressRecoveryTokens.removeValue(
|
||||
forKey: taskID
|
||||
)
|
||||
@@ -1115,6 +1149,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
Set(
|
||||
handledTaskIDsSnapshot()
|
||||
+ Array(taskIdentifiers.keys)
|
||||
+ Array(foregroundStartTokens.keys)
|
||||
+ Array(pendingTaskIDs)
|
||||
+ Array(activeTasks.keys)
|
||||
+ Array(expiringTasks.keys)
|
||||
@@ -1140,6 +1175,7 @@ final class GopeedContinuedProcessingManager: NSObject {
|
||||
}
|
||||
|
||||
activeTasks.removeAll()
|
||||
foregroundStartTokens.removeAll()
|
||||
pendingTaskIDs.removeAll()
|
||||
expiringTasks.removeAll()
|
||||
taskGenerations.removeAll()
|
||||
|
||||
@@ -248,26 +248,22 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
guard
|
||||
let data = payload.data(using: .utf8),
|
||||
let json = try? JSONSerialization.jsonObject(with: data)
|
||||
as? [String: Any],
|
||||
let type = json["type"] as? String,
|
||||
let taskID = json["taskId"] as? String
|
||||
else {
|
||||
guard let event = GopeedTaskEvent.decode(payload) else {
|
||||
print("LiveActivity: invalid task event payload")
|
||||
return
|
||||
}
|
||||
|
||||
let taskID = event.taskID
|
||||
|
||||
guard !isTaskSuppressed(taskID) else {
|
||||
return
|
||||
}
|
||||
|
||||
let name = json["name"] as? String ?? "Download"
|
||||
let name = event.name
|
||||
|
||||
switch type {
|
||||
switch event.type {
|
||||
|
||||
case "task.start":
|
||||
case .start:
|
||||
let generation =
|
||||
startGeneration(
|
||||
taskID: taskID
|
||||
@@ -282,7 +278,7 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
)
|
||||
}
|
||||
|
||||
case "task.progress":
|
||||
case .progress:
|
||||
guard
|
||||
let generation =
|
||||
generationForProgress(
|
||||
@@ -298,7 +294,7 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
generation: generation
|
||||
)
|
||||
|
||||
case "task.pause":
|
||||
case .pause:
|
||||
if let generation =
|
||||
invalidateGeneration(
|
||||
taskID: taskID
|
||||
@@ -312,7 +308,7 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
case "task.done":
|
||||
case .done:
|
||||
if let generation =
|
||||
invalidateGeneration(
|
||||
taskID: taskID
|
||||
@@ -325,9 +321,9 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
case "task.error":
|
||||
case .error:
|
||||
let error =
|
||||
json["error"] as? String
|
||||
event.error
|
||||
?? "Download failed"
|
||||
|
||||
if let generation =
|
||||
@@ -343,7 +339,7 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
case "task.delete":
|
||||
case .delete:
|
||||
if let generation =
|
||||
invalidateGeneration(
|
||||
taskID: taskID
|
||||
@@ -357,7 +353,7 @@ final class GopeedLiveActivityManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
case .other:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import Foundation
|
||||
|
||||
enum GopeedTaskEventType: Equatable {
|
||||
case start
|
||||
case progress
|
||||
case pause
|
||||
case done
|
||||
case error
|
||||
case delete
|
||||
case other(String)
|
||||
|
||||
init(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "task.start":
|
||||
self = .start
|
||||
case "task.progress":
|
||||
self = .progress
|
||||
case "task.pause":
|
||||
self = .pause
|
||||
case "task.done":
|
||||
self = .done
|
||||
case "task.error":
|
||||
self = .error
|
||||
case "task.delete":
|
||||
self = .delete
|
||||
default:
|
||||
self = .other(rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GopeedTaskEvent {
|
||||
let type: GopeedTaskEventType
|
||||
let taskID: String
|
||||
let name: String
|
||||
let error: String?
|
||||
|
||||
private struct Payload: Decodable {
|
||||
let type: String
|
||||
let taskId: String
|
||||
let name: String?
|
||||
let error: String?
|
||||
}
|
||||
|
||||
static func decode(
|
||||
_ payload: String
|
||||
) -> GopeedTaskEvent? {
|
||||
guard
|
||||
let data = payload.data(using: .utf8),
|
||||
let decoded =
|
||||
try? JSONDecoder().decode(
|
||||
Payload.self,
|
||||
from: data
|
||||
)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GopeedTaskEvent(
|
||||
type:
|
||||
GopeedTaskEventType(
|
||||
rawValue: decoded.type
|
||||
),
|
||||
taskID: decoded.taskId,
|
||||
name: decoded.name ?? "Download",
|
||||
error: decoded.error
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user