download method

Future<File> download(
  1. String path, {
  2. required String savePath,
  3. Map<String, String>? query,
  4. Map<String, String>? headers,
  5. TClientToken? token,
  6. OnCancelCallback? onCancelCallback,
  7. void onError(
    1. String message
    )?,
  8. OnReceiveProgressCallback? onReceiveProgress,
  9. OnReceiveProgressSpeedCallback? onReceiveProgressSpeed,
})

File download with progress

supported partial download [206]

Implementation

Future<File> download(
  String path, {
  required String savePath,
  Map<String, String>? query,
  Map<String, String>? headers,
  TClientToken? token,
  OnCancelCallback? onCancelCallback,
  void Function(String message)? onError,
  OnReceiveProgressCallback? onReceiveProgress,
  OnReceiveProgressSpeedCallback? onReceiveProgressSpeed,
}) async {
  final file = File(savePath);
  int downloadedLength = file.existsSync() ? await file.length() : 0;

  try {
    final uri = Uri.parse(
      '${options.baseUrl}$path',
    ).replace(queryParameters: query);
    // timeout
    final request = await ioClient.getUrl(uri).timeout(options.sendTimeout);

    // Default + custom headers
    final allHeaders = {...options.headers, ...?headers};
    allHeaders.forEach((key, value) => request.headers.set(key, value));

    if (downloadedLength > 0) {
      // Range header ထည့် → offset ကနေစပြီး တောင်း
      request.headers.set('Range', 'bytes=$downloadedLength-');
    }

    final response = await request.close().timeout(options.receiveTimeout);
    // progress
    int received = 0;
    int total = response.contentLength;
    final startTime = DateTime.now();
    // header
    final contentRange = response.headers.value(
      HttpHeaders.contentRangeHeader,
    );
    if (contentRange != null) {
      // Content-Range: bytes 1000-9999/50000
      final match = RegExp(r'bytes \d+-\d+/(\d+)').firstMatch(contentRange);
      if (match != null) {
        total = int.parse(match.group(1)!);
      }
    }

    /// partial download
    if (response.statusCode == 206) {
      // Server support
      TClientLogger.instance.showLog(
        "Resume with Range OK",
        tag: 'downloadResume',
      );
      received = downloadedLength;
      final raf = file.openSync(mode: FileMode.append);

      await response.forEach((chunk) {
        // cancel token
        if (token?.isCanceled ?? false) {
          raf.close();
          if (token!.isCancelFileDelete) {
            file.deleteSync(); // Delete partial file
          }
          onCancelCallback?.call(token.onCancelMessage);
          throw Exception(token.onCancelMessage);
        }
        // write
        raf.writeFromSync(chunk);
        received += chunk.length;
        // progress
        if (onReceiveProgressSpeed != null && total > 0) {
          final elapsed = DateTime.now().difference(startTime);
          final elapsedSec = elapsed.inMilliseconds / 1000.0;
          final speed = elapsedSec > 0
              ? received / elapsedSec
              : 0.0; // bytes per second
          final eta = speed > 0
              ? Duration(seconds: ((total - received) / speed).round())
              : null;
          onReceiveProgressSpeed.call(received, total, speed, eta);
        }

        if (onReceiveProgress != null && total > 0) {
          onReceiveProgress(received, total);
        }
      });
      await raf.close();
    }
    // Server not support → overwrite trick
    else if (response.statusCode == 200) {
      // Server က file တစ်ခုလုံးပေးတာ

      final raf = file.openSync(mode: FileMode.write);

      await response.forEach((chunk) {
        // cancel token
        if (token?.isCanceled ?? false) {
          raf.close();
          if (token!.isCancelFileDelete) {
            file.deleteSync(); // Delete partial file
          }
          onCancelCallback?.call(token.onCancelMessage);
          throw Exception(token.onCancelMessage);
        }
        // write file
        raf.writeFromSync(chunk);
        received += chunk.length;

        // progress
        if (onReceiveProgressSpeed != null && total > 0) {
          final elapsed = DateTime.now().difference(startTime);
          final elapsedSec = elapsed.inMilliseconds / 1000.0;
          final speed = elapsedSec > 0
              ? received / elapsedSec
              : 0.0; // bytes per second
          final eta = speed > 0
              ? Duration(seconds: ((total - received) / speed).round())
              : null;
          onReceiveProgressSpeed.call(received, total, speed, eta);
        }

        if (onReceiveProgress != null && total > 0) {
          onReceiveProgress(received, total);
        }
      });

      await raf.close();
    }
  } catch (e) {
    TClientLogger.instance.showLog(e.toString(), tag: 'downloadResume');
    onError?.call(e.toString());
  }
  return file;
}