downloadWithProgress static method

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

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 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,
}) {
  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 {
      debugPrint('🚫 Cancellation requested');

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

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

  _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;  // ← ADD: Store task ID when created
    },
  ).whenComplete(() {
    // Clean up cancellation listener when download completes
    cancellationListener?.cancel();
  });

  return progress.stream;
}