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_raw;
pub mod download_pb;
pub mod multibar;
pub mod nix_path;
pub mod state_manager;

View File

@@ -17,6 +17,7 @@ static LAZY: LazyLock<i32> = LazyLock::new(|| {
pub mod action;
pub mod action_raw;
pub mod download_pb;
pub mod multibar;
pub mod nix_path;
pub mod state_manager;
@@ -71,7 +72,7 @@ impl TermLike for TextTerm {
fn main() -> Result<(), color_eyre::Report> {
color_eyre::install().unwrap();
println!("Lazy value: {}", *LAZY);
// println!("Lazy value: {}", *LAZY);
// let pb = ProgressBar::new(100);
// 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));
// }
// pb.finish_and_clear();
let term = Term::stdout();
let empty = style("-").blue().to_string();
let bar = MultiBar([
BarSegment::Dynamic("=", 10),
BarSegment::Static(">"),
BarSegment::Dynamic(empty.as_str(), 3),
BarSegment::Dynamic(" ", 50),
])
.scale(u64::from(term.size().1 - 2));
println!("[{}]", bar);
return Ok(());
// let empty = style("-").blue().to_string();
// let bar = MultiBar([
// BarSegment::Dynamic("=", 10),
// BarSegment::Static(">"),
// BarSegment::Dynamic(empty.as_str(), 3),
// BarSegment::Dynamic(" ", 50),
// ])
// .scale(u64::from(term.size().1 - 2));
// println!("[{}]", bar);
// return Ok(());
let lines = std::fs::read_to_string("build.log")?;
// let lines = lines.lines().take(0).collect::<Vec<_>>().join("\n");
let term = Term::stdout();
let mut state = State {
multi_progress: MultiProgress::new(),
manager: StateManager::new(),
separator: None,
width: term.size().1,
current_width: term.size().1,
term,
};
// let progress = MultiProgress::new();
@@ -224,8 +227,9 @@ fn main() -> Result<(), color_eyre::Report> {
if expected == 0 {
continue;
};
if let Some(child) = state.manager.get_mut(*id) {
child.progress(done, expected);
if let Some(mut child) = state.manager.take(*id) {
child.progress(&mut state, done, expected);
state.manager.insert_parent(*id, child);
}
// sleep(Duration::from_millis(1));
// 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 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 multi_progress: MultiProgress,
pub manager: StateManager<'a>,
pub separator: Option<ProgressBar>,
pub term: Term,
pub width: u16,
pub current_width: u16,
}
@@ -56,12 +57,11 @@ pub enum BuildEnumState<'a> {
},
SubstituteCopy {
path: Cow<'a, str>,
bar: ProgressBar,
bar: DownloadBar,
},
SubstituteFetch {
path: Cow<'a, str>,
extract_bar: ProgressBar,
download_bar: ProgressBar,
bar: DownloadBar,
},
Query {
path: Cow<'a, str>,
@@ -105,32 +105,13 @@ impl<'a> BuildState<'a> {
(BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => {
let name = nix_path::extract_package_name(&path).unwrap_or("unknown".to_string());
let bar = state.add_pb(ProgressBar::new(100));
bar.set_message(format!("Copying substitute for {}", name));
bar.set_style(
ProgressStyle::default_bar()
.template("{prefix}{msg} [{bar:40.cyan/blue}] {pos:>3}%")
.unwrap()
.progress_chars("=> "),
);
bar.tick();
let bar = DownloadBar::new(bar, name, state.width);
BuildEnumState::SubstituteCopy { path, bar }
}
(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 {
path: path.clone(),
extract_bar: bar.clone(),
download_bar,
bar: bar.clone(),
}
}
(_, 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 {
BuildEnumState::SubstituteFetch { download_bar, .. } => {
let percentage = (done * 100 / expected) as u64;
if percentage > download_bar.position() {
download_bar.set_position(percentage);
};
BuildEnumState::SubstituteFetch { bar, .. } => {
bar.download_expected.replace(expected);
bar.download_done.replace(done);
bar.update(state.width);
}
BuildEnumState::SubstituteCopy { path, bar } => {
let percentage = (done * 100 / expected) as u64;
if percentage > bar.position() {
bar.set_position(percentage);
};
bar.extract_expected.replace(expected);
bar.extract_done.replace(done);
bar.update(state.width);
}
_ => {}
}
@@ -194,6 +173,9 @@ impl<'a> StateManager<'a> {
pub fn get_or_insert(&mut self, id: u64) -> &mut BuildState<'a> {
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) {
if let Some(state) = self.states.get(&id) {
if let Some(pb) = &state.progress_bar {