-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathchunked_uploader.dart
More file actions
137 lines (119 loc) · 4 KB
/
chunked_uploader.dart
File metadata and controls
137 lines (119 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart';
class ChunkedUploader {
final Dio _dio;
ChunkedUploader(this._dio);
Future<Response?> upload({
required String filePath,
required String path,
required String contentType,
required Map<String, dynamic> data,
required Map<String, String> params,
CancelToken? cancelToken,
required int maxChunkSize,
required Function(double) onUploadProgress,
String method = 'POST',
}) =>
UploadRequest(_dio,
filePath: filePath,
path: path,
contentType: contentType,
params: params,
method: method,
data: data,
cancelToken: cancelToken,
maxChunkSize: maxChunkSize,
onUploadProgress: onUploadProgress)
.upload();
static Future<String> generateMd5(Stream<List<int>> stream) async {
return (await md5.bind(stream).first).toString();
}
}
class UploadRequest {
final Dio dio;
final String filePath, fileName, path;
final Map<String, String> params;
final String method;
final String contentType;
final Map<String, dynamic> data;
final CancelToken? cancelToken;
final File _file;
final Function(double) onUploadProgress;
late int _maxChunkSize;
late int _fileSize;
UploadRequest(this.dio,
{required this.filePath,
required this.params,
required this.contentType,
required this.path,
required this.method,
required this.data,
this.cancelToken,
required this.onUploadProgress,
required int maxChunkSize})
: _file = File(filePath),
fileName = basename(filePath) {
_fileSize = _file.lengthSync();
_maxChunkSize = min(_fileSize, maxChunkSize);
}
Future<Response?> upload() async {
Response? finalResponse;
String originalSum;
List<String> chunkSums = [];
originalSum = await ChunkedUploader.generateMd5(_file.openRead());
for (int i = 0; i < _chunksCount; i++) {
final start = _getChunkStart(i);
final end = _getChunkEnd(i);
final chunkStream = _getChunkStream(start, end);
chunkSums.add(await ChunkedUploader.generateMd5(chunkStream));
}
for (int i = 0; i < _chunksCount; i++) {
final start = _getChunkStart(i);
final end = _getChunkEnd(i);
final chunkStream = _getChunkStream(start, end);
final formData = FormData.fromMap({
'chunks': _chunksCount,
'chunk': i,
'chunk_sum': chunkSums[i],
'original_sum': originalSum,
'file': MultipartFile.fromStream(
() => chunkStream,
end - start,
filename: fileName,
),
...data
});
Response response = await dio.request(
path,
data: formData,
queryParameters: params,
cancelToken: cancelToken,
options: Options(
method: method,
contentType: contentType,
headers: _getHeaders(start, end),
),
onSendProgress: (current, total) => _updateProgress(i, current, total),
);
if (response.data != null && json.decode(response.data)?['result']?['id'] != null) {
finalResponse = response;
}
}
return finalResponse;
}
Stream<List<int>> _getChunkStream(int start, int end) => _file.openRead(start, end);
_updateProgress(int chunkIndex, int chunkCurrent, int chunkTotal) {
int totalUploadedSize = (chunkIndex * _maxChunkSize) + chunkCurrent;
double totalUploadProgress = totalUploadedSize / _fileSize;
onUploadProgress.call(totalUploadProgress);
}
int _getChunkStart(int chunkIndex) => chunkIndex * _maxChunkSize;
int _getChunkEnd(int chunkIndex) => min((chunkIndex + 1) * _maxChunkSize, _fileSize);
Map<String, dynamic> _getHeaders(int start, int end) => {'Content-Range': 'bytes $start-${end - 1}/$_fileSize'};
int get _chunksCount => (_fileSize / _maxChunkSize).ceil();
}