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

Generated by: LCOV version 1.14