chunkedUpload method

  1. @override
Future<Response> chunkedUpload({
  1. required String path,
  2. required Map<String, dynamic> params,
  3. required String paramName,
  4. required String idParamName,
  5. required Map<String, String> headers,
  6. dynamic onProgress(
    1. UploadProgress
    )?,
})
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.bytes == null) {
    throw AppwriteException("File bytes must be provided for Flutter web");
  }

  int size = file.bytes!.length;

  late Response res;
  if (size <= chunkSize) {
    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) async {
    List<int> chunk = [];
    chunk = file.bytes!.getRange(start, end).toList();

    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 {
    while (nextChunk < chunks.length) {
      final chunk = chunks[nextChunk++];
      final chunkResponse = await uploadChunk(
        chunk['index']!,
        chunk['start']!,
        chunk['end']!,
        uploadId,
      );
      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);
    }
  }

  final concurrency = min(8, chunks.length);
  await Future.wait(List.generate(concurrency, (_) => uploadNext()));

  return finalResponse ?? lastResponse;
}