downloadWithProgress static method

Stream<int> downloadWithProgress({
  1. required String url,
  2. required String targetPath,
  3. String? token,
  4. int maxRetries = 10,
  5. CancelToken? cancelToken,
  6. bool? foreground,
})

Downloads a file with smart retry logic and HTTP-aware error handling

url - File URL (any server) targetPath - Local file path to save to token - Optional authorization token (e.g., HuggingFace, custom auth) maxRetries - Maximum number of retry attempts for transient errors (default: 10) cancelToken - Optional token for cancellation foreground - Android foreground service mode:

  • null (default): auto-detect based on file size (>500MB = foreground)
  • true: always use foreground (shows notification)
  • false: never use foreground

Note: Auth errors (401/403/404) fail after 1 attempt, regardless of maxRetries. Only network errors and server errors (5xx) will be retried up to maxRetries times. Returns a stream of progress percentages (0-100)

The stream will emit DownloadCancelledException if cancelled via cancelToken.

Implementation

static Stream<int> downloadWithProgress({
  required String url,
  required String targetPath,
  String? token,
  int maxRetries = 10,
  CancelToken? cancelToken,
  bool? foreground,
}) {
  final progress = StreamController<int>();
  StreamSubscription? currentListener;
  StreamSubscription? cancellationListener;
  String? currentTaskId; // ← ADD: Store task ID for cancellation

  // Listen for cancellation
  if (cancelToken != null) {
    cancellationListener = cancelToken.whenCancelled.asStream().listen((
      _,
    ) async {
      gemmaLog('🚫 Cancellation requested');

      // Cancel the actual download task
      if (currentTaskId != null) {
        gemmaLog('🚫 Cancelling task: $currentTaskId');
        try {
          await FileDownloader().cancelTaskWithId(
            currentTaskId!,
          ); // ← ADD: Actually cancel the task
        } catch (e) {
          gemmaLog('⚠️ Failed to cancel task: $e');
        }
      }

      if (!progress.isClosed) {
        progress.addError(
          DownloadCancelledException(
            cancelToken.cancelReason ?? 'Download cancelled',
            StackTrace.current,
          ),
        );
        progress.close();
      }
      currentListener?.cancel();
      cancellationListener?.cancel();
    });
  }

  // Configure FileDownloader and start download
  _ensureConfigured(foreground)
      .then((_) async {
        await _downloadWithSmartRetry(
          url: url,
          targetPath: targetPath,
          token: token,
          maxRetries: maxRetries,
          progress: progress,
          currentAttempt: 1,
          currentListener: currentListener,
          cancelToken: cancelToken,
          onListenerCreated: (listener) {
            currentListener = listener;
          },
          onTaskCreated: (taskId) {
            currentTaskId = taskId;
          },
        );
      })
      .whenComplete(() {
        // Clean up cancellation listener when download completes
        cancellationListener?.cancel();
      });

  return progress.stream;
}