use anyhow::Result;
use crate::Installer;
pub trait ProgressSink: Send + Sync {
fn set_status(&self, status: &str);
fn set_progress(&self, fraction: f64);
fn log(&self, message: &str);
}
pub struct StderrProgressSink {
is_tty: bool,
state: std::sync::Mutex<StderrSinkState>,
}
#[derive(Default)]
struct StderrSinkState {
last_status: String,
last_fraction: f64,
progress_line_active: bool,
}
impl StderrProgressSink {
pub fn new() -> Self {
use std::io::IsTerminal;
Self {
is_tty: std::io::stderr().is_terminal(),
state: std::sync::Mutex::new(StderrSinkState::default()),
}
}
fn redraw(&self, state: &mut StderrSinkState) {
use std::io::Write;
if !self.is_tty {
return;
}
let pct = (state.last_fraction * 100.0).round() as u32;
let bar_width = 24usize;
let filled = ((state.last_fraction * bar_width as f64).round() as usize).min(bar_width);
let bar: String = "#".repeat(filled) + &"-".repeat(bar_width - filled);
let mut out = std::io::stderr().lock();
let _ = write!(out, "\r[{bar}] {pct:>3}% {}\x1b[K", state.last_status);
let _ = out.flush();
state.progress_line_active = true;
}
fn clear_progress_line(&self, state: &mut StderrSinkState) {
use std::io::Write;
if !self.is_tty || !state.progress_line_active {
return;
}
let mut out = std::io::stderr().lock();
let _ = write!(out, "\r\x1b[2K");
let _ = out.flush();
state.progress_line_active = false;
}
}
impl Default for StderrProgressSink {
fn default() -> Self {
Self::new()
}
}
impl Drop for StderrProgressSink {
fn drop(&mut self) {
use std::io::Write;
let mut state = self.state.lock().unwrap();
if self.is_tty && state.progress_line_active {
let mut out = std::io::stderr().lock();
let _ = writeln!(out);
let _ = out.flush();
state.progress_line_active = false;
}
}
}
impl ProgressSink for StderrProgressSink {
fn set_status(&self, status: &str) {
let mut state = self.state.lock().unwrap();
state.last_status = status.to_string();
if self.is_tty {
self.redraw(&mut state);
} else {
self.clear_progress_line(&mut state);
eprintln!("[*] {status}");
}
}
fn set_progress(&self, fraction: f64) {
let mut state = self.state.lock().unwrap();
state.last_fraction = fraction.clamp(0.0, 1.0);
if self.is_tty {
self.redraw(&mut state);
}
}
fn log(&self, message: &str) {
let mut state = self.state.lock().unwrap();
self.clear_progress_line(&mut state);
eprintln!(" {message}");
if self.is_tty {
self.redraw(&mut state);
}
}
}
pub(crate) struct ProgressState {
pub(crate) steps_done: f64,
pub(crate) step_range_start: f64,
pub(crate) step_range_end: f64,
}
impl Installer {
pub fn total_steps(&self) -> u64 {
self.components
.iter()
.filter(|c| c.selected)
.map(|c| c.progress_weight as u64)
.sum()
}
pub fn reset_progress(&mut self) {
let mut state = self.progress.lock().unwrap();
state.steps_done = 0.0;
state.step_range_start = 0.0;
state.step_range_end = 0.0;
}
pub fn begin_step(&self, status: &str, weight: u32) {
self.emit_status(&Some(status.to_string()));
let mut state = self.progress.lock().unwrap();
state.step_range_start = state.steps_done;
state.step_range_end = state.steps_done + weight as f64;
drop(state);
self.emit_progress();
}
pub fn set_step_progress(&self, fraction: f64) {
let f = fraction.clamp(0.0, 1.0);
let mut state = self.progress.lock().unwrap();
let span = state.step_range_end - state.step_range_start;
state.steps_done = state.step_range_start + f * span;
drop(state);
self.emit_progress();
}
pub fn end_step(&self) {
let mut state = self.progress.lock().unwrap();
state.steps_done = state.step_range_end;
state.step_range_start = state.step_range_end;
drop(state);
self.emit_progress();
}
pub fn step(&self, status: &str, weight: u32) {
self.begin_step(status, weight);
self.end_step();
}
pub(crate) fn emit_progress(&self) {
let Some(sink) = self.sink.as_ref() else {
return;
};
let state = self.progress.lock().unwrap();
let total = self.total_steps();
let fraction = if total == 0 {
0.0
} else {
(state.steps_done / total as f64).clamp(0.0, 1.0)
};
drop(state);
sink.set_progress(fraction);
}
pub(crate) fn run_weighted_step<F>(&self, weight: u32, f: F) -> Result<()>
where
F: FnOnce() -> Result<()>,
{
{
let mut state = self.progress.lock().unwrap();
state.step_range_start = state.steps_done;
state.step_range_end = state.steps_done + weight as f64;
}
self.emit_progress();
let result = f();
{
let mut state = self.progress.lock().unwrap();
state.steps_done = state.step_range_end;
state.step_range_start = state.step_range_end;
}
self.emit_progress();
result
}
}