LCOV - code coverage report
Current view: top level - Users/duwen/Documents/code/dio/dio/lib/src/entry - dio_for_native.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 66 77 85.7 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:io';
       3             : import '../adapter.dart';
       4             : import '../cancel_token.dart';
       5             : import '../dio_mixin.dart';
       6             : import '../response.dart';
       7             : import '../dio.dart';
       8             : import '../headers.dart';
       9             : import '../options.dart';
      10             : import '../dio_error.dart';
      11             : import '../adapters/io_adapter.dart';
      12             : 
      13          16 : Dio createDio([BaseOptions? baseOptions]) => DioForNative(baseOptions);
      14             : 
      15             : class DioForNative with DioMixin implements Dio {
      16             :   /// Create Dio instance with default [BaseOptions].
      17             :   /// It is recommended that an application use only the same DIO singleton.
      18           8 :   DioForNative([BaseOptions? baseOptions]) {
      19          16 :     options = baseOptions ?? BaseOptions();
      20          16 :     httpClientAdapter = DefaultHttpClientAdapter();
      21             :   }
      22             : 
      23             :   ///  Download the file and save it in local. The default http method is "GET",
      24             :   ///  you can custom it by [Options.method].
      25             :   ///
      26             :   ///  [urlPath]: The file url.
      27             :   ///
      28             :   ///  [savePath]: The path to save the downloading file later. it can be a String or
      29             :   ///  a callback [String Function(Headers)]:
      30             :   ///  1. A path with String type, eg "xs.jpg"
      31             :   ///  2. A callback `String Function(Headers)`; for example:
      32             :   ///  ```dart
      33             :   ///   await dio.download(url,(Headers headers){
      34             :   ///        // Extra info: redirect counts
      35             :   ///        print(headers.value('redirects'));
      36             :   ///        // Extra info: real uri
      37             :   ///        print(headers.value('uri'));
      38             :   ///      ...
      39             :   ///      return "...";
      40             :   ///    });
      41             :   ///  ```
      42             :   ///
      43             :   ///  [onReceiveProgress]: The callback to listen downloading progress.
      44             :   ///  please refer to [ProgressCallback].
      45             :   ///
      46             :   /// [deleteOnError] Whether delete the file when error occurs. The default value is [true].
      47             :   ///
      48             :   ///  [lengthHeader] : The real size of original file (not compressed).
      49             :   ///  When file is compressed:
      50             :   ///  1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
      51             :   ///  2. If this value is not 'content-length', maybe a custom header indicates the original
      52             :   ///  file size , the `total` argument of `onProgress` will be this header value.
      53             :   ///
      54             :   ///  you can also disable the compression by specifying the 'accept-encoding' header value as '*'
      55             :   ///  to assure the value of `total` argument of `onProgress` is not -1. for example:
      56             :   ///
      57             :   ///     await dio.download(url, "./example/flutter.svg",
      58             :   ///     options: Options(headers: {HttpHeaders.acceptEncodingHeader: "*"}),  // disable gzip
      59             :   ///     onProgress: (received, total) {
      60             :   ///       if (total != -1) {
      61             :   ///        print((received / total * 100).toStringAsFixed(0) + "%");
      62             :   ///       }
      63             :   ///     });
      64             :   @override
      65           1 :   Future<Response> download(
      66             :     String urlPath,
      67             :     savePath, {
      68             :     ProgressCallback? onReceiveProgress,
      69             :     Map<String, dynamic>? queryParameters,
      70             :     CancelToken? cancelToken,
      71             :     bool deleteOnError = true,
      72             :     String lengthHeader = Headers.contentLengthHeader,
      73             :     data,
      74             :     Options? options,
      75             :   }) async {
      76             :     // We set the `responseType` to [ResponseType.STREAM] to retrieve the
      77             :     // response stream.
      78           1 :     options ??= DioMixin.checkOptions('GET', options);
      79             : 
      80             :     // Receive data with stream.
      81           1 :     options.responseType = ResponseType.stream;
      82             :     Response<ResponseBody> response;
      83             :     try {
      84           2 :       response = await request<ResponseBody>(
      85             :         urlPath,
      86             :         data: data,
      87             :         options: options,
      88             :         queryParameters: queryParameters,
      89           1 :         cancelToken: cancelToken ?? CancelToken(),
      90             :       );
      91           1 :     } on DioError catch (e) {
      92           2 :       if (e.type == DioErrorType.response) {
      93           4 :         if (e.response!.requestOptions.receiveDataWhenStatusError == true) {
      94           3 :           var res = await transformer.transformResponse(
      95           3 :             e.response!.requestOptions..responseType = ResponseType.json,
      96           2 :             e.response!.data as ResponseBody,
      97             :           );
      98           2 :           e.response!.data = res;
      99             :         } else {
     100           2 :           e.response!.data = null;
     101             :         }
     102             :       }
     103             :       rethrow;
     104             :     }
     105             : 
     106           4 :     response.headers = Headers.fromMap(response.data!.headers);
     107             : 
     108             :     File file;
     109           1 :     if (savePath is Function) {
     110           1 :       assert(savePath is String Function(Headers),
     111             :           'savePath callback type must be `String Function(HttpHeaders)`');
     112             : 
     113             :       // Add real uri and redirect information to headers
     114           1 :       response.headers
     115           4 :         ..add('redirects', response.redirects.length.toString())
     116           3 :         ..add('uri', response.realUri.toString());
     117             : 
     118           3 :       file = File(savePath(response.headers) as String);
     119             :     } else {
     120           2 :       file = File(savePath.toString());
     121             :     }
     122             : 
     123             :     //If directory (or file) doesn't exist yet, the entire method fails
     124           1 :     file.createSync(recursive: true);
     125             : 
     126             :     // Shouldn't call file.writeAsBytesSync(list, flush: flush),
     127             :     // because it can write all bytes by once. Consider that the
     128             :     // file with a very big size(up 1G), it will be expensive in memory.
     129           1 :     var raf = file.openSync(mode: FileMode.write);
     130             : 
     131             :     //Create a Completer to notify the success/error state.
     132           1 :     var completer = Completer<Response>();
     133           1 :     var future = completer.future;
     134             :     var received = 0;
     135             : 
     136             :     // Stream<Uint8List>
     137           2 :     var stream = response.data!.stream;
     138             :     var compressed = false;
     139             :     var total = 0;
     140           2 :     var contentEncoding = response.headers.value(Headers.contentEncodingHeader);
     141             :     if (contentEncoding != null) {
     142           2 :       compressed = ['gzip', 'deflate', 'compress'].contains(contentEncoding);
     143             :     }
     144           1 :     if (lengthHeader == Headers.contentLengthHeader && compressed) {
     145           0 :       total = -1;
     146             :     } else {
     147           3 :       total = int.parse(response.headers.value(lengthHeader) ?? '-1');
     148             :     }
     149             : 
     150             :     late StreamSubscription subscription;
     151             :     Future? asyncWrite;
     152             :     var closed = false;
     153           1 :     Future _closeAndDelete() async {
     154             :       if (!closed) {
     155             :         closed = true;
     156           1 :         await asyncWrite;
     157           2 :         await raf.close();
     158           2 :         if (deleteOnError) await file.delete();
     159             :       }
     160             :     }
     161             : 
     162           1 :     subscription = stream.listen(
     163           1 :       (data) {
     164           1 :         subscription.pause();
     165             :         // Write file asynchronously
     166           3 :         asyncWrite = raf.writeFrom(data).then((_raf) {
     167             :           // Notify progress
     168           2 :           received += data.length;
     169             : 
     170             :           onReceiveProgress?.call(received, total);
     171             : 
     172             :           raf = _raf;
     173           0 :           if (cancelToken == null || !cancelToken.isCancelled) {
     174           1 :             subscription.resume();
     175             :           }
     176           1 :         }).catchError((err, StackTrace stackTrace) async {
     177             :           try {
     178           0 :             await subscription.cancel();
     179             :           } finally {
     180           0 :             completer.completeError(DioMixin.assureDioError(
     181             :               err,
     182           0 :               response.requestOptions,
     183             :             ));
     184             :           }
     185             :         });
     186             :       },
     187           1 :       onDone: () async {
     188             :         try {
     189           1 :           await asyncWrite;
     190             :           closed = true;
     191           2 :           await raf.close();
     192           1 :           completer.complete(response);
     193             :         } catch (e) {
     194           0 :           completer.completeError(DioMixin.assureDioError(
     195             :             e,
     196           0 :             response.requestOptions,
     197             :           ));
     198             :         }
     199             :       },
     200           0 :       onError: (e) async {
     201             :         try {
     202           0 :           await _closeAndDelete();
     203             :         } finally {
     204           0 :           completer.completeError(DioMixin.assureDioError(
     205             :             e,
     206           0 :             response.requestOptions,
     207             :           ));
     208             :         }
     209             :       },
     210             :       cancelOnError: true,
     211             :     );
     212             :     // ignore: unawaited_futures
     213           3 :     cancelToken?.whenCancel.then((_) async {
     214           2 :       await subscription.cancel();
     215           1 :       await _closeAndDelete();
     216             :     });
     217             : 
     218           3 :     if (response.requestOptions.receiveTimeout > 0) {
     219             :       future = future
     220           2 :           .timeout(Duration(
     221           2 :         milliseconds: response.requestOptions.receiveTimeout,
     222             :       ))
     223           2 :           .catchError((Object err) async {
     224           2 :         await subscription.cancel();
     225           1 :         await _closeAndDelete();
     226           1 :         if (err is TimeoutException) {
     227           1 :           throw DioError(
     228           1 :             requestOptions: response.requestOptions,
     229             :             error:
     230           3 :                 'Receiving data timeout[${response.requestOptions.receiveTimeout}ms]',
     231             :             type: DioErrorType.receiveTimeout,
     232             :           );
     233             :         } else {
     234             :           throw err;
     235             :         }
     236             :       });
     237             :     }
     238           1 :     return DioMixin.listenCancelForAsyncTask(cancelToken, future);
     239             :   }
     240             : 
     241             :   ///  Download the file and save it in local. The default http method is "GET",
     242             :   ///  you can custom it by [Options.method].
     243             :   ///
     244             :   ///  [uri]: The file url.
     245             :   ///
     246             :   ///  [savePath]: The path to save the downloading file later. it can be a String or
     247             :   ///  a callback:
     248             :   ///  1. A path with String type, eg "xs.jpg"
     249             :   ///  2. A callback `String Function(Headers)`; for example:
     250             :   ///  ```dart
     251             :   ///   await dio.downloadUri(uri,(Headers headers){
     252             :   ///        // Extra info: redirect counts
     253             :   ///        print(headers.value('redirects'));
     254             :   ///        // Extra info: real uri
     255             :   ///        print(headers.value('uri'));
     256             :   ///      ...
     257             :   ///      return "...";
     258             :   ///    });
     259             :   ///  ```
     260             :   ///
     261             :   ///  [onReceiveProgress]: The callback to listen downloading progress.
     262             :   ///  please refer to [ProgressCallback].
     263             :   ///
     264             :   ///  [lengthHeader] : The real size of original file (not compressed).
     265             :   ///  When file is compressed:
     266             :   ///  1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
     267             :   ///  2. If this value is not 'content-length', maybe a custom header indicates the original
     268             :   ///  file size , the `total` argument of `onProgress` will be this header value.
     269             :   ///
     270             :   ///  you can also disable the compression by specifying the 'accept-encoding' header value as '*'
     271             :   ///  to assure the value of `total` argument of `onProgress` is not -1. for example:
     272             :   ///
     273             :   ///     await dio.downloadUri(uri, "./example/flutter.svg",
     274             :   ///     options: Options(headers: {HttpHeaders.acceptEncodingHeader: "*"}),  // disable gzip
     275             :   ///     onProgress: (received, total) {
     276             :   ///       if (total != -1) {
     277             :   ///        print((received / total * 100).toStringAsFixed(0) + "%");
     278             :   ///       }
     279             :   ///     });
     280           1 :   @override
     281             :   Future<Response> downloadUri(
     282             :     Uri uri,
     283             :     savePath, {
     284             :     ProgressCallback? onReceiveProgress,
     285             :     CancelToken? cancelToken,
     286             :     bool deleteOnError = true,
     287             :     lengthHeader = Headers.contentLengthHeader,
     288             :     data,
     289             :     Options? options,
     290             :   }) {
     291           1 :     return download(
     292           1 :       uri.toString(),
     293             :       savePath,
     294             :       onReceiveProgress: onReceiveProgress,
     295             :       lengthHeader: lengthHeader,
     296             :       deleteOnError: deleteOnError,
     297             :       cancelToken: cancelToken,
     298             :       data: data,
     299             :       options: options,
     300             :     );
     301             :   }
     302             : }

Generated by: LCOV version 1.14