66//
77
88import Foundation
9+ import SwiftUI
10+
11+ /// 下载状态模型(纯数据结构)
12+ struct DownloadState {
13+ var downloadingVersion : String = " "
14+ var downloadingUrl : String = " "
15+ var progress : Double = 0.0
16+ var speed : String = " 0.0 KB/s "
17+ var downloadedSize : String = " 0.0 MB "
18+ var totalSize : String = " 0.0 MB "
19+ var isFinished : Bool = false
20+ var errorMessage : String ? = nil
21+ var downloadedFilePath : String ? = nil
22+ }
923
1024/// 通用下载代理,支持进度、超时、错误、完成回调
11- class DownloadDelegate : NSObject , URLSessionDelegate , URLSessionDownloadDelegate , @unchecked Sendable {
25+ @MainActor
26+ class DownloadManager : NSObject , ObservableObject , URLSessionDelegate , URLSessionDownloadDelegate {
27+ /// 一个整体的下载状态
28+ @Published var state = DownloadState ( )
29+
1230 private let timerLock = NSLock ( )
1331 private var _timer : Timer ?
1432 private var timer : Timer ? {
@@ -19,7 +37,6 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
1937 private let timeoutSeconds : Double
2038 private var didTimeout = false
2139 private var downloadTask : URLSessionDownloadTask ?
22- private let onProgress : ( Double , String , String ) -> Void
2340 private let onSuccess : ( String ) -> Void
2441 private let onError : ( String ) -> Void
2542 private var lastWritten : Int64 = 0
@@ -28,27 +45,52 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
2845 /// 初始化
2946 /// - Parameters:
3047 /// - timeout: 超时时间(秒)
31- /// - onProgress: 进度回调 0~1
3248 /// - onSuccess: 成功回调,参数为下载文件临时路径
3349 /// - onError: 错误回调,参数为错误信息
3450 init ( timeout: Double = 10 ,
35- onProgress: @escaping ( Double , String , String ) -> Void ,
3651 onSuccess: @escaping ( String ) -> Void ,
3752 onError: @escaping ( String ) -> Void ) {
3853 timeoutSeconds = timeout
39- self . onProgress = onProgress
4054 self . onSuccess = onSuccess
4155 self . onError = onError
4256 }
4357
58+ // MARK: - 对外启动下载接口
59+ func startDownload( from urlString: String , version: String , totalSize: Int64 , useProxy: Bool = true ) {
60+ guard let url = URL ( string: urlString) else {
61+ self . state. isFinished = true
62+ self . state. errorMessage = String ( localized: . DownloadURLInvalid)
63+ self . onError ( self . state. errorMessage)
64+ return
65+ }
66+ state. downloadingVersion = version
67+ state. downloadingUrl = urlString
68+ state. totalSize = formatByte ( Double ( totalSize) )
69+ state. isFinished = false
70+ state. progress = 0.0
71+ state. errorMessage = nil
72+ var config = URLSessionConfiguration . default
73+ if useProxy {
74+ config = getProxyUrlSessionConfigure ( )
75+ }
76+ session = URLSession ( configuration: config, delegate: self , delegateQueue: nil )
77+ let task = session!. downloadTask ( with: url)
78+ // 启动超时检测计时器
79+ startTimeout ( downloadTask: task)
80+ // 启动下载任务
81+ task. resume ( )
82+ }
83+
4484 func startTimeout( downloadTask: URLSessionDownloadTask ) {
4585 self . downloadTask = downloadTask
4686 timer = Timer . scheduledTimer ( withTimeInterval: timeoutSeconds, repeats: false ) { [ weak self] _ in
4787 guard let self = self else { return }
4888 self . didTimeout = true
4989 downloadTask. cancel ( )
5090 DispatchQueue . main. async {
51- self . onError ( " 下载超时,请检查网络或代理设置 " )
91+ self . state. isFinished = true
92+ self . state. errorMessage = String ( localized: . DownloadErrorOccurred)
93+ self . onError ( self . state. errorMessage)
5294 }
5395 }
5496 }
@@ -61,7 +103,9 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
61103 self . didTimeout = true
62104 task. cancel ( )
63105 DispatchQueue . main. async {
64- self . onError ( " 下载超时,请检查网络或代理设置 " )
106+ self . state. isFinished = true
107+ self . state. errorMessage = String ( localized: . DownloadTimeoutError)
108+ self . onError ( self . state. errorMessage)
65109 }
66110 }
67111 }
@@ -83,10 +127,11 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
83127 lastWritten = totalBytesWritten
84128 lastTime = now
85129 let progress = totalBytesExpectedToWrite > 0 ? Double ( totalBytesWritten) / Double( totalBytesExpectedToWrite) : 0
86-
87- DispatchQueue . main. async {
88- self . onProgress ( progress, speed, formatByte ( Double ( totalBytesWritten) ) )
89- }
130+ let downloaded = formatByte ( Double ( totalBytesWritten) )
131+ // 更新结构体中的状态
132+ self . state. progress = progress
133+ self . state. speed = speed
134+ self . state. downloadedSize = downloaded
90135 resetTimeout ( )
91136 }
92137
@@ -96,6 +141,9 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
96141 let fileName = downloadTask. response? . suggestedFilename ?? " "
97142 guard locationPath != " " , fileName != " " else {
98143 logger. info ( " urlSession: locationPath or fileName missing " , )
144+ self . state. isFinished = true
145+ self . state. errorMessage = String ( localized: . DownloadSaveFailed) + " : urlSession: locationPath or fileName missing "
146+ self . onError ( self . state. errorMessage)
99147 return
100148 }
101149 let documentsPath = NSHomeDirectory ( ) + " /Library/Caches/Download "
@@ -106,18 +154,28 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
106154 try FileManager . default. removeItem ( atPath: filePath)
107155 }
108156 } catch let catchError {
109- alertDialog ( title: " 下载失败1 " , message: " 保存出错 = \( catchError. localizedDescription) " )
157+ self . state. isFinished = true
158+ self . state. errorMessage = String ( localized: . DownloadSaveFailed) + " : \( catchError. localizedDescription) "
159+ self . onError ( self . state. errorMessage)
160+ return
110161 }
111162 do {
112163 if FileManager . default. fileExists ( atPath: documentsPath) == false {
113164 try FileManager . default. createDirectory ( atPath: documentsPath, withIntermediateDirectories: true )
114165 }
166+ // 记录下载文件路径
167+ self . state. downloadedFilePath = filePath
115168 // 移动文件,不然随时被删除
116169 try FileManager . default. moveItem ( atPath: locationPath, toPath: filePath)
117170 } catch let catchError {
118- alertDialog ( title: " 下载失败2 " , message: " 保存出错 = \( catchError. localizedDescription) " )
171+ self . state. isFinished = true
172+ self . state. errorMessage = String ( localized: . DownloadSaveFailed) + " : \( catchError. localizedDescription) "
173+ self . onError ( self . state. errorMessage)
174+ return
119175 }
120176 DispatchQueue . main. async {
177+ // 更新状态为完成
178+ self . state. isFinished = true
121179 self . onSuccess ( filePath)
122180 }
123181 }
@@ -130,7 +188,9 @@ class DownloadDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate
130188 return
131189 }
132190 DispatchQueue . main. async {
133- self . onError ( " 下载失败: \( error. localizedDescription) " )
191+ self . state. isFinished = true
192+ self . state. errorMessage = String ( localized: . DownloadErrorOccurred) + " : \( error. localizedDescription) "
193+ self . onError ( self . state. errorMessage)
134194 }
135195 }
136196 }
0 commit comments