breakPointDownload method
void
breakPointDownload({
- required String savePath,
- ProgressCallback? onReceiveProgress,
- Success? success,
- Failure? failure,
- Completed? completed,
- dynamic cancelCallback()?,
断点下载
Implementation
void breakPointDownload({
required String savePath,
adapter.ProgressCallback? onReceiveProgress,
Success? success,
Failure? failure,
Completed? completed,
Function()? cancelCallback,
}) async {
if (!(await _checkNetWork())) {
return;
}
final file = File(savePath);
try {
final url = _buildFinalUrl();
final payload = _resolveRequestPayload();
if (!file.parent.existsSync()) {
file.parent.createSync(recursive: true);
}
final requestedStart = file.existsSync() ? file.lengthSync() : 0;
final headers = _buildHeaders();
if (requestedStart > 0) {
headers[HttpHeaders.rangeHeader] = 'bytes=$requestedStart-';
}
// 构建 AdapterRequest,内部会自动使用 stream 响应类型
final adapterRequest = _buildAdapterRequest(
url: url,
queryParams: payload.queryParams,
data: payload.body,
headers: headers,
contentType: payload.contentType,
responseType: rxnet_plus.ResponseType.stream,
);
// 使用适配器发送请求
final adapter = _requireAdapter();
final response = await adapter.request(adapterRequest);
onResponse?.call(response);
final stream = _extractByteStream(response.data);
if (stream == null) {
throw NetworkException(
'Breakpoint download requires a byte stream response', null);
}
final contentRange = response.getHeader(HttpHeaders.contentRangeHeader);
final isResumed = requestedStart > 0 &&
response.statusCode == HttpStatus.partialContent &&
_parseContentRangeStart(contentRange) == requestedStart;
var downloaded = isResumed ? requestedStart : 0;
final total = _resolveBreakpointDownloadTotal(
response,
isResumed: isResumed,
downloaded: downloaded,
);
final raf = file.openSync(
mode: isResumed ? FileMode.append : FileMode.write,
);
try {
await for (final chunk in stream) {
raf.writeFromSync(chunk);
downloaded += chunk.length;
onReceiveProgress?.call(
downloaded,
total > 0 ? total : downloaded,
);
}
} catch (error) {
if (_isCancelledStreamError(error)) {
cancelCallback?.call();
return;
}
rethrow;
} finally {
await raf.close();
}
if (total > 0 && downloaded > total) {
onReceiveProgress?.call(total, total);
}
success?.call(file, SourcesType.net);
} on AdapterException catch (error) {
if (error.type == AdapterExceptionType.cancel) {
cancelCallback?.call();
} else if (error.type == AdapterExceptionType.response &&
error.statusCode == HttpStatus.requestedRangeNotSatisfiable) {
final localLength = file.existsSync() ? file.lengthSync() : 0;
final total = _parseContentRangeTotal(
error.response?.getHeader(HttpHeaders.contentRangeHeader),
);
if (total != null && total == localLength) {
onReceiveProgress?.call(total, total);
success?.call(file, SourcesType.net);
} else {
failure?.call(error);
}
} else {
failure?.call(error);
}
} catch (e) {
failure?.call(e);
} finally {
completed?.call();
}
}