From 71768f71d7321220c22931da9efc143ba5ae85e7 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Sat, 3 Oct 2026 00:03:26 +0200 Subject: [PATCH] refactor(kernel): report kernel download progress through the sink get_default_kernel emits Progress events for the APK transfer instead of drawing a bar to stderr from inside the library. --- crates/ecr-cli/src/main.rs | 2 +- crates/ecr/src/kernel.rs | 41 +++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/ecr-cli/src/main.rs b/crates/ecr-cli/src/main.rs index c5e3013..f37e925 100644 --- a/crates/ecr-cli/src/main.rs +++ b/crates/ecr-cli/src/main.rs @@ -63,7 +63,7 @@ fn main() -> Result<()> { } None => { veprintln!("QEMU mode: downloading default kernel..."); - kernel::get_default_kernel(cache.dir(), &arch)? + kernel::get_default_kernel(cache.dir(), &arch, &ecr::NoProgress)? } }; diff --git a/crates/ecr/src/kernel.rs b/crates/ecr/src/kernel.rs index cc244ea..6dc0ebd 100644 --- a/crates/ecr/src/kernel.rs +++ b/crates/ecr/src/kernel.rs @@ -17,9 +17,9 @@ //! kernel image gzipped, and QEMU's riscv `-kernel` loader understands //! only ELF, uImage and raw images. +use crate::progress::{Progress, Unit}; use crate::veprintln; use anyhow::{anyhow, Context, Result}; -use indicatif::{ProgressBar, ProgressStyle}; use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -237,7 +237,12 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String } /// Download and extract the kernel from an Alpine package repository -fn download_alpine_kernel(branch: &str, arch: &str, dest: &Path) -> Result<()> { +fn download_alpine_kernel( + branch: &str, + arch: &str, + dest: &Path, + progress: &dyn Progress, +) -> Result<()> { let (package, version) = find_kernel_package(branch, arch)?; veprintln!("Found {} version: {}", package, version); @@ -252,7 +257,7 @@ fn download_alpine_kernel(branch: &str, arch: &str, dest: &Path) -> Result<()> { // Use async download via the existing download module pattern let rt = tokio::runtime::Runtime::new().context("Failed to create Tokio runtime")?; - rt.block_on(download_kernel_async(&url, dest))?; + rt.block_on(download_kernel_async(&url, dest, progress))?; // Extract the kernel image from the APK, then store it decompressed. // Remove the ~45MB APK whatever the outcome — don't leave it behind @@ -264,7 +269,7 @@ fn download_alpine_kernel(branch: &str, arch: &str, dest: &Path) -> Result<()> { result } -async fn download_kernel_async(url: &str, dest: &Path) -> Result<()> { +async fn download_kernel_async(url: &str, dest: &Path, progress: &dyn Progress) -> Result<()> { use futures_util::StreamExt; let client = reqwest::Client::builder() .timeout(Duration::from_secs(300)) @@ -286,13 +291,10 @@ async fn download_kernel_async(url: &str, dest: &Path) -> Result<()> { let total_size = response.content_length().unwrap_or(0); - // Setup progress bar - let pb = ProgressBar::new(total_size); - pb.set_style( - ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})") - .unwrap() - .progress_chars("#>-"), + progress.begin( + "Downloading kernel", + Unit::Bytes, + (total_size > 0).then_some(total_size), ); let temp_apk = dest.with_extension("apk.partial"); @@ -300,7 +302,6 @@ async fn download_kernel_async(url: &str, dest: &Path) -> Result<()> { .await .context("Failed to create temp APK file")?; - let mut downloaded: u64 = 0; let mut stream = response.bytes_stream(); while let Some(chunk) = stream.next().await { @@ -308,15 +309,14 @@ async fn download_kernel_async(url: &str, dest: &Path) -> Result<()> { tokio::io::AsyncWriteExt::write_all(&mut file, &chunk) .await .context("Failed to write chunk")?; - downloaded += chunk.len() as u64; - pb.set_position(downloaded); + progress.advance(chunk.len() as u64); } tokio::io::AsyncWriteExt::flush(&mut file) .await .context("Failed to flush file")?; - pb.finish_with_message("Download complete"); + progress.finish(); // Rename to final name let final_apk = dest.with_extension("apk"); @@ -392,8 +392,13 @@ fn decompress_kernel_if_gzipped(path: &Path) -> Result<()> { } /// Get the path to the cached default kernel for the given architecture. -/// Downloads and caches it if not present. -pub fn get_default_kernel(cache_dir: &Path, arch: &str) -> Result { +/// Downloads and caches it if not present, reporting download progress +/// through `progress`. +pub fn get_default_kernel( + cache_dir: &Path, + arch: &str, + progress: &dyn Progress, +) -> Result { let alpine_arch = alpine_kernel_arch(arch); // Cache filename includes architecture @@ -414,7 +419,7 @@ pub fn get_default_kernel(cache_dir: &Path, arch: &str) -> Result { veprintln!("Using Alpine branch: {}", branch); // Download and extract the kernel - download_alpine_kernel(&branch, alpine_arch, &kernel_path)?; + download_alpine_kernel(&branch, alpine_arch, &kernel_path, progress)?; Ok(kernel_path) }