@@ -193,14 +193,15 @@ class DownloadManager {
193193 }
194194
195195 Future <void > _startDownload (DownloadTask task) async {
196+ Dio ? dio;
196197 try {
197198 if (task.mirrors.isEmpty) {
198199 _updateTaskStatus (task.id, DownloadStatus .failed,
199200 errorMessage: 'No mirrors available!' );
200201 return ;
201202 }
202203
203- Dio dio = Dio ();
204+ dio = Dio ();
204205 String path = await _getFilePath ('${task .md5 }.${task .format }' );
205206 List <String > orderedMirrors = _reorderMirrors (task.mirrors);
206207
@@ -226,37 +227,98 @@ class DownloadManager {
226227 return ;
227228 }
228229
229- _updateTaskStatus (task.id, DownloadStatus .downloading);
230-
230+ // Try to download from each mirror until successful
231+ bool downloadSuccessful = false ;
232+ int mirrorIndex = orderedMirrors.indexOf (workingMirror);
233+
234+ // Create a single cancel token for the entire mirror retry sequence
231235 CancelToken cancelToken = CancelToken ();
232236 _activeDownloads[task.id] =
233237 _activeDownloads[task.id]! .copyWith (cancelToken: cancelToken);
234-
235- await dio.download (
236- workingMirror,
237- path,
238- options: Options (headers: {
239- 'Connection' : 'Keep-Alive' ,
240- 'User-Agent' :
241- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
242- }),
243- onReceiveProgress: (rcv, total) {
244- if (! (rcv.isNaN || rcv.isInfinite) &&
245- ! (total.isNaN || total.isInfinite)) {
246- double progress = rcv / total;
247- _updateTaskProgress (task.id, progress, rcv, total);
248-
249- _notificationService.showDownloadNotification (
238+
239+ while (mirrorIndex < orderedMirrors.length && ! downloadSuccessful) {
240+ final currentMirror = orderedMirrors[mirrorIndex];
241+
242+ try {
243+ _updateTaskStatus (task.id, DownloadStatus .downloading);
244+
245+ await dio.download (
246+ currentMirror,
247+ path,
248+ options: Options (headers: {
249+ 'Connection' : 'Keep-Alive' ,
250+ 'User-Agent' :
251+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
252+ }),
253+ onReceiveProgress: (rcv, total) {
254+ if (! (rcv.isNaN || rcv.isInfinite) &&
255+ ! (total.isNaN || total.isInfinite)) {
256+ double progress = rcv / total;
257+ _updateTaskProgress (task.id, progress, rcv, total);
258+
259+ _notificationService.showDownloadNotification (
260+ id: task.id.hashCode,
261+ title: task.title,
262+ body: 'Downloading...' ,
263+ progress: (progress * 100 ).toInt (),
264+ );
265+ }
266+ },
267+ deleteOnError: true ,
268+ cancelToken: cancelToken,
269+ );
270+
271+ // Download completed successfully
272+ downloadSuccessful = true ;
273+
274+ } on DioException catch (e) {
275+ if (e.type == DioExceptionType .cancel) {
276+ _updateTaskStatus (task.id, DownloadStatus .cancelled);
277+ await _notificationService.cancelNotification (task.id.hashCode);
278+ return ;
279+ }
280+
281+ // Try next mirror if available
282+ mirrorIndex++ ;
283+ if (mirrorIndex < orderedMirrors.length) {
284+ _updateTaskStatus (task.id, DownloadStatus .downloadingMirrors);
285+ await _notificationService.showDownloadNotification (
250286 id: task.id.hashCode,
251287 title: task.title,
252- body: 'Downloading ...' ,
253- progress: (progress * 100 ). toInt () ,
288+ body: 'Retrying with alternate mirror ...' ,
289+ progress: 0 ,
254290 );
291+
292+ // Wait up to 2 seconds before retrying, but check for cancellation
293+ const totalDelay = Duration (seconds: 2 );
294+ const stepDelay = Duration (milliseconds: 100 );
295+ var elapsed = Duration .zero;
296+ while (elapsed < totalDelay) {
297+ await Future .delayed (stepDelay);
298+ elapsed += stepDelay;
299+
300+ // Check if task was cancelled during the delay
301+ if (! _activeDownloads.containsKey (task.id) ||
302+ _activeDownloads[task.id]? .cancelToken? .isCancelled == true ) {
303+ _updateTaskStatus (task.id, DownloadStatus .cancelled);
304+ await _notificationService.cancelNotification (task.id.hashCode);
305+ return ;
306+ }
307+ }
308+ } else {
309+ // No more mirrors to try; mark task as failed before re-throwing
310+ _updateTaskStatus (task.id, DownloadStatus .failed,
311+ errorMessage: 'All mirrors failed!' );
312+ await _notificationService.showDownloadNotification (
313+ id: task.id.hashCode,
314+ title: task.title,
315+ body: 'Download failed: All mirrors exhausted' ,
316+ progress: - 1 ,
317+ );
318+ rethrow ;
255319 }
256- },
257- deleteOnError: true ,
258- cancelToken: cancelToken,
259- );
320+ }
321+ }
260322
261323 if (! _activeDownloads.containsKey (task.id)) {
262324 return ;
@@ -321,6 +383,9 @@ class DownloadManager {
321383 body: 'Download failed' ,
322384 progress: - 1 ,
323385 );
386+ } finally {
387+ // Always close the Dio instance to prevent resource leaks
388+ dio? .close ();
324389 }
325390 }
326391
0 commit comments