multipart method

Future<String> multipart(
  1. SharedFile file, {
  2. bool? storeMode,
  3. ProgressListener? onProgress,
  4. CancelToken? cancelToken,
  5. int? maxConcurrentChunkRequests,
})

Make upload to /multipart endpoint maxConcurrentChunkRequests maximum concurrent requests cancelToken make cancelable request

Implementation

Future<String> multipart(
  SharedFile file, {
  bool? storeMode,
  ProgressListener? onProgress,
  CancelToken? cancelToken,
  int? maxConcurrentChunkRequests,
}) async {
  maxConcurrentChunkRequests ??= options.multipartMaxConcurrentChunkRequests;

  final filename = file.name;
  final filesize = await file.length();
  final mimeType = file.mimeType;

  assert(filesize > _kRecomendedMaxFilesizeForBaseUpload,
      'Minimum file size to use with Multipart Uploads is 10MB');

  final completer = Completer<String>();

  final startTransaction = createMultipartRequest(
      'POST', buildUri('$uploadUrl/multipart/start/'), false)
    ..fields.addAll({
      'UPLOADCARE_PUB_KEY': publicKey,
      'UPLOADCARE_STORE': resolveStoreModeParam(storeMode),
      'filename': filename,
      'size': filesize.toString(),
      'content_type': mimeType,
      if (options.useSignedUploads) ..._signUpload(),
    });

  if (cancelToken != null) {
    cancelToken.onCancel = _completeWithError(
      completer: completer,
      action: () => startTransaction.cancel(),
      cancelMessage: cancelToken.cancelMessage,
    );
  }

  // ignore: unawaited_futures
  resolveStreamedResponse(startTransaction.send()).then((map) {
    final urls = (map['parts'] as List).cast<String>();
    final uuid = map['uuid'] as String;
    final inProgressActions = <UcRequest>[];

    ProgressEntity progress = ProgressEntity(0, filesize);

    if (onProgress != null) onProgress(progress);

    return Future.wait(List.generate(urls.length, (index) {
      final url = urls[index];
      final offset = index * _kChunkSize;
      final diff = filesize - offset;
      final bytesToRead = _kChunkSize < diff ? _kChunkSize : diff;

      return Future.value(() {
        if (cancelToken != null && cancelToken.isCanceled) {
          return Future.value(null);
        }

        return file
            .openRead(offset, offset + bytesToRead)
            .toList()
            .then((bytesList) => bytesList.expand((list) => list).toList())
            .then((bytes) => createRequest('PUT', buildUri(url), false)
              ..bodyBytes = bytes
              ..headers.addAll({
                'Content-Type': mimeType,
              }))
            .then((request) {
          inProgressActions.add(request);

          return resolveStreamedResponseStatusCode(request.send())
              .then((response) {
            inProgressActions.remove(request);
            if (onProgress != null) {
              onProgress(progress = progress.copyWith(
                uploaded: progress.uploaded + bytesToRead,
              ));
            }
            return response;
          });
        });
      });
    })).then((actions) {
      if (cancelToken != null) {
        cancelToken.onCancel = _completeWithError(
          completer: completer,
          action: () => inProgressActions.forEach(
            (request) => request.cancel(),
          ),
          cancelMessage: cancelToken.cancelMessage,
        );
      }
      return ConcurrentRunner<Response?>(maxConcurrentChunkRequests!, actions)
          .run();
    }).then((_) {
      final finishTransaction = createMultipartRequest(
          'POST', buildUri('$uploadUrl/multipart/complete/'), false)
        ..fields.addAll({
          'UPLOADCARE_PUB_KEY': publicKey,
          'uuid': uuid,
          if (options.useSignedUploads) ..._signUpload(),
        });

      if (!completer.isCompleted) {
        completer.complete(
          resolveStreamedResponse(finishTransaction.send()).then((_) => uuid),
        );
      }
    });
  }).catchError((e) {
    if (!completer.isCompleted) {
      completer.completeError(e);
    }
  });

  return completer.future;
}