downloadAndVerifyBinary function

Future<void> downloadAndVerifyBinary({
  1. required String binaryUrl,
  2. required String expectedChecksum,
  3. required String binaryPath,
  4. String? authUsername,
  5. String? authPassword,
})

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');
}