-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
multipart_file.dart
182 lines (163 loc) · 6.03 KB
/
multipart_file.dart
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import 'dart:convert' show utf8;
import 'dart:typed_data' show Uint8List;
import 'package:http_parser/http_parser.dart' show MediaType;
import 'multipart_file/io_multipart_file.dart'
if (dart.library.js_interop) 'multipart_file/browser_multipart_file.dart'
if (dart.library.html) 'multipart_file/browser_multipart_file.dart';
import 'utils.dart';
/// The type (alias) for specifying the content-type of the `MultipartFile`.
typedef DioMediaType = MediaType;
/// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to
/// correspond to a physical file.
///
/// MultipartFile is based on stream, and a stream can be read only once,
/// so the same MultipartFile can't be read multiple times.
class MultipartFile {
/// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length
/// of the file in bytes must be known in advance. If it's not, read the data
/// from the stream and use [MultipartFile.fromBytes] instead.
///
/// [contentType] currently defaults to `application/octet-stream`, but in the
/// future may be inferred from [filename].
@Deprecated(
'MultipartFile.clone() will not work when the stream is provided, use the MultipartFile.fromStream instead.'
'This will be removed in 6.0.0',
)
MultipartFile(
Stream<List<int>> stream,
this.length, {
this.filename,
DioMediaType? contentType,
Map<String, List<String>>? headers,
}) : _dataBuilder = (() => stream),
headers = caseInsensitiveKeyMap(headers),
contentType = contentType ?? MediaType('application', 'octet-stream');
/// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length
/// of the file in bytes must be known in advance. If it's not, read the data
/// from the stream and use [MultipartFile.fromBytes] instead.
///
/// [contentType] currently defaults to `application/octet-stream`, but in the
/// future may be inferred from [filename].
MultipartFile.fromStream(
Stream<List<int>> Function() data,
this.length, {
this.filename,
DioMediaType? contentType,
Map<String, List<String>>? headers,
}) : _dataBuilder = data,
headers = caseInsensitiveKeyMap(headers),
contentType = contentType ?? MediaType('application', 'octet-stream');
/// Creates a new [MultipartFile] from a byte array.
///
/// [contentType] currently defaults to `application/octet-stream`, but in the
/// future may be inferred from [filename].
factory MultipartFile.fromBytes(
List<int> value, {
String? filename,
DioMediaType? contentType,
final Map<String, List<String>>? headers,
}) {
return MultipartFile.fromStream(
() => Stream.fromIterable([value]),
value.length,
filename: filename,
contentType: contentType,
headers: headers,
);
}
/// Creates a new [MultipartFile] from a string.
///
/// The encoding to use when translating [value] into bytes is taken from
/// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8.
/// [contentType] currently defaults to `text/plain; charset=utf-8`, but in
/// the future may be inferred from [filename].
factory MultipartFile.fromString(
String value, {
String? filename,
DioMediaType? contentType,
final Map<String, List<String>>? headers,
}) {
contentType ??= MediaType('text', 'plain');
final encoding = encodingForCharset(
contentType.parameters['charset'],
utf8,
);
contentType = contentType.change(parameters: {'charset': encoding.name});
return MultipartFile.fromBytes(
encoding.encode(value),
filename: filename,
contentType: contentType,
headers: headers,
);
}
/// The size of the file in bytes. This must be known in advance, even if this
/// file is created from a [ByteStream].
final int length;
/// The basename of the file. May be null.
final String? filename;
/// The additional headers the file has. May be null.
final Map<String, List<String>>? headers;
/// The content-type of the file. Defaults to `application/octet-stream`.
final DioMediaType? contentType;
/// The stream builder that will emit the file's contents for every call.
final Stream<List<int>> Function() _dataBuilder;
/// Whether [finalize] has been called.
bool get isFinalized => _isFinalized;
/// Creates a new [MultipartFile] from a path to a file on disk.
///
/// [filename] defaults to the basename of [filePath]. [contentType] currently
/// defaults to `application/octet-stream`, but in the future may be inferred
/// from [filename].
///
/// Throws an [UnsupportedError] if `dart:io` isn't supported in this
/// environment.
static Future<MultipartFile> fromFile(
String filePath, {
String? filename,
DioMediaType? contentType,
final Map<String, List<String>>? headers,
}) =>
multipartFileFromPath(
filePath,
filename: filename,
contentType: contentType,
headers: headers,
);
static MultipartFile fromFileSync(
String filePath, {
String? filename,
DioMediaType? contentType,
final Map<String, List<String>>? headers,
}) =>
multipartFileFromPathSync(
filePath,
filename: filename,
contentType: contentType,
headers: headers,
);
bool _isFinalized = false;
Stream<Uint8List> finalize() {
if (isFinalized) {
throw StateError(
'The MultipartFile has already been finalized. '
'This typically means you are using '
'the same MultipartFile in repeated requests.',
);
}
_isFinalized = true;
return _dataBuilder()
.map((e) => e is Uint8List ? e : Uint8List.fromList(e));
}
/// Clone MultipartFile, returning a new instance of the same object.
/// This is useful if your request failed and you wish to retry it,
/// such as an unauthorized exception can be solved by refreshing the token.
MultipartFile clone() {
return MultipartFile.fromStream(
_dataBuilder,
length,
filename: filename,
contentType: contentType,
headers: headers,
);
}
}