diff --git a/dio/lib/src/dio.dart b/dio/lib/src/dio.dart index 321398745..727a9d5dc 100644 --- a/dio/lib/src/dio.dart +++ b/dio/lib/src/dio.dart @@ -209,6 +209,9 @@ abstract class Dio { /// [deleteOnError] whether delete the file when error occurs. /// The default value is [true]. /// + /// [fileAccessMode] + /// {@macro dio.options.FileAccessMode} + /// /// [lengthHeader] : The real size of original file (not compressed). /// When file is compressed: /// 1. If this value is 'content-length', the `total` argument of @@ -242,6 +245,7 @@ abstract class Dio { Map? queryParameters, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode fileAccessMode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, @@ -254,6 +258,7 @@ abstract class Dio { ProgressCallback? onReceiveProgress, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode mode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, @@ -266,6 +271,7 @@ abstract class Dio { deleteOnError: deleteOnError, cancelToken: cancelToken, data: data, + fileAccessMode: mode, options: options, ); } diff --git a/dio/lib/src/dio/dio_for_native.dart b/dio/lib/src/dio/dio_for_native.dart index 1e016935d..6e8f2b95a 100644 --- a/dio/lib/src/dio/dio_for_native.dart +++ b/dio/lib/src/dio/dio_for_native.dart @@ -32,6 +32,7 @@ class DioForNative with DioMixin implements Dio { Map? queryParameters, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode fileAccessMode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, @@ -95,7 +96,11 @@ class DioForNative with DioMixin implements Dio { // Shouldn't call file.writeAsBytesSync(list, flush: flush), // because it can write all bytes by once. Consider that the file is // a very big size (up to 1 Gigabytes), it will be expensive in memory. - RandomAccessFile raf = file.openSync(mode: FileMode.write); + RandomAccessFile raf = file.openSync( + mode: fileAccessMode == FileAccessMode.write + ? FileMode.write + : FileMode.append, + ); // Create a Completer to notify the success/error state. final completer = Completer(); diff --git a/dio/lib/src/dio_mixin.dart b/dio/lib/src/dio_mixin.dart index dbc1bd96b..4bec4f1e0 100644 --- a/dio/lib/src/dio_mixin.dart +++ b/dio/lib/src/dio_mixin.dart @@ -286,6 +286,7 @@ abstract class DioMixin implements Dio { ProgressCallback? onReceiveProgress, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode mode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, @@ -310,6 +311,7 @@ abstract class DioMixin implements Dio { Map? queryParameters, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode fileAccessMode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options, diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index 1f1e58983..1e3842fc0 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -753,3 +753,17 @@ class _RequestConfig { ResponseDecoder? responseDecoder; late ListFormat listFormat; } + +/// {@template dio.options.FileAccessMode} +/// The file access mode when downloading a file, corresponds to a subset of +/// dart:io::[FileMode]. +/// {@endtemplate} +enum FileAccessMode { + /// Mode for opening a file for reading and writing. The file is overwritten + /// if it already exists. The file is created if it does not already exist. + write, + + /// Mode for opening a file for reading and writing to the end of it. + /// The file is created if it does not already exist. + append, +} diff --git a/dio_test/lib/src/test/download_tests.dart b/dio_test/lib/src/test/download_tests.dart index f81e30a9c..6041f241b 100644 --- a/dio_test/lib/src/test/download_tests.dart +++ b/dio_test/lib/src/test/download_tests.dart @@ -380,6 +380,55 @@ void downloadTests( completes, ); }); + + test('append bytes to previous download', () async { + final cancelToken = CancelToken(); + final path = p.join(tmp.path, 'download_3.txt'); + final requestedBytes = 1024 * 1024 * 10; + var recievedBytes1 = 0; + await expectLater( + dio.download( + '/bytes/$requestedBytes', + path, + cancelToken: cancelToken, + onReceiveProgress: (c, t) { + if (c > 5000) { + recievedBytes1 = c; + cancelToken.cancel(); + } + }, + deleteOnError: false, + ), + throwsDioException( + DioExceptionType.cancel, + stackTraceContains: 'test/download_tests.dart', + ), + ); + + final cancelToken2 = CancelToken(); + var recievedBytes2 = 0; + expectLater( + dio.download( + '/bytes/$requestedBytes', + path, + cancelToken: cancelToken, + onReceiveProgress: (c, t) { + recievedBytes2 = c; + if (c > 5000) { + cancelToken2.cancel(); + } + }, + deleteOnError: false, + fileAccessMode: FileAccessMode.append, + ), + throwsDioException( + DioExceptionType.cancel, + stackTraceContains: 'test/download_tests.dart', + ), + ); + await Future.delayed(const Duration(milliseconds: 100), () {}); + expect(File(path).lengthSync(), recievedBytes1 + recievedBytes2); + }); }, testOn: 'vm', ); diff --git a/plugins/web_adapter/lib/src/dio_impl.dart b/plugins/web_adapter/lib/src/dio_impl.dart index a9b777f6f..4b797e625 100644 --- a/plugins/web_adapter/lib/src/dio_impl.dart +++ b/plugins/web_adapter/lib/src/dio_impl.dart @@ -22,6 +22,7 @@ class DioForBrowser with DioMixin implements Dio { Map? queryParameters, CancelToken? cancelToken, bool deleteOnError = true, + FileAccessMode fileAccessMode = FileAccessMode.write, String lengthHeader = Headers.contentLengthHeader, Object? data, Options? options,