downloadPatchAssets method

  1. @override
Future<bool> downloadPatchAssets({
  1. required UpdateManifest manifest,
  2. required void onProgress(
    1. double progress
    ),
})
override

Downloads patch binary assets with onProgress reporting.

Implementation

@override
Future<bool> downloadPatchAssets({
  required UpdateManifest manifest,
  required void Function(double progress) onProgress,
}) async {
  if (manifest.downloadUrl == null || manifest.downloadUrl!.isEmpty) {
    throw StateError('Cannot download patch: manifest.downloadUrl is empty.');
  }

  final uri = Uri.parse(manifest.downloadUrl!);
  logger.info('BloomHttpUpdateAdapter: Downloading patch "${manifest.id}" from $uri');

  try {
    final request = http.Request('GET', uri);
    request.headers.addAll(defaultHeaders);

    final response = await httpClient.send(request);
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw HttpException('Download failed with HTTP status ${response.statusCode}', uri: uri);
    }

    final contentLength = response.contentLength ?? 0;
    final bytesBuilder = BytesBuilder(copy: false);
    var receivedBytes = 0;

    await for (final chunk in response.stream) {
      bytesBuilder.add(chunk);
      receivedBytes += chunk.length;
      if (contentLength > 0) {
        onProgress(receivedBytes / contentLength);
      } else {
        onProgress(0.5);
      }
    }

    final downloadedBytes = bytesBuilder.takeBytes();
    onProgress(1.0);

    // Verify SHA-256 integrity hash if provided in manifest
    if (manifest.assetHash != null && manifest.assetHash!.isNotEmpty) {
      final computedSha = sha256.convert(downloadedBytes).toString();
      if (computedSha.toLowerCase() != manifest.assetHash!.toLowerCase()) {
        throw StateError('Patch asset cryptographic integrity check failed: expected "${manifest.assetHash}", got "$computedSha"');
      }
      logger.debug('BloomHttpUpdateAdapter: Cryptographic asset hash verified: $computedSha');
    }

    // Stage patch file to disk
    if (!stagingDir.existsSync()) {
      stagingDir.createSync(recursive: true);
    }

    final patchFile = File(p.join(stagingDir.path, '${manifest.id}.patch'));
    patchFile.writeAsBytesSync(downloadedBytes);

    logger.info('BloomHttpUpdateAdapter: Staged patch "${manifest.id}" at ${patchFile.path} (${downloadedBytes.length} bytes)');
    return true;
  } catch (e, st) {
    logger.error('BloomHttpUpdateAdapter: Patch download error: $e', e, st);
    rethrow;
  }
}