start method
Starts downloading the file for the given task
, sending progress and status
updates back to the main isolate via sendPort
.
Implementation
Future<void> start(DownloadTask task, SendPort sendPort) async {
try {
HttpClientRequest request =
await (client ?? HttpClient()).getUrl(task.uri);
bool fileAppend = false;
String range = '';
// Set up HTTP Range header for resuming or partial downloads.
if (task.downloadedBytes > 0 || task.startRange > 0) {
int startRange = task.downloadedBytes + task.startRange;
range = 'bytes=$startRange-';
}
if (task.endRange != null) {
if (range.isEmpty) {
range = 'bytes=0-';
} else {
fileAppend = true;
}
range += '${task.endRange}';
}
// Add custom headers except 'host' and 'range'.
if (task.headers != null) {
task.headers!.forEach((key, value) {
logIsolate('task.headers: ${task.headers}');
String keyLower = key.toLowerCase();
if (keyLower == 'host' || keyLower == 'range') return;
request.headers.set(key, value);
});
}
request.headers.add('Range', range);
logIsolate('[DownloadIsolate] START ${task.uri} \n'
'headers: {\n ${request.headers} } \n');
final response = await request.close();
logIsolate('[DownloadIsolate] status code: ${response.statusCode} '
'${task.uri} range: $range');
// Handle HTTP errors and retry logic.
if (response.statusCode < 200 || response.statusCode >= 300) {
if (response.statusCode == 416) retryTimes = 0;
if (retryTimes > 0) {
logIsolate('[DownloadIsolate] retry $retryTimes: '
'${task.uri} range: $range');
retryTimes--;
start(task, sendPort);
return;
}
logIsolate('[DownloadIsolate] failed: ${task.uri} range: $range');
task.status = DownloadStatus.FAILED;
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
return;
}
// Check if contentLength is valid
if (response.contentLength == -1) {
logIsolate('[DownloadIsolate] failed to get the total file size.');
}
// Calculate the total file size
final totalBytes = task.downloadedBytes + response.contentLength;
task.totalBytes = response.contentLength == -1 ? 0 : totalBytes;
// Record the time of the last update progress
DateTime lastUpdateTime = DateTime.now();
// Creating a temporary storage area
List<int> buffer = [];
File saveFile = File(task.isolateSavePath);
// Read data from the response stream.
await for (var data in response) {
// Check if it has been cancelled or suspended
if (taskStatus[task.id] == DownloadStatus.PAUSED) {
await _writeToFile(saveFile, buffer, fileAppend);
task.status = DownloadStatus.PAUSED;
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
logIsolate("[DownloadIsolate] PAUSED ${task.toString()} ");
return;
}
if (taskStatus[task.id] == DownloadStatus.CANCELLED) {
await saveFile.delete();
task.status = DownloadStatus.CANCELLED;
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
logIsolate("[DownloadIsolate] CANCELLED ${task.toString()} ");
return;
}
task.downloadedBytes += data.length;
buffer.addAll(data);
// Calculate the interval between the current time and the last update time
final currentTime = DateTime.now();
final timeDiff = currentTime.difference(lastUpdateTime).inMilliseconds;
// If the time interval exceeds the specified minimum update interval,
// or the download is complete, then update progress
if (task.status == DownloadStatus.DOWNLOADING &&
timeDiff >= MIN_PROGRESS_UPDATE_INTERVAL) {
if (task.totalBytes > 0) {
task.progress = task.downloadedBytes / task.totalBytes;
}
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
lastUpdateTime = currentTime;
logIsolate("[DownloadIsolate] DOWNLOADING ${task.toString()}");
}
}
// Download complete, update status and write to file.
task.data = buffer;
task.status = DownloadStatus.COMPLETED;
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
logIsolate("[DownloadIsolate] COMPLETED ${task.toString()}");
await _writeToFile(saveFile, buffer, fileAppend);
task.file = saveFile;
task.status = DownloadStatus.FINISHED;
sendPort.send(DownloadIsolateMsg(IsolateMsgType.task, task));
logIsolate("[DownloadIsolate] FINISHED ${task.toString()}");
task.reset();
} catch (e) {
logIsolate('[DownloadIsolate] Download error: $e');
} finally {
logIsolate('[DownloadIsolate] close ${task.url}');
}
}