fix: Fix the issue with download tasks

1. Fixed the issue where after the task download is completed, the program will prompt that it has been paused when re-entering the program
This commit is contained in:
X1a0He
2025-02-06 16:29:59 +08:00
parent 7b6bed4fa5
commit d3835005fb
8 changed files with 206 additions and 180 deletions

View File

@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@@ -4,22 +4,6 @@
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "18E41264-2FE9-43DB-B1ED-0F25585C8B89"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Adobe Downloader/Utils/DownloadUtils.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "442"
endingLineNumber = "442"
landmarkName = "startDownloadProcess(task:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
@@ -52,5 +36,19 @@
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "FF32C0C6-C193-4B43-831E-E6F00D1EE00D"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Adobe Downloader/Commons/Extensions.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "22"
endingLineNumber = "22">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@@ -11,7 +11,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if event.modifierFlags.contains(.command) && event.characters?.lowercased() == "q" {
if let mainWindow = NSApp.mainWindow,
mainWindow.sheets.isEmpty && !mainWindow.isSheet {
self?.handleQuitCommand()
_ = self?.applicationShouldTerminate(NSApp)
return nil
}
}
@@ -19,16 +19,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
@MainActor private func handleQuitCommand() {
guard let manager = networkManager else {
NSApplication.shared.terminate(nil)
return
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
guard let manager = networkManager else { return .terminateNow }
let hasActiveDownloads = manager.downloadTasks.contains { task in
if case .downloading = task.totalStatus {
return true
}
if case .downloading = task.totalStatus { return true }
return false
}
@@ -40,6 +35,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
taskId: task.id,
reason: .other(String(localized: "程序即将退出"))
)
await manager.saveTask(task)
}
}
@@ -68,6 +64,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} else {
NSApplication.shared.terminate(nil)
}
return .terminateCancel
}
deinit {

View File

@@ -19,38 +19,3 @@ extension NewDownloadTask {
}
}
}
extension NetworkManager {
func configureNetworkMonitor() {
monitor.pathUpdateHandler = { [weak self] path in
Task { @MainActor [weak self] in
guard let self else { return }
let wasConnected = self.isConnected
self.isConnected = path.status == .satisfied
switch (wasConnected, self.isConnected) {
case (false, true): await resumePausedTasks()
case (true, false): await pauseActiveTasks()
default: break
}
}
}
monitor.start(queue: .global(qos: .utility))
}
private func resumePausedTasks() async {
for task in downloadTasks {
if case .paused(let info) = task.status,
info.reason == .networkIssue {
await downloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
private func pauseActiveTasks() async {
for task in downloadTasks {
if case .downloading = task.status {
await downloadUtils.pauseDownloadTask(taskId: task.id, reason: .networkIssue)
}
}
}
}

View File

@@ -99,22 +99,21 @@ class NetworkManager: ObservableObject {
downloadTasks.append(task)
updateDockBadge()
saveTask(task)
await saveTask(task)
do {
try await downloadUtils.handleDownload(task: task, productInfo: productInfo, allowedPlatform: StorageData.shared.allowedPlatform, saps: saps)
} catch {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: error.localizedDescription,
error: error,
timestamp: Date(),
recoverable: true
)))
await saveTask(task)
await MainActor.run {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: error.localizedDescription,
error: error,
timestamp: Date(),
recoverable: true
)))
saveTask(task)
objectWillChange.send()
}
throw error
}
}
@@ -136,7 +135,7 @@ class NetworkManager: ObservableObject {
timestamp: Date(),
recoverable: false
)))
saveTask(task)
await saveTask(task)
}
if removeFiles {
@@ -352,19 +351,59 @@ class NetworkManager: ObservableObject {
}
}
func saveTask(_ task: NewDownloadTask) {
TaskPersistenceManager.shared.saveTask(task)
func loadSavedTasks() {
Task {
let savedTasks = TaskPersistenceManager.shared.loadTasks()
await MainActor.run {
for task in savedTasks {
for product in task.productsToDownload {
product.updateCompletedPackages()
}
}
downloadTasks.append(contentsOf: savedTasks)
updateDockBadge()
}
}
}
func saveTask(_ task: NewDownloadTask) async {
await TaskPersistenceManager.shared.saveTask(task)
objectWillChange.send()
}
func loadSavedTasks() {
let savedTasks = TaskPersistenceManager.shared.loadTasks()
for task in savedTasks {
for product in task.productsToDownload {
product.updateCompletedPackages()
func configureNetworkMonitor() {
monitor.pathUpdateHandler = { [weak self] path in
let task = { @MainActor @Sendable [weak self] in
guard let self else { return }
let wasConnected = self.isConnected
self.isConnected = path.status == .satisfied
switch (wasConnected, self.isConnected) {
case (false, true):
await self.resumePausedTasks()
case (true, false):
await self.pauseActiveTasks()
default: break
}
}
Task(operation: task)
}
monitor.start(queue: .global(qos: .utility))
}
private func resumePausedTasks() async {
for task in downloadTasks {
if case .paused(let info) = task.status,
info.reason == .networkIssue {
await downloadUtils.resumeDownloadTask(taskId: task.id)
}
}
}
private func pauseActiveTasks() async {
for task in downloadTasks {
if case .downloading = task.status {
await downloadUtils.pauseDownloadTask(taskId: task.id, reason: .networkIssue)
}
}
downloadTasks.append(contentsOf: savedTasks)
updateDockBadge()
}
}

View File

@@ -113,7 +113,9 @@ class DownloadUtils {
guard timeDiff >= NetworkConstants.progressUpdateInterval else { return }
progressHandler?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
Task {
progressHandler?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
lastUpdateTime = now
lastBytes = totalBytesWritten
@@ -133,29 +135,31 @@ class DownloadUtils {
}
}
await MainActor.run {
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.paused(DownloadStatus.PauseInfo(
reason: reason,
timestamp: Date(),
resumable: true
)))
networkManager?.saveTask(task)
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.paused(DownloadStatus.PauseInfo(
reason: reason,
timestamp: Date(),
resumable: true
)))
await networkManager?.saveTask(task)
await MainActor.run {
networkManager?.objectWillChange.send()
}
}
}
func resumeDownloadTask(taskId: UUID) async {
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
startTime: Date(),
estimatedTimeRemaining: nil
)))
await networkManager?.saveTask(task)
await MainActor.run {
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
fileName: task.currentPackage?.fullPackageName ?? "",
currentPackageIndex: 0,
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
startTime: Date(),
estimatedTimeRemaining: nil
)))
networkManager?.saveTask(task)
networkManager?.objectWillChange.send()
}
if task.sapCode == "APRO" {
@@ -178,19 +182,20 @@ class DownloadUtils {
func cancelDownloadTask(taskId: UUID, removeFiles: Bool = false) async {
await cancelTracker.cancel(taskId)
await MainActor.run {
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
}
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
if removeFiles {
try? FileManager.default.removeItem(at: task.directory)
}
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: String(localized: "下载已取消"),
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: String(localized: "下载已取消"),
error: NetworkError.downloadCancelled,
timestamp: Date(),
recoverable: false
)))
await networkManager?.saveTask(task)
await MainActor.run {
networkManager?.updateDockBadge()
networkManager?.objectWillChange.send()
}
@@ -259,47 +264,50 @@ class DownloadUtils {
return
}
Task { @MainActor in
package.downloadedSize = package.downloadSize
package.progress = 1.0
package.status = .completed
package.downloaded = true
Task {
await MainActor.run {
package.downloadedSize = package.downloadSize
package.progress = 1.0
package.status = .completed
package.downloaded = true
var totalDownloaded: Int64 = 0
var totalSize: Int64 = 0
var totalDownloaded: Int64 = 0
var totalSize: Int64 = 0
for prod in task.productsToDownload {
for pkg in prod.packages {
totalSize += pkg.downloadSize
if pkg.downloaded {
totalDownloaded += pkg.downloadSize
for prod in task.productsToDownload {
for pkg in prod.packages {
totalSize += pkg.downloadSize
if pkg.downloaded {
totalDownloaded += pkg.downloadSize
}
}
}
task.totalSize = totalSize
task.totalDownloadedSize = totalDownloaded
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
task.totalSpeed = 0
let allCompleted = task.productsToDownload.allSatisfy {
product in product.packages.allSatisfy { $0.downloaded }
}
if allCompleted {
task.setStatus(.completed(DownloadStatus.CompletionInfo(
timestamp: Date(),
totalTime: Date().timeIntervalSince(task.createAt),
totalSize: totalSize
)))
}
product.updateCompletedPackages()
}
task.totalSize = totalSize
task.totalDownloadedSize = totalDownloaded
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
task.totalSpeed = 0
let allCompleted = task.productsToDownload.allSatisfy {
product in product.packages.allSatisfy { $0.downloaded }
await networkManager?.saveTask(task)
await MainActor.run {
networkManager?.objectWillChange.send()
}
if allCompleted {
task.setStatus(.completed(DownloadStatus.CompletionInfo(
timestamp: Date(),
totalTime: Date().timeIntervalSince(task.createAt),
totalSize: totalSize
)))
}
product.updateCompletedPackages()
networkManager?.saveTask(task)
networkManager?.objectWillChange.send()
continuation.resume()
}
continuation.resume()
},
progressHandler: { [weak networkManager] bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
Task { @MainActor in
@@ -444,8 +452,8 @@ class DownloadUtils {
startTime: Date(),
estimatedTimeRemaining: nil
)))
networkManager?.saveTask(task)
}
await networkManager?.saveTask(task)
await progress.increment()
@@ -487,8 +495,8 @@ class DownloadUtils {
totalTime: Date().timeIntervalSince(task.createAt),
totalSize: task.totalSize
)))
networkManager?.saveTask(task)
}
await networkManager?.saveTask(task)
}
}
@@ -925,6 +933,18 @@ class DownloadUtils {
let (errorMessage, isRecoverable) = classifyError(error)
if isRecoverable,
let downloadTask = await cancelTracker.downloadTasks[taskId] {
let resumeData = await withCheckedContinuation { continuation in
downloadTask.cancel(byProducingResumeData: { data in
continuation.resume(returning: data)
})
}
if let resumeData = resumeData {
await cancelTracker.storeResumeData(taskId, data: resumeData)
}
}
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
task.retryCount += 1
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
@@ -946,22 +966,22 @@ class DownloadUtils {
}
}
} else {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: errorMessage,
error: error,
timestamp: Date(),
recoverable: isRecoverable
)))
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("\(task.sapCode)")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
await networkManager?.saveTask(task)
await MainActor.run {
task.setStatus(.failed(DownloadStatus.FailureInfo(
message: errorMessage,
error: error,
timestamp: Date(),
recoverable: isRecoverable
)))
if let currentPackage = task.currentPackage {
let destinationDir = task.directory
.appendingPathComponent("\(task.sapCode)")
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
try? FileManager.default.removeItem(at: fileURL)
}
networkManager?.saveTask(task)
networkManager?.updateDockBadge()
networkManager?.objectWillChange.send()
}
@@ -973,7 +993,7 @@ class DownloadUtils {
case let networkError as NetworkError:
switch networkError {
case .noConnection:
return (String(localized: "网络连接已断开"), true)
return (String(localized: "网络连接已断开"), true)
case .timeout:
return (String(localized: "下载超时"), true)
case .serverUnreachable:
@@ -987,12 +1007,14 @@ class DownloadUtils {
}
case let urlError as URLError:
switch urlError.code {
case .notConnectedToInternet:
case .notConnectedToInternet, .networkConnectionLost, .dataNotAllowed:
return (String(localized: "网络连接已断开"), true)
case .timedOut:
return (String(localized: "连接超时"), true)
case .cancelled:
return (String(localized: "下载已取消"), false)
case .cannotConnectToHost, .dnsLookupFailed:
return (String(localized: "无法连接到服务器"), true)
default:
return (urlError.localizedDescription, true)
}

View File

@@ -23,7 +23,7 @@ class TaskPersistenceManager {
: "Adobe Downloader \(sapCode)_\(version)-\(language)-\(platform)-task.json"
}
func saveTask(_ task: NewDownloadTask) {
func saveTask(_ task: NewDownloadTask) async {
let fileName = getTaskFileName(
sapCode: task.sapCode,
version: task.version,
@@ -34,12 +34,10 @@ class TaskPersistenceManager {
var resumeDataDict: [String: Data]? = nil
Task {
if let currentPackage = task.currentPackage,
let cancelTracker = self.cancelTracker,
let resumeData = await cancelTracker.getResumeData(task.id) {
resumeDataDict = [currentPackage.id.uuidString: resumeData]
}
if let currentPackage = task.currentPackage,
let cancelTracker = self.cancelTracker,
let resumeData = await cancelTracker.getResumeData(task.id) {
resumeDataDict = [currentPackage.id.uuidString: resumeData]
}
let taskData = TaskData(
@@ -151,13 +149,13 @@ class TaskPersistenceManager {
let initialStatus: DownloadStatus
switch taskData.totalStatus {
case .completed:
initialStatus = taskData.totalStatus
case .failed:
initialStatus = taskData.totalStatus
case .completed(let info):
initialStatus = .completed(info)
case .failed(let info):
initialStatus = .failed(info)
case .downloading:
initialStatus = .paused(DownloadStatus.PauseInfo(
reason: .other(String(localized: "程序意外退出")),
reason: .other(String(localized: "程序退出")),
timestamp: Date(),
resumable: true
))

View File

@@ -11,6 +11,7 @@
},
"(可能导致处理 Setup 组件失败)" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -21,6 +22,7 @@
}
},
"(将导致无法使用安装功能)" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -286,7 +288,6 @@
},
"Debug 模式" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1384,7 +1385,6 @@
}
},
"将执行的命令:" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1425,7 +1425,6 @@
}
},
"展开全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1446,6 +1445,7 @@
}
},
"已处理" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1456,6 +1456,7 @@
}
},
"已备份" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1661,7 +1662,6 @@
}
},
"折叠全部" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -1889,6 +1889,9 @@
}
}
}
},
"无法连接到服务器" : {
},
"无法连接到服务器: %@" : {
"comment" : "Server unreachable",
@@ -2583,6 +2586,7 @@
}
},
"程序意外退出" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
@@ -2601,6 +2605,9 @@
}
}
}
},
"程序退出" : {
},
"程序重启后自动暂停" : {
"localizations" : {