downloadModel method

Future<void> downloadModel(
  1. String modelId, {
  2. bool allowMetered = false,
  3. void onProgress(
    1. double progress
    )?,
})

Download + install the model. Idempotent: skips if the file already exists and (when applicable) the SHA-256 verifies. Reports progress 0..1 via onProgress when the server returns a Content-Length.

Implementation

Future<void> downloadModel(
  String modelId, {
  bool allowMetered = false,
  void Function(double progress)? onProgress,
}) async {
  final spec = _modelSpecs[modelId];
  if (spec == null) {
    throw StateError('Unknown local model: "$modelId".');
  }
  final destPath = await _modelPath(spec);
  final destFile = File(destPath);
  if (await destFile.exists()) {
    if (spec.sha256 == null || await _verifySha256(destFile, spec.sha256!)) {
      dev.log('Model "$modelId" already installed', name: 'LocalLlm');
      return;
    }
    dev.log(
      'Model "$modelId" exists but failed SHA-256 check — re-downloading',
      name: 'LocalLlm',
    );
    await destFile.delete();
  }
  final partialPath = '$destPath.partial';
  final partialFile = File(partialPath);
  if (await partialFile.exists()) await partialFile.delete();
  await partialFile.parent.create(recursive: true);

  dev.log('Downloading $modelId from ${spec.url}', name: 'LocalLlm');

  await _dio.download(
    spec.url,
    partialPath,
    onReceiveProgress: (received, total) {
      if (total > 0 && onProgress != null) {
        onProgress(received / total);
      }
    },
    options: Options(
      responseType: ResponseType.stream,
      followRedirects: true,
    ),
  );

  if (spec.sha256 != null &&
      !await _verifySha256(partialFile, spec.sha256!)) {
    await partialFile.delete();
    throw StateError(
      'SHA-256 mismatch for "$modelId" — download corrupted, please retry.',
    );
  }

  await partialFile.rename(destPath);
  dev.log('Installed $modelId at $destPath', name: 'LocalLlm');
}