@@ -14,10 +14,12 @@ enum DownloadStatus {
1414}
1515
1616/// 下载进度回调
17- typedef DownloadProgressCallback = void Function (int received, int total, double progress);
17+ typedef DownloadProgressCallback =
18+ void Function (int received, int total, double progress);
1819
1920/// 下载状态回调
20- typedef DownloadStatusCallback = void Function (DownloadStatus status, String ? message);
21+ typedef DownloadStatusCallback =
22+ void Function (DownloadStatus status, String ? message);
2123
2224/// 下载配置类
2325class DownloadConfig {
@@ -96,53 +98,64 @@ class DownloadUtils {
9698 ),
9799 );
98100
101+ /// 存储所有下载的CancelToken
102+ static final List <CancelToken > _cancelTokens = [];
103+
99104 /// 单文件下载
100105 static Future <DownloadResult > downloadFile ({
101106 required DownloadConfig config,
102107 DownloadProgressCallback ? onProgress,
103108 DownloadStatusCallback ? onStatusChange,
104109 }) async {
105- try {
106- // 通知开始下载
107- onStatusChange? .call (DownloadStatus .pending, 'download_preparing' .tr ());
110+ // 通知开始下载
111+ onStatusChange? .call (DownloadStatus .pending, 'download_preparing' .tr ());
108112
109- // 确定保存目录
110- final String saveDirectory = config.saveDir ?? await _getDefaultDownloadDir ();
111- if (saveDirectory.isEmpty) {
112- return DownloadResult (
113- status: DownloadStatus .failed,
114- errorMessage: 'download_no_directory' .tr (),
115- );
116- }
113+ // 确定保存目录
114+ final String saveDirectory =
115+ config.saveDir ?? await _getDefaultDownloadDir ();
116+ if (saveDirectory.isEmpty) {
117+ return DownloadResult (
118+ status: DownloadStatus .failed,
119+ errorMessage: 'download_no_directory' .tr (),
120+ );
121+ }
117122
118- // 构建文件路径
119- String filePath = '$saveDirectory /${config .fileName }' ;
120-
121- // 检查文件是否已存在
122- final File file = File (filePath);
123- if (file.existsSync () && ! config.allowOverwrite) {
124- // 生成新文件名
125- final String extension = filePath.split ('.' ).last;
126- final String baseName = filePath.split ('.' ).take (filePath.split ('.' ).length - 1 ).join ('.' );
127- int counter = 1 ;
128- while (File ('$baseName ($counter ).$extension ' ).existsSync ()) {
129- counter++ ;
130- }
131- filePath = '$baseName ($counter ).$extension ' ;
123+ // 构建文件路径
124+ String filePath = '$saveDirectory /${config .fileName }' ;
125+
126+ // 检查文件是否已存在
127+ final File file = File (filePath);
128+ if (file.existsSync () && ! config.allowOverwrite) {
129+ // 生成新文件名
130+ final String extension = filePath.split ('.' ).last;
131+ final String baseName = filePath
132+ .split ('.' )
133+ .take (filePath.split ('.' ).length - 1 )
134+ .join ('.' );
135+ int counter = 1 ;
136+ while (File ('$baseName ($counter ).$extension ' ).existsSync ()) {
137+ counter++ ;
132138 }
139+ filePath = '$baseName ($counter ).$extension ' ;
140+ }
133141
134- // 确保目录存在
135- final Directory directory = Directory (saveDirectory);
136- if (! directory.existsSync ()) {
137- directory.createSync (recursive: true );
138- }
142+ // 确保目录存在
143+ final Directory directory = Directory (saveDirectory);
144+ if (! directory.existsSync ()) {
145+ directory.createSync (recursive: true );
146+ }
147+
148+ // 开始下载
149+ onStatusChange? .call (DownloadStatus .downloading, 'download_starting' .tr ());
139150
140- // 开始下载
141- onStatusChange ? . call ( DownloadStatus .downloading, 'download_starting' . tr ()) ;
151+ int retryCount = 0 ;
152+ bool downloadSuccess = false ;
142153
143- int retryCount = 0 ;
144- bool downloadSuccess = false ;
154+ // 创建CancelToken并添加到列表
155+ final cancelToken = CancelToken ();
156+ _cancelTokens.add (cancelToken);
145157
158+ try {
146159 while (retryCount <= config.maxRetries && ! downloadSuccess) {
147160 try {
148161 await _dio.download (
@@ -153,6 +166,7 @@ class DownloadUtils {
153166 receiveTimeout: Duration (seconds: config.timeout),
154167 ),
155168 queryParameters: config.queryParams,
169+ cancelToken: cancelToken,
156170 onReceiveProgress: (received, total) {
157171 if (total != - 1 ) {
158172 final double progress = received / total;
@@ -167,10 +181,12 @@ class DownloadUtils {
167181 if (retryCount <= config.maxRetries) {
168182 onStatusChange? .call (
169183 DownloadStatus .pending,
170- 'download_retrying' .tr (namedArgs: {
171- 'attempt' : retryCount.toString (),
172- 'max' : config.maxRetries.toString (),
173- }),
184+ 'download_retrying' .tr (
185+ namedArgs: {
186+ 'attempt' : retryCount.toString (),
187+ 'max' : config.maxRetries.toString (),
188+ },
189+ ),
174190 );
175191 await Future .delayed (Duration (milliseconds: config.retryInterval));
176192 } else {
@@ -195,11 +211,17 @@ class DownloadUtils {
195211 downloadedSize: file.lengthSync (),
196212 );
197213 } catch (e) {
198- onStatusChange? .call (DownloadStatus .failed, 'download_failed' .tr (namedArgs: {'message' : e.toString ()}));
214+ onStatusChange? .call (
215+ DownloadStatus .failed,
216+ 'download_failed' .tr (namedArgs: {'message' : e.toString ()}),
217+ );
199218 return DownloadResult (
200219 status: DownloadStatus .failed,
201220 errorMessage: e.toString (),
202221 );
222+ } finally {
223+ // 从列表中移除cancelToken
224+ _cancelTokens.remove (cancelToken);
203225 }
204226 }
205227
@@ -218,11 +240,13 @@ class DownloadUtils {
218240 final config = configs[i];
219241 onStatusChange? .call (
220242 DownloadStatus .pending,
221- 'download_preparing_file' .tr (namedArgs: {
222- 'index' : (i + 1 ).toString (),
223- 'total' : totalCount.toString (),
224- 'name' : config.fileName,
225- }),
243+ 'download_preparing_file' .tr (
244+ namedArgs: {
245+ 'index' : (i + 1 ).toString (),
246+ 'total' : totalCount.toString (),
247+ 'name' : config.fileName,
248+ },
249+ ),
226250 );
227251
228252 final result = await downloadFile (
@@ -300,7 +324,13 @@ class DownloadUtils {
300324
301325 /// 取消所有下载
302326 static void cancelAllDownloads () {
303- // TODO:Dio 的取消逻辑需要使用 CancelToken
304- // 这里简化处理,实际项目中可能需要维护一个 CancelToken 列表
327+ // 取消所有下载任务
328+ for (final token in _cancelTokens) {
329+ if (! token.isCancelled) {
330+ token.cancel ('Download cancelled by user' );
331+ }
332+ }
333+ // 清空列表
334+ _cancelTokens.clear ();
305335 }
306336}
0 commit comments