Skip to content

Commit

Permalink
Introducing FileAccessMode for Dio.download (#2281)
Browse files Browse the repository at this point in the history
Add the ability to change the `FileMode` of the download without altering the default value, which is `FileMode.write`, to make resuming downloads easier to implement by introducing `FileAccessMode` and mapping it to `FileMode` in `DioForNative`.

---

Signed-off-by: shehab mohame <[email protected]>
Co-authored-by: Alex Li <[email protected]>
  • Loading branch information
shehabmohamed0 and AlexV525 authored Jan 23, 2025
1 parent ffa1cc6 commit 779ccf0
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 2 deletions.
1 change: 1 addition & 0 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
- Update comments and strings with `MultipartFile`.
- Removes redundant warnings when composing request options on Web.
- Fixes boundary inconsistency in `FormData.clone()`.
- Support `FileAccessMode` in `Dio.download` and `Dio.downloadUri` to change download file opening mode

## 5.7.0

Expand Down
6 changes: 6 additions & 0 deletions dio/lib/src/dio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -242,6 +245,7 @@ abstract class Dio {
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand All @@ -254,6 +258,7 @@ abstract class Dio {
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand All @@ -266,6 +271,7 @@ abstract class Dio {
deleteOnError: deleteOnError,
cancelToken: cancelToken,
data: data,
fileAccessMode: fileAccessMode,
options: options,
);
}
Expand Down
7 changes: 6 additions & 1 deletion dio/lib/src/dio/dio_for_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class DioForNative with DioMixin implements Dio {
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand Down Expand Up @@ -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<Response>();
Expand Down
3 changes: 3 additions & 0 deletions dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ abstract class DioMixin implements Dio {
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand All @@ -298,6 +299,7 @@ abstract class DioMixin implements Dio {
deleteOnError: deleteOnError,
cancelToken: cancelToken,
data: data,
fileAccessMode: fileAccessMode,
options: options,
);
}
Expand All @@ -310,6 +312,7 @@ abstract class DioMixin implements Dio {
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand Down
14 changes: 14 additions & 0 deletions dio/lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
49 changes: 49 additions & 0 deletions dio_test/lib/src/test/download_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
int 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();
int 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',
);
Expand Down
2 changes: 1 addition & 1 deletion plugins/web_adapter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Unreleased

*None.*
- Support `FileAccessMode` in `Dio.download` and `Dio.downloadUri` to change download file opening mode

## 2.0.0

Expand Down
1 change: 1 addition & 0 deletions plugins/web_adapter/lib/src/dio_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DioForBrowser with DioMixin implements Dio {
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
FileAccessMode fileAccessMode = FileAccessMode.write,
String lengthHeader = Headers.contentLengthHeader,
Object? data,
Options? options,
Expand Down

0 comments on commit 779ccf0

Please sign in to comment.