work on download progress bars

This commit is contained in:
2025-11-12 21:54:45 +04:00
parent 4dfefb0602
commit c33ef71e55
4 changed files with 129 additions and 51 deletions

90
src/download_pb.rs Normal file
View File

@@ -0,0 +1,90 @@
use std::{cell::RefCell, rc::Rc};
use indicatif::{MultiProgress, ProgressBar};
use crate::multibar::{BarSegment, MultiBar};
#[derive(Debug, Clone)]
pub struct DownloadBar {
pub bar: ProgressBar,
pub name: String,
pub download_expected: Rc<RefCell<u64>>,
pub download_done: Rc<RefCell<u64>>,
pub extract_expected: Rc<RefCell<u64>>,
pub extract_done: Rc<RefCell<u64>>,
}
impl DownloadBar {
pub fn new(bar: ProgressBar, name: String, width: u16) -> Self {
bar.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap());
let new_self = Self {
name,
bar,
download_expected: Rc::new(RefCell::new(0)),
download_done: Rc::new(RefCell::new(0)),
extract_expected: Rc::new(RefCell::new(0)),
extract_done: Rc::new(RefCell::new(0)),
};
new_self.update(width);
new_self
}
pub fn update(&self, width: u16) {
let download_done = (*self.download_done.borrow());
let download_expected = (*self.download_expected.borrow());
let extract_done = *self.extract_done.borrow();
let extract_expected = *self.extract_expected.borrow();
let name = pad_string(&self.name, 20);
if download_expected == 0 || extract_expected == 0 {
self.bar.set_message(format!("Download {}", name));
return;
}
let total_expected = download_expected + extract_expected;
let total_done = download_done + extract_done;
let dl_percent = ((download_done as f64 / download_expected as f64) * 100.0) as u64;
let ex_percent = ((extract_done as f64 / extract_expected as f64) * 100.0) as u64;
let msg = match width {
0..50 => {
format!(
"{}: {}/{}",
self.name,
total_done,
if total_expected == 0 {
"-".to_string()
} else {
total_expected.to_string()
}
)
}
_ => {
let bar_dl = MultiBar([
BarSegment::Dynamic("=", download_done),
BarSegment::Dynamic(" ", download_expected.saturating_sub(download_done)),
])
.scale((width / 6) as u64);
let bar_ex = MultiBar([
BarSegment::Dynamic("=", extract_done),
BarSegment::Dynamic(" ", extract_expected.saturating_sub(extract_done)),
])
.scale((width / 6) as u64);
format!("Download {name} [{bar_dl}] {dl_percent:3}% [{bar_ex}] {ex_percent:3}%",)
}
};
self.bar.set_message(msg);
self.bar.tick();
}
}
fn pad_string(s: &str, width: usize) -> String {
if s.len() >= width {
s.to_string()
} else {
let mut padded = s.to_string();
padded.push_str(&" ".repeat(width - s.len()));
padded
}
}

View File

@@ -1,4 +1,6 @@
pub mod action; pub mod action;
pub mod action_raw; pub mod action_raw;
pub mod download_pb;
pub mod multibar;
pub mod nix_path; pub mod nix_path;
pub mod state_manager; pub mod state_manager;

View File

@@ -17,6 +17,7 @@ static LAZY: LazyLock<i32> = LazyLock::new(|| {
pub mod action; pub mod action;
pub mod action_raw; pub mod action_raw;
pub mod download_pb;
pub mod multibar; pub mod multibar;
pub mod nix_path; pub mod nix_path;
pub mod state_manager; pub mod state_manager;
@@ -71,7 +72,7 @@ impl TermLike for TextTerm {
fn main() -> Result<(), color_eyre::Report> { fn main() -> Result<(), color_eyre::Report> {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
println!("Lazy value: {}", *LAZY); // println!("Lazy value: {}", *LAZY);
// let pb = ProgressBar::new(100); // let pb = ProgressBar::new(100);
// pb.set_draw_target(indicatif::ProgressDrawTarget::term_like(Box::new(TextTerm))); // pb.set_draw_target(indicatif::ProgressDrawTarget::term_like(Box::new(TextTerm)));
// //
@@ -86,25 +87,27 @@ fn main() -> Result<(), color_eyre::Report> {
// sleep(Duration::from_millis(50)); // sleep(Duration::from_millis(50));
// } // }
// pb.finish_and_clear(); // pb.finish_and_clear();
let term = Term::stdout(); // let empty = style("-").blue().to_string();
let empty = style("-").blue().to_string(); // let bar = MultiBar([
let bar = MultiBar([ // BarSegment::Dynamic("=", 10),
BarSegment::Dynamic("=", 10), // BarSegment::Static(">"),
BarSegment::Static(">"), // BarSegment::Dynamic(empty.as_str(), 3),
BarSegment::Dynamic(empty.as_str(), 3), // BarSegment::Dynamic(" ", 50),
BarSegment::Dynamic(" ", 50), // ])
]) // .scale(u64::from(term.size().1 - 2));
.scale(u64::from(term.size().1 - 2)); // println!("[{}]", bar);
println!("[{}]", bar); // return Ok(());
return Ok(());
let lines = std::fs::read_to_string("build.log")?; let lines = std::fs::read_to_string("build.log")?;
// let lines = lines.lines().take(0).collect::<Vec<_>>().join("\n"); // let lines = lines.lines().take(0).collect::<Vec<_>>().join("\n");
let term = Term::stdout();
let mut state = State { let mut state = State {
multi_progress: MultiProgress::new(), multi_progress: MultiProgress::new(),
manager: StateManager::new(), manager: StateManager::new(),
separator: None, separator: None,
width: term.size().1,
current_width: term.size().1,
term,
}; };
// let progress = MultiProgress::new(); // let progress = MultiProgress::new();
@@ -224,8 +227,9 @@ fn main() -> Result<(), color_eyre::Report> {
if expected == 0 { if expected == 0 {
continue; continue;
}; };
if let Some(child) = state.manager.get_mut(*id) { if let Some(mut child) = state.manager.take(*id) {
child.progress(done, expected); child.progress(&mut state, done, expected);
state.manager.insert_parent(*id, child);
} }
// sleep(Duration::from_millis(1)); // sleep(Duration::from_millis(1));
// if let Some(build_state) = state.manager.get_mut(*id) // if let Some(build_state) = state.manager.get_mut(*id)

View File

@@ -1,13 +1,14 @@
use console::style; use console::{Term, style};
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
use std::{borrow::Cow, collections::HashMap, io}; use std::{borrow::Cow, collections::HashMap, io};
use crate::{action::StartFields, nix_path}; use crate::{action::StartFields, download_pb::DownloadBar, nix_path};
pub struct State<'a> { pub struct State<'a> {
pub multi_progress: MultiProgress, pub multi_progress: MultiProgress,
pub manager: StateManager<'a>, pub manager: StateManager<'a>,
pub separator: Option<ProgressBar>, pub separator: Option<ProgressBar>,
pub term: Term,
pub width: u16, pub width: u16,
pub current_width: u16, pub current_width: u16,
} }
@@ -56,12 +57,11 @@ pub enum BuildEnumState<'a> {
}, },
SubstituteCopy { SubstituteCopy {
path: Cow<'a, str>, path: Cow<'a, str>,
bar: ProgressBar, bar: DownloadBar,
}, },
SubstituteFetch { SubstituteFetch {
path: Cow<'a, str>, path: Cow<'a, str>,
extract_bar: ProgressBar, bar: DownloadBar,
download_bar: ProgressBar,
}, },
Query { Query {
path: Cow<'a, str>, path: Cow<'a, str>,
@@ -105,32 +105,13 @@ impl<'a> BuildState<'a> {
(BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => { (BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => {
let name = nix_path::extract_package_name(&path).unwrap_or("unknown".to_string()); let name = nix_path::extract_package_name(&path).unwrap_or("unknown".to_string());
let bar = state.add_pb(ProgressBar::new(100)); let bar = state.add_pb(ProgressBar::new(100));
bar.set_message(format!("Copying substitute for {}", name)); let bar = DownloadBar::new(bar, name, state.width);
bar.set_style(
ProgressStyle::default_bar()
.template("{prefix}{msg} [{bar:40.cyan/blue}] {pos:>3}%")
.unwrap()
.progress_chars("=> "),
);
bar.tick();
BuildEnumState::SubstituteCopy { path, bar } BuildEnumState::SubstituteCopy { path, bar }
} }
(BuildEnumState::SubstituteCopy { path, bar }, StartFields::FileTransfer { .. }) => { (BuildEnumState::SubstituteCopy { path, bar }, StartFields::FileTransfer { .. }) => {
let download_bar = state.add_pb_after(bar, ProgressBar::new(100));
let name = nix_path::extract_package_name(path).unwrap_or("unknown".to_string());
download_bar.set_message(format!("Fetching substitute for {}", name));
download_bar.set_style(
ProgressStyle::default_bar()
.template("{prefix}{msg} [{bar:40.cyan/blue}] {pos:>3}%")
.unwrap()
.progress_chars("=> "),
);
download_bar.set_prefix("=== ");
download_bar.tick();
BuildEnumState::SubstituteFetch { BuildEnumState::SubstituteFetch {
path: path.clone(), path: path.clone(),
extract_bar: bar.clone(), bar: bar.clone(),
download_bar,
} }
} }
(_, StartFields::Unknown) => BuildEnumState::Unknown, (_, StartFields::Unknown) => BuildEnumState::Unknown,
@@ -153,19 +134,17 @@ impl<'a> BuildState<'a> {
_ => {} _ => {}
} }
} }
pub fn progress(&mut self, done: u64, expected: u64) { pub fn progress(&mut self, state: &mut State<'a>, done: u64, expected: u64) {
match &self.state { match &self.state {
BuildEnumState::SubstituteFetch { download_bar, .. } => { BuildEnumState::SubstituteFetch { bar, .. } => {
let percentage = (done * 100 / expected) as u64; bar.download_expected.replace(expected);
if percentage > download_bar.position() { bar.download_done.replace(done);
download_bar.set_position(percentage); bar.update(state.width);
};
} }
BuildEnumState::SubstituteCopy { path, bar } => { BuildEnumState::SubstituteCopy { path, bar } => {
let percentage = (done * 100 / expected) as u64; bar.extract_expected.replace(expected);
if percentage > bar.position() { bar.extract_done.replace(done);
bar.set_position(percentage); bar.update(state.width);
};
} }
_ => {} _ => {}
} }
@@ -194,6 +173,9 @@ impl<'a> StateManager<'a> {
pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> { pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> {
self.states.entry(id).or_insert_with(BuildState::new) self.states.entry(id).or_insert_with(BuildState::new)
} }
pub fn take(&mut self, id: u64) -> Option<BuildState<'a>> {
self.states.remove(&id)
}
pub fn remove(&mut self, id: u64) { pub fn remove(&mut self, id: u64) {
if let Some(state) = self.states.get(&id) { if let Some(state) = self.states.get(&id) {
if let Some(pb) = &state.progress_bar { if let Some(pb) = &state.progress_bar {