downloadAndVerifyBinary function
Download and verify a binary with stall detection and retry logic.
Implementation
Future<void> downloadAndVerifyBinary({
required String binaryUrl,
required String expectedChecksum,
required String binaryPath,
String? authUsername,
String? authPassword,
}) async {
Exception? lastError;
for (int attempt = 1; attempt <= maxDownloadRetries; attempt++) {
final client = HttpClient();
Timer? stallTimer;
bool aborted = false;
void clearStallTimer() {
stallTimer?.cancel();
stallTimer = null;
}
void resetStallTimer() {
clearStallTimer();
stallTimer = Timer(Duration(milliseconds: getStallTimeoutMs()), () {
aborted = true;
client.close(force: true);
});
}
try {
resetStallTimer();
final request = await client.getUrl(Uri.parse(binaryUrl));
if (authUsername != null && authPassword != null) {
request.headers.set(
'Authorization',
'Basic ${base64Encode(utf8.encode('$authUsername:$authPassword'))}',
);
}
final response = await request.close().timeout(
const Duration(minutes: 5),
);
final chunks = <List<int>>[];
int totalBytes = 0;
await for (final chunk in response) {
if (aborted) throw StallTimeoutError();
resetStallTimer();
chunks.add(chunk);
totalBytes += chunk.length;
}
clearStallTimer();
// Combine chunks
final data = Uint8List(totalBytes);
int offset = 0;
for (final chunk in chunks) {
data.setRange(offset, offset + chunk.length, chunk);
offset += chunk.length;
}
// Verify checksum
final actualChecksum = sha256.convert(data).toString();
if (actualChecksum != expectedChecksum) {
throw Exception(
'Checksum mismatch: expected $expectedChecksum, got $actualChecksum',
);
}
// Write binary to disk
final file = File(binaryPath);
await file.writeAsBytes(data);
if (!Platform.isWindows) {
await Process.run('chmod', ['755', binaryPath]);
}
return; // Success
} catch (e) {
clearStallTimer();
if (aborted) {
lastError = StallTimeoutError();
} else {
lastError = e is Exception ? e : Exception(e.toString());
}
// Only retry on stall timeouts
if (aborted && attempt < maxDownloadRetries) {
_logDebug(
'Download stalled on attempt $attempt/$maxDownloadRetries, retrying...',
);
await Future.delayed(const Duration(seconds: 1));
continue;
}
throw lastError;
} finally {
client.close();
}
}
throw lastError ?? Exception('Download failed after all retries');
}