use std::io::{self, IsTerminal as _, Write as _};
use std::time::Instant;
use arcsec_catalogue::fetch::DownloadEvent;
use arcsec_catalogue::human_bytes as human;
#[derive(Default)]
pub struct Progress {
state: Option<(bool, Instant)>,
}
impl Progress {
pub fn event(&mut self, label: &str, event: DownloadEvent) {
match event {
DownloadEvent::Started { .. } => {
self.state = Some((io::stderr().is_terminal(), Instant::now()));
}
DownloadEvent::Progress { written, total } => {
let Some((tty, last_report)) = &mut self.state else {
return;
};
let (cr, tail) = ends(*tty);
let interval_ms = if *tty { 300 } else { 15_000 };
if last_report.elapsed().as_millis() > interval_ms {
match total {
Some(t) if t > 0 => eprint!(
"{cr} {label}: {} / {} ({:.0}%){tail}",
human(written),
human(t),
written as f64 / t as f64 * 100.0,
),
_ => eprint!("{cr} {label}: {}{tail}", human(written)),
}
let _ = io::stderr().flush();
*last_report = Instant::now();
}
}
DownloadEvent::Finished { written } => {
let tty = self.state.is_some_and(|(tty, _)| tty);
let (cr, _) = ends(tty);
eprintln!("{cr} {label}: {} downloaded ", human(written));
}
DownloadEvent::AlreadyComplete { bytes } => {
eprintln!(" {label}: already downloaded ({})", human(bytes));
}
}
}
}
const fn ends(tty: bool) -> (&'static str, &'static str) {
if tty { ("\r", " ") } else { ("", "\n") }
}