@@ -9,6 +9,7 @@ import '../../models/audio_item.dart';
99import '../../providers/audio_library_provider.dart' ;
1010import '../../providers/collection_provider.dart' ;
1111import '../../services/app_logger.dart' ;
12+ import '../../services/reliable_http_downloader.dart' ;
1213import '../../utils/app_data_dir.dart' ;
1314import '../../utils/audio_duration.dart' ;
1415import 'audio_finalization_service.dart' ;
@@ -44,9 +45,15 @@ class AudioImportService {
4445 transcodeService: transcodeService ?? AudioTranscodeService (),
4546 computeSha256: computeSha256,
4647 uuid: uuid,
47- );
48+ ) {
49+ _downloader = DioReliableHttpDownloader (dio: _dio);
50+ }
4851
4952 final Dio _dio;
53+
54+ /// `ReliableHttpDownloader` 接口本身不提供释放能力,本类也不做 `dispose`
55+ /// (复用调用方注入的 [_dio] 或全局默认 Dio,生命周期不归本类管)。
56+ late final ReliableHttpDownloader _downloader;
5057 final Uuid _uuid;
5158 final Future <Directory > Function () _resolveDataDir;
5259 final Future <int > Function (String relativePath) _readDurationSeconds;
@@ -278,30 +285,37 @@ class AudioImportService {
278285 final tmpDir = Directory (p.join (dataDir.path, 'tmp' , 'audio_import' ));
279286 await tmpDir.create (recursive: true );
280287
281- final tmpFile = File (p.join (tmpDir.path, '$audioId .part' ));
282288 final downloadedFile = File (
283289 p.join (tmpDir.path, '$audioId .${resolved .extension }' ),
284290 );
285291 try {
286- await _dio.download (
287- resolved.uri.toString (),
288- tmpFile.path,
292+ // allowResume: false——导入取消/失败不需要跨调用续传,失败即清理
293+ // `.part`,与迁移前手写 finally 删临时文件的既有语义一致。
294+ await _downloader.download (
295+ uri: resolved.uri,
296+ savePath: downloadedFile.path,
297+ allowResume: false ,
289298 cancelToken: cancelToken,
290- options: Options (followRedirects: true ),
291- onReceiveProgress: (received, total) {
292- onProgress? .call (received, total <= 0 ? null : total);
293- },
299+ onProgress: (received, total) => onProgress? .call (received, total),
294300 );
295- await tmpFile.rename (downloadedFile.path);
296301 return p.join ('tmp' , 'audio_import' , p.basename (downloadedFile.path));
297- } on DioException catch (e) {
298- if (CancelToken . isCancel (e) ) {
302+ } on ReliableDownloadException catch (e) {
303+ if (e.kind == ReliableDownloadFailure .cancelled ) {
299304 throw const AudioImportException (
300305 AudioImportFailureCode .canceled,
301306 'Audio import canceled' ,
302307 );
303308 }
304309 _logDownloadFailure (resolved.uri, e);
310+ // 磁盘写入失败已被下载器内部归类为 storage(如空间不足),单独区分,
311+ // 不与「网络请求失败」混为一谈。
312+ if (e.kind == ReliableDownloadFailure .storage) {
313+ throw AudioImportException (
314+ AudioImportFailureCode .storage,
315+ 'Failed to save audio' ,
316+ e,
317+ );
318+ }
305319 throw AudioImportException (
306320 AudioImportFailureCode .network,
307321 'Failed to download audio' ,
@@ -313,12 +327,6 @@ class AudioImportService {
313327 'Failed to save audio' ,
314328 e,
315329 );
316- } finally {
317- if (await tmpFile.exists ()) {
318- try {
319- await tmpFile.delete ();
320- } catch (_) {}
321- }
322330 }
323331 }
324332
@@ -365,14 +373,14 @@ class AudioImportService {
365373 return uri.replace (scheme: 'https' , pathSegments: nextSegments);
366374 }
367375
368- void _logDownloadFailure (Uri uri, DioException error) {
376+ void _logDownloadFailure (Uri uri, ReliableDownloadException error) {
369377 AppLogger .log (
370378 _logTag,
371379 'download failed url=$uri '
372- 'type =${error .type } '
373- 'status=${error .response ?. statusCode ?? "(null)" } '
374- 'message=${error .message ?? "(null)" } '
375- 'cause=${error .error ?? "(null)" }' ,
380+ 'kind =${error .kind } '
381+ 'status=${error .statusCode ?? "(null)" } '
382+ 'message=${error .message } '
383+ 'cause=${error .cause ?? "(null)" }' ,
376384 );
377385 }
378386
0 commit comments