upload method

Future<void> upload({
  1. dynamic onProgress(
    1. double,
    2. Duration
    )?,
  2. dynamic onStart(
    1. TusClient,
    2. Duration?
    )?,
  3. dynamic onComplete()?,
  4. required Uri uri,
  5. Map<String, String>? metadata = const {},
  6. Map<String, String>? headers = const {},
  7. bool measureUploadSpeed = false,
  8. bool preventDuplicates = true,
})

Start or resume an upload in chunks of maxChunkSize throwing ProtocolException on server error

Implementation

Future<void> upload({
  Function(double, Duration)? onProgress,
  Function(TusClient, Duration?)? onStart,
  Function()? onComplete,
  required Uri uri,
  Map<String, String>? metadata = const {},
  Map<String, String>? headers = const {},
  bool measureUploadSpeed = false,
  bool preventDuplicates = true,
}) async {
  _log('Starting upload process to $uri');
  _log(
      'Parameters: measureUploadSpeed=$measureUploadSpeed, preventDuplicates=$preventDuplicates');

  setUploadData(uri, headers, metadata);
  _log('Upload data set');

  // Check for duplicates if requested
  if (preventDuplicates) {
    _log('Checking for duplicate uploads');
    final (exists, canResume) = await checkExistingUpload();
    _log('Existing upload check: exists=$exists, canResume=$canResume');

    if (exists && !canResume) {
      _log('Upload exists but cannot be resumed');
      throw ProtocolException(
          'An upload with the same fingerprint exists but cannot be resumed. '
          'If you wish to force a new upload, set preventDuplicates to false.');
    }
  }

  _log('Checking if upload is resumable');
  final _isResumable = await isResumable();
  _log('Is resumable: $_isResumable');

  // Save the callbacks for possible resume.
  _onProgress = onProgress;
  _onComplete = onComplete;
  _onStart = onStart;
  _log('Callbacks saved');

  if (measureUploadSpeed) {
    _log('Measuring upload speed');
    await setUploadTestServers();
    await uploadSpeedTest();
  }

  if (!_isResumable) {
    _log('Upload not resumable, creating new upload');
    await createUpload();
  } else {
    _log('Upload is resumable, using existing upload');
  }

  // get offset from server
  _log('Getting current offset from server');
  _offset = await _getOffset();
  _log('Current offset: $_offset bytes');

  // Save the file size as an int in a variable to avoid having to call
  int totalBytes = _fileSize as int;
  _log('Total bytes to upload: $totalBytes');

  // File existence check for non-web platforms before starting upload
  if (!kIsWeb && _file.path.isNotEmpty) {
    _log('Checking file existence: ${_file.path}');
    try {
      final file = File(_file.path);
      if (!file.existsSync()) {
        _log('Error: File not found');
        throw Exception("Cannot find file ${_file.path.split('/').last}");
      }
      _log('File exists');
    } catch (e) {
      _log('Error accessing file: $e');
      throw Exception("Cannot access file ${_file.path.split('/').last}: $e");
    }
  }

  // We start a stopwatch to calculate the upload speed
  _log('Starting upload stopwatch');
  final uploadStopwatch = Stopwatch()..start();

  // start upload
  _log('Initializing HTTP client');
  final client = getHttpClient();

  if (onStart != null) {
    _log('Calling onStart callback');
    Duration? estimate;
    if (uploadSpeed != null) {
      final _workedUploadSpeed = uploadSpeed! * 1000000;
      _log('Calculated upload speed: $_workedUploadSpeed bytes/s');

      estimate = Duration(
        seconds: (totalBytes / _workedUploadSpeed).round(),
      );
      _log('Estimated upload time: ${estimate.inSeconds} seconds');
    }
    // The time remaining to finish the upload
    onStart(this, estimate);
  }

  _log('Starting upload loop');
  while (!_pauseUpload && _offset < totalBytes) {
    _log(
        'Upload chunk: offset=$_offset, chunk=${min(maxChunkSize, totalBytes - _offset)} bytes');

    // File existence check for non-web platforms before each chunk
    if (!kIsWeb && _file.path.isNotEmpty) {
      try {
        final file = File(_file.path);
        if (!file.existsSync()) {
          _log('Error: File no longer exists');
          throw Exception("Cannot find file ${_file.path.split('/').last}");
        }
      } catch (e) {
        _log('Error accessing file during chunk upload: $e');
        throw Exception(
            "Cannot access file ${_file.path.split('/').last}: $e");
      }
    }

    final uploadHeaders = Map<String, String>.from(headers ?? {})
      ..addAll({
        "Tus-Resumable": tusVersion,
        "Upload-Offset": "$_offset",
        "Content-Type": "application/offset+octet-stream"
      });
    _log('Upload headers: $uploadHeaders');

    await _performUpload(
      onComplete: onComplete,
      onProgress: onProgress,
      uploadHeaders: uploadHeaders,
      client: client,
      uploadStopwatch: uploadStopwatch,
      totalBytes: totalBytes,
    );
  }

  if (_pauseUpload) {
    _log('Upload paused at offset: $_offset / $totalBytes bytes');
  } else {
    _log('Upload loop completed');
  }
}