download static method

Stream<DownloadProgress> download(
  1. Uri uri,
  2. String savePath, {
  3. Map<String, String>? headers,
})

Downloads uri to savePath and reports progress as a stream.

Each event contains the received bytes, the total size (when the server sends content-length), the remaining bytes and the percent. The last event has isDone == true. Errors (including non-2xx responses) are emitted as FinchHttpException; a partial file is deleted on failure.

Implementation

static Stream<DownloadProgress> download(
  Uri uri,
  String savePath, {
  Map<String, String>? headers,
}) async* {
  final client = HttpClient();
  final file = File(savePath);
  IOSink? sink;
  var received = 0;
  var finished = false;

  try {
    final request = await client.getUrl(uri);
    headers?.forEach(request.headers.set);
    final response = await request.close();

    if (response.statusCode < 200 || response.statusCode >= 300) {
      await response.drain<void>();
      throw FinchHttpException(
        'Failed to download: ${response.reasonPhrase}',
        statusCode: response.statusCode,
      );
    }

    final total = response.contentLength >= 0 ? response.contentLength : null;
    await file.parent.create(recursive: true);
    sink = file.openWrite();

    yield DownloadProgress(received: 0, total: total);

    await for (final chunk in response) {
      sink.add(chunk);
      received += chunk.length;
      yield DownloadProgress(received: received, total: total);
    }

    await sink.flush();
    await sink.close();
    sink = null;
    finished = true;
    yield DownloadProgress(
      received: received,
      total: total,
      isDone: true,
    );
  } on FinchHttpException {
    rethrow;
  } catch (e) {
    throw FinchHttpException('Failed to download: $e');
  } finally {
    client.close(force: true);
    try {
      await sink?.close();
    } catch (_) {}
    if (!finished && await file.exists()) {
      await file.delete();
    }
  }
}