download method
- String path, {
- required String savePath,
- Map<
String, String> ? query, - Map<
String, String> ? headers, - TClientToken? token,
- OnCancelCallback? onCancelCallback,
- void onError(
- String message
- OnReceiveProgressCallback? onReceiveProgress,
- 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;
}