chunkedUpload method
Future<Response>
chunkedUpload({
- required String path,
- required Map<
String, dynamic> params, - required String paramName,
- required String idParamName,
- required Map<
String, String> headers, - dynamic onProgress()?,
override
Upload a file in chunks.
Implementation
@override
Future<Response> chunkedUpload({
required String path,
required Map<String, dynamic> params,
required String paramName,
required String idParamName,
required Map<String, String> headers,
Function(UploadProgress)? onProgress,
}) async {
InputFile file = params[paramName];
if (file.path == null && file.bytes == null) {
throw AppwriteException("File path or bytes must be provided");
}
int size = 0;
if (file.bytes != null) {
size = file.bytes!.length;
}
File? iofile;
if (file.path != null) {
iofile = File(file.path!);
size = await iofile.length();
}
late Response res;
if (size <= chunkSize) {
if (file.path != null) {
params[paramName] = await http.MultipartFile.fromPath(
paramName,
file.path!,
filename: file.filename,
);
} else {
params[paramName] = http.MultipartFile.fromBytes(
paramName,
file.bytes!,
filename: file.filename,
);
}
return call(
HttpMethod.post,
path: path,
params: params,
headers: headers,
);
}
var offset = 0;
String? uploadId;
if (idParamName.isNotEmpty) {
//make a request to check if a file already exists
try {
res = await call(
HttpMethod.get,
path: '$path/${params[idParamName]}',
headers: headers,
);
final int chunksUploaded = res.data['chunksUploaded'] as int;
offset = chunksUploaded * chunkSize;
uploadId = res.data['\$id'] ?? params[idParamName]?.toString();
} on AppwriteException catch (_) {}
}
if (offset >= size) {
return res;
}
final totalChunks = (size / chunkSize).ceil();
Future<Response> uploadChunk(int index, int start, int end, String? id,
[RandomAccessFile? raf]) async {
List<int> chunk = [];
if (file.bytes != null) {
chunk = file.bytes!.getRange(start, end).toList();
} else {
if (raf != null) {
await raf.setPosition(start);
chunk = await raf.read(end - start);
} else {
final chunkFile = await iofile!.open(mode: FileMode.read);
try {
await chunkFile.setPosition(start);
chunk = await chunkFile.read(end - start);
} finally {
await chunkFile.close();
}
}
}
final chunkParams = Map<String, dynamic>.from(params);
chunkParams[paramName] = http.MultipartFile.fromBytes(
paramName,
chunk,
filename: file.filename,
);
final chunkHeaders = Map<String, String>.from(headers);
if (id != null && id.isNotEmpty) {
chunkHeaders['x-appwrite-id'] = id;
}
chunkHeaders['content-range'] = 'bytes $start-${end - 1}/$size';
return call(
HttpMethod.post,
path: path,
headers: chunkHeaders,
params: chunkParams,
);
}
final firstStart = offset;
final firstEnd = min(firstStart + chunkSize, size);
final firstIndex = firstStart ~/ chunkSize;
res = await uploadChunk(firstIndex, firstStart, firstEnd, uploadId);
uploadId = res.data['\$id'] ?? uploadId;
var completedChunks = firstIndex + 1;
var uploadedBytes = firstEnd;
var lastResponse = res;
Response? finalResponse;
bool isUploadComplete(Response response) {
final chunksUploaded = response.data['chunksUploaded'];
final chunksTotal = response.data['chunksTotal'] ?? totalChunks;
return chunksUploaded is num &&
chunksTotal is num &&
chunksUploaded >= chunksTotal;
}
final progress = UploadProgress(
$id: uploadId ?? '',
progress: min(uploadedBytes, size) / size * 100,
sizeUploaded: min(uploadedBytes, size),
chunksTotal: totalChunks,
chunksUploaded: completedChunks,
);
onProgress?.call(progress);
final chunks = <Map<String, int>>[];
for (var start = firstEnd; start < size; start += chunkSize) {
final end = min(start + chunkSize, size);
chunks.add({
'index': start ~/ chunkSize,
'start': start,
'end': end,
});
}
var nextChunk = 0;
Future<void> uploadNext() async {
final raf =
file.bytes == null ? await iofile!.open(mode: FileMode.read) : null;
try {
while (nextChunk < chunks.length) {
final chunk = chunks[nextChunk++];
final chunkResponse = await uploadChunk(
chunk['index']!,
chunk['start']!,
chunk['end']!,
uploadId,
raf,
);
completedChunks++;
uploadedBytes += chunk['end']! - chunk['start']!;
lastResponse = chunkResponse;
if (isUploadComplete(chunkResponse)) {
finalResponse = chunkResponse;
}
final progress = UploadProgress(
$id: uploadId ?? '',
progress: min(uploadedBytes, size) / size * 100,
sizeUploaded: min(uploadedBytes, size),
chunksTotal: totalChunks,
chunksUploaded: completedChunks,
);
onProgress?.call(progress);
}
} finally {
await raf?.close();
}
}
final concurrency = min(8, chunks.length);
await Future.wait(List.generate(concurrency, (_) => uploadNext()));
return finalResponse ?? lastResponse;
}