This commit is contained in:
2025-11-15 07:51:32 +00:00
parent 69de65ee89
commit 01743c3c75
6 changed files with 24 additions and 512 deletions

View File

@@ -1,6 +1,6 @@
pub const DOWNLOAD_STYLE: owo_colors::Style = owo_colors::Style::new().blue().bold();
pub const EXTRACT_STYLE: owo_colors::Style = owo_colors::Style::new().green().bold();
use std::{borrow::Cow, fmt::Display, rc::Rc};
use std::{borrow::Cow, fmt::Display};
use indicatif::HumanBytes;
use owo_colors::Style;

View File

@@ -1,139 +0,0 @@
use std::{cell::RefCell, rc::Rc, sync::LazyLock, time::Instant};
use console::style;
use indicatif::{HumanBytes, MultiProgress, ProgressBar};
use crate::{
estimator::Estimator,
multibar::{BarSegment, MultiBar},
pad_string,
};
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| style("#").blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| style("#").green().bold().to_string());
const INFO_WIDTH: usize = 12;
#[derive(Debug, Clone)]
pub struct DownloadBar {
pub download_estimator: Rc<RefCell<Estimator>>,
pub extract_estimator: Rc<RefCell<Estimator>>,
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>>,
pub started_at: Instant,
}
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_estimator: Rc::new(RefCell::new(Estimator::new(Instant::now()))),
extract_estimator: Rc::new(RefCell::new(Estimator::new(Instant::now()))),
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)),
started_at: Instant::now(),
};
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();
self.download_estimator
.borrow_mut()
.record(download_done, Instant::now());
self.extract_estimator
.borrow_mut()
.record(extract_done, Instant::now());
let name = pad_string(&self.name, (width / 4) as usize);
let status = format!("Download {} |", style(name).magenta().bold());
let status_len = (width / 4) as usize + 10;
if download_expected == 0 || extract_expected == 0 {
let text = pad_string("Pending...", width as usize / 3);
self.bar.set_message(format!("{status}"));
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 download_per_sec = HumanBytes(
self.download_estimator
.borrow()
.steps_per_second(Instant::now()) as u64,
)
.to_string();
let download_done_human = HumanBytes(download_done).to_string();
let download_done_human = style(pad_string(&download_done_human, INFO_WIDTH))
.blue()
.bold()
.to_string();
let extract_done_human = HumanBytes(extract_done).to_string();
let extract_done_human = style(pad_string(&extract_done_human, INFO_WIDTH))
.green()
.bold()
.to_string();
let download_per_sec = style(pad_string(&format!("{download_per_sec}/s"), INFO_WIDTH))
.blue()
.bold()
.to_string();
let display = format!("{download_per_sec} | {download_done_human} | {extract_done_human} ");
let display_length = (INFO_WIDTH * 3) + 9;
// + 6 to account for final format
let total_length = status_len + display_length + 4;
let min = dl_percent.min(ex_percent);
let dl = dl_percent.saturating_sub(min);
let bar = MultiBar([
BarSegment::Dynamic(&DONE_CHAR, min),
BarSegment::Dynamic(&DOWNLOAD_CHAR, dl),
BarSegment::Dynamic(" ", 100 - min - dl),
])
.scale((width - total_length as u16) as u64);
let msg = match width {
0..50 => {
format!(
"{}: {}/{}",
self.name,
total_done,
if total_expected == 0 {
"-".to_string()
} else {
total_expected.to_string()
}
)
}
_ => {
format!("{status} {display} [{bar}]",)
}
};
self.bar.set_message(msg);
self.bar.tick();
}
}
fn str_len(s: &str) -> usize {
s.chars()
.filter(|x| x.is_alphanumeric() || x.is_whitespace())
.count()
}

View File

@@ -1,21 +1,18 @@
use std::{borrow::Cow, sync::LazyLock, time::Instant};
use std::{borrow::Cow, time::Instant};
use indicatif::{HumanBytes, ProgressBar};
use owo_colors::{OwoColorize, Style};
use owo_colors::Style;
use crate::{
DONE_CHAR, DOWNLOAD_CHAR,
action::{Action, BuildStepId, ResultFields, StartFields},
download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar},
estimator::Estimator,
handlers::{Handler, fetch::FetchHandler},
handlers::Handler,
multibar::{BarSegment, MultiBar},
nix_path, pad_string,
nix_path,
};
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| "-".blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "#".green().bold().to_string());
const INFO_WIDTH: usize = 13;
pub struct SubstituteHandler;
impl Handler for SubstituteHandler {
@@ -109,12 +106,9 @@ impl DownloadHandler {
};
let name = nix_path::extract_package_name_string(&self.path)
.unwrap_or_else(|| "unknown".to_string());
let name = pad_string(&name, (width / 4) as usize);
let status = format!("Download {} |", name.purple().bold());
let status_len = (width / 4) as usize + 10;
if self.download_expected == 0 || self.extract_expected == 0 {
bar.set_message(format!("{status}"));
bar.set_message(format!("Downloading {name}"));
bar.tick();
return;
}

View File

@@ -1,21 +1,17 @@
use std::{borrow::Cow, collections::HashMap, sync::LazyLock, time::Instant};
use std::{borrow::Cow, collections::HashMap, time::Instant};
use indicatif::{HumanBytes, ProgressBar};
use owo_colors::{OwoColorize, Style};
use owo_colors::Style;
use crate::{
DONE_CHAR, DOWNLOAD_CHAR, INPROGRESS_CHAR,
action::{Action, ActionType, BuildStepId, ResultFields, StartFields},
download_display::{DOWNLOAD_STYLE, EXTRACT_STYLE, StyledString, style_bar},
estimator::Estimator,
handlers::{Handler, fetch::FetchHandler},
handlers::Handler,
multibar::{BarSegment, MultiBar},
nix_path, pad_string,
};
static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| "-".blue().bold().to_string());
static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "#".green().bold().to_string());
static INPROGRESS_CHAR: LazyLock<String> = LazyLock::new(|| "-".purple().bold().to_string());
pub struct CopyPathsHandler;
impl Handler for CopyPathsHandler {
@@ -30,7 +26,6 @@ impl Handler for CopyPathsHandler {
id,
..
} => {
state.println(format!("CopyPaths start"))?;
let progress = state.add_pb(ProgressBar::new(1));
progress.set_style(indicatif::ProgressStyle::with_template("{msg}").unwrap());
@@ -103,11 +98,15 @@ impl SubstitutionStatusHandler {
}
let dl_percent = ((self.get_done() as f64 / self.max_transfer as f64) * 100.0) as u64;
let ex_percent = ((self.get_unpacked() as f64 / self.max_copy as f64) * 100.0) as u64;
let expected = (((self.get_running() + self.get_running_copy()) as f64
/ (self.max_transfer + self.max_copy) as f64)
* 100.0) as u64;
let dl_expected_percent =
((self.get_running() as f64 / self.max_transfer as f64) * 100.0) as u64;
let ex_expected_percent =
((self.get_running_copy() as f64 / self.max_copy as f64) * 100.0) as u64;
let min = dl_percent.min(ex_percent);
let dl = dl_percent.saturating_sub(min);
let expected = dl_expected_percent.max(ex_expected_percent);
let exp = expected.saturating_sub(min + dl);
let mbar = MultiBar([
BarSegment::Dynamic(&DONE_CHAR, min),

View File

@@ -1,20 +1,24 @@
use std::sync::LazyLock;
use std::thread::sleep;
use std::time::Duration;
use console::Term;
use owo_colors::OwoColorize;
use crate::state::State;
pub mod action;
pub mod action_raw;
pub mod download_display;
pub mod download_pb;
pub mod estimator;
pub mod handlers;
pub mod multibar;
pub mod nix_path;
pub mod state;
pub mod state_manager;
pub static DOWNLOAD_CHAR: LazyLock<String> = LazyLock::new(|| "-".blue().bold().to_string());
pub static DONE_CHAR: LazyLock<String> = LazyLock::new(|| "=".green().bold().to_string());
pub static INPROGRESS_CHAR: LazyLock<String> = LazyLock::new(|| "-".purple().bold().to_string());
pub fn pad_string(s: &str, width: usize) -> String {
if s.len() >= width {
@@ -32,168 +36,11 @@ fn main() -> Result<(), color_eyre::Report> {
let lines = std::fs::read_to_string("build.log")?;
let mut state = State::new(Term::stderr().size().1);
// 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,
// };
for line in lines.lines() {
let line = line.strip_prefix("@nix ").unwrap_or(line);
sleep(Duration::from_millis(1));
let action = action::Action::parse(line)?;
state.handle(&action)?;
// match action {
// action::Action::Msg { level, msg } => {
// state.println(format!("MSG (level {level}): {msg}"))?;
// }
// action::Action::Start {
// start_type,
// id,
// level: _,
// parent,
// text: _,
// } => {
// // state.progress.println(format!("START {start_type:?}"));
// if let Some(parent) = state.manager.get(*parent) {
// let mut child = parent.clone();
// child.merge(&mut state, start_type);
// child.tick(&mut state);
// state.manager.insert_parent(*id, child);
// };
// // match start_type {
// // StartFields::Substitute { source, target } => {
// // state
// // .manager
// // .insert_parent(*id, BuildState::new(Some(source.to_string()), None));
// //
// // let build_state = state.manager.get_mut(*id).unwrap();
// // build_state.state = BuildEnumState::Substituting;
// // }
// // StartFields::CopyPath {
// // path,
// // origin,
// // destination,
// // } => {
// // state.manager.add_child(*id, *parent);
// // }
// // StartFields::QueryPathInfo { path, source } => {
// // state.progress.println(format!(
// // "START QueryPathInfo (id: {}, parent: {}): path={}",
// // id, parent, path
// // ))?;
// // }
// // StartFields::FileTransfer { target } => {
// // // state.progress.println(format!(
// // // "START FileTransfer (id: {}, parent: {}): target={}",
// // // id, parent, target
// // // ))?;
// // if let Some(parent) = state.manager.get(*parent) {
// // state
// // .manager
// // .insert_parent(*id, BuildState::new(parent.path.clone(), None));
// // let build_state = state.manager.get_mut(*id).unwrap();
// // build_state.state = BuildEnumState::Downloading;
// // };
// //
// // // state.manager.add_child(*id, *parent);
// // // Add child ID mapping to parent
// // }
// // _ => {}
// // };
// }
// action::Action::Stop { id } => {
// state.manager.remove(*id);
// // Stop will only return Some when the last reference is stopped
// // if let Some(build_state) = state.manager.stop(*id) {
// // if let Some(pb) = &build_state.progress_bar {
// // pb.finish_and_clear();
// // state.progress.remove(pb);
// // }
// // state.progress.println(format!(
// // "Completed: {}",
// // build_state.path.as_deref().unwrap_or("unknown")
// // ))?;
// // }
// }
// action::Action::Result { id, fields } => match fields {
// action::ResultFields::FetchStatus(status) => {
// state.println(format!(
// "RESULT FetchStatus (id: {}): status={}",
// id, status
// ))?;
// }
// action::ResultFields::Progress {
// done,
// expected,
// running,
// failed,
// } => {
// if expected == 0 {
// continue;
// };
// 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)
// // && expected > 0
// // {
// // let percentage = if expected == 0 {
// // 0
// // } else {
// // (done * 100 / expected) as u64
// // };
// // match &build_state.progress_bar {
// // Some(pb) => {
// // if percentage > pb.position() {
// // pb.set_position(percentage);
// // };
// // }
// // None => {
// // state.progress.println(format!(
// // "Creating progress bar for id {} (done: {}, expected: {})",
// // id, done, expected
// // ))?;
// // let n = match build_state.state {
// // BuildEnumState::Downloading => "Downloading",
// // BuildEnumState::Substituting => "Substituting",
// // _ => "Processing",
// // };
// // let name =
// // nix_path::extract_full_name(build_state.path.as_ref().unwrap());
// // let pb = state.progress.add(
// // ProgressBar::new(100)
// // .with_style(
// // ProgressStyle::default_bar()
// // .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%")
// // .unwrap(),
// // )
// // .with_message(format!(
// // "{n} {}",
// // name.as_deref().unwrap_or("unknown")
// // )),
// // );
// // pb.set_position(percentage);
// // build_state.progress_bar = Some(pb);
// // }
// // };
// // } else {
// // // state.progress.println(format!(
// // // "RESULT Progress (id: {}): done={}, expected={}, running={}, failed={}",
// // // id, done, expected, running, failed
// // // ))?;
// // };
// }
// _ => {}
// },
// }
}
Ok(())

View File

@@ -1,189 +0,0 @@
use console::{Term, style};
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
use std::{borrow::Cow, collections::HashMap, io, time::Instant};
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,
}
impl<'a> State<'a> {
pub fn add_pb(&mut self, pb: ProgressBar) -> ProgressBar {
let separator = self.separator.get_or_insert_with(|| {
let separator = ProgressBar::new_spinner()
.with_style(
ProgressStyle::default_spinner()
.template(&style("··{wide_msg:<}").dim().to_string())
.expect("invalid template"),
)
.with_message("·".repeat(512))
.with_finish(ProgressFinish::AndClear);
let separator = self.multi_progress.insert(0, separator);
separator.set_length(0);
separator
});
self.multi_progress.insert_after(separator, pb)
// self.progress.add(pb)
}
pub fn add_pb_before(&mut self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.multi_progress.insert_before(before, pb)
}
pub fn add_pb_after(&mut self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.multi_progress.insert_after(after, pb)
}
pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> {
self.multi_progress.println(msg)
}
}
#[derive(Debug, Clone)]
pub enum BuildEnumState<'a> {
Init,
Unknown,
Realise,
Builds,
CopyPaths,
Substitute {
source: Cow<'a, str>,
target: Cow<'a, str>,
},
SubstituteCopy {
path: Cow<'a, str>,
bar: DownloadBar,
},
SubstituteFetch {
path: Cow<'a, str>,
bar: DownloadBar,
},
Query {
path: Cow<'a, str>,
source: Cow<'a, str>,
},
QueryFetch {
target: Cow<'a, str>,
},
}
#[derive(Debug, Clone)]
pub struct BuildState<'a> {
pub path: Option<String>,
pub progress_bar: Option<ProgressBar>,
pub state: BuildEnumState<'a>,
}
impl<'a> BuildState<'a> {
pub fn new() -> Self {
Self {
path: None,
progress_bar: None,
state: BuildEnumState::Init,
}
}
}
impl<'a> BuildState<'a> {
pub fn merge(&mut self, state: &mut State, start_type: StartFields<'a>) {
let state = match (&self.state, start_type.clone()) {
(BuildEnumState::Init, StartFields::QueryPathInfo { path, source }) => {
BuildEnumState::Query { path, source }
}
(BuildEnumState::Query { path, source }, StartFields::FileTransfer { target }) => {
BuildEnumState::QueryFetch { target }
}
(BuildEnumState::Init, StartFields::Realise) => BuildEnumState::Realise,
(BuildEnumState::Init, StartFields::Builds) => BuildEnumState::Builds,
(BuildEnumState::Init, StartFields::CopyPaths) => BuildEnumState::CopyPaths,
(BuildEnumState::Init, StartFields::Substitute { source, target }) => {
BuildEnumState::Substitute { source, target }
}
(BuildEnumState::Substitute { .. }, StartFields::CopyPath { path, .. }) => {
let name =
nix_path::extract_package_name_string(&path).unwrap_or("unknown".to_string());
let bar = state.add_pb(ProgressBar::new(100));
let bar = DownloadBar::new(bar, name, state.width);
BuildEnumState::SubstituteCopy { path, bar }
}
(BuildEnumState::SubstituteCopy { path, bar }, StartFields::FileTransfer { .. }) => {
BuildEnumState::SubstituteFetch {
path: path.clone(),
bar: bar.clone(),
}
}
(_, StartFields::Unknown) => BuildEnumState::Unknown,
_ => unimplemented!(
"Unsupported state transition from {:?} with {:?}",
self.state,
start_type
),
};
self.state = state;
}
pub fn tick(&mut self, state: &mut State<'a>) {
match &self.state {
BuildEnumState::QueryFetch { target } => {
let pb = state.add_pb(ProgressBar::new_spinner());
pb.set_message(format!("Fetching info for {}", target));
pb.enable_steady_tick(std::time::Duration::from_millis(100));
self.progress_bar = Some(pb);
}
_ => {}
}
}
pub fn progress(&mut self, state: &mut State<'a>, done: u64, expected: u64) {
match &self.state {
BuildEnumState::SubstituteFetch { bar, .. } => {
bar.download_expected.replace(expected);
bar.download_done.replace(done);
bar.update(state.width);
}
BuildEnumState::SubstituteCopy { path, bar } => {
bar.extract_expected.replace(expected);
bar.extract_done.replace(done);
bar.update(state.width);
}
_ => {}
}
}
}
pub struct StateManager<'a> {
states: HashMap<u64, BuildState<'a>>,
}
impl<'a> StateManager<'a> {
pub fn new() -> Self {
let mut states = HashMap::new();
states.insert(0, BuildState::new());
Self { states }
}
pub fn get(&self, id: u64) -> Option<&BuildState<'a>> {
self.states.get(&id)
}
pub fn get_mut(&mut self, id: u64) -> Option<&mut BuildState<'a>> {
self.states.get_mut(&id)
}
pub fn insert_parent(&mut self, id: u64, state: BuildState<'a>) {
self.states.insert(id, state);
}
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 {
pb.finish_and_clear();
self.states.remove(&id);
}
}
self.states.remove(&id);
}
}