From b08a3c682ed9df1f062a1cc4ed19d88a3d153bde Mon Sep 17 00:00:00 2001 From: Nikkuss Date: Sun, 9 Nov 2025 21:51:23 +0400 Subject: [PATCH] meow meow --- src/lib.rs | 4 + src/main.rs | 206 +++++++++++++++++++++++++------------------ src/nix_path.rs | 38 ++++++++ src/state_manager.rs | 107 ++++++++++++++++++++++ 4 files changed, 270 insertions(+), 85 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/nix_path.rs create mode 100644 src/state_manager.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..157775c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod action; +pub mod action_raw; +pub mod nix_path; +pub mod state_manager; diff --git a/src/main.rs b/src/main.rs index 20efdf9..1cde0b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,29 @@ -use std::{collections::HashMap, thread::sleep, time::Duration}; +use std::thread::sleep; +use std::time::Duration; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use crate::action::StartFields; +use crate::state_manager::{BuildEnumState, BuildState, StateManager}; pub mod action; pub mod action_raw; - -enum BuildEnumState { - Query, - Downloading, -} -struct BuildState { - path: Option, - progress_bar: ProgressBar, - state: BuildEnumState, -} +pub mod nix_path; +pub mod state_manager; struct State { progress: MultiProgress, - bars: HashMap, + manager: StateManager, } fn main() -> Result<(), color_eyre::Report> { color_eyre::install().unwrap(); - let lines = std::fs::read_to_string("log2")?; + let lines = std::fs::read_to_string("build.log")?; // let lines = lines.lines().take(1000).collect::>().join("\n"); let mut state = State { progress: MultiProgress::new(), - bars: HashMap::new(), + manager: StateManager::new(), }; // let progress = MultiProgress::new(); @@ -58,7 +52,7 @@ fn main() -> Result<(), color_eyre::Report> { for line in lines.lines() { lines_pb.inc(1); let line = line.strip_prefix("@nix ").unwrap_or(line); - sleep(Duration::from_millis(50)); + sleep(Duration::from_millis(10)); let action = action::Action::parse(line)?; match action { action::Action::Msg { level, msg } => { @@ -69,81 +63,123 @@ fn main() -> Result<(), color_eyre::Report> { action::Action::Start { start_type, id, - level, + level: _, parent, - text, - } => match start_type { - StartFields::QueryPathInfo { path, source } => { - state.bars.insert( - *id, - BuildState { - path: Some(path.to_string()), - progress_bar: state.progress.add(ProgressBar::new(100)), - state: BuildEnumState::Query, - }, - ); - } - StartFields::FileTransfer { target } => { - state.progress.println(format!( - "START FileTransfer (id: {}, parent: {}): target={}", - id, parent, target - ))?; - if let Some(bar) = state.bars.get_mut(&parent) { - bar.state = BuildEnumState::Downloading; - bar.progress_bar.set_style( - ProgressStyle::default_bar() - .template("{msg} [{bar:40.cyan/blue}] {pos:>3}%") - .unwrap(), - ); - bar.progress_bar.set_message(format!( - "Downloading {}", - bar.path.as_deref().unwrap_or("unknown") - )); + text: _, + } => { + state.progress.println(format!("START {start_type:?}")); + 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; } - // let bar = progress.add(ProgressBar::new(100)); - // bar // bar.set_message(format!("meow {id} ")); - // map.insert(id, bar); + StartFields::CopyPath { + path, + origin, + destination, + } => { + state.manager.add_child(*id, *parent); + } + // StartFields::QueryPathInfo { path, source: _ } => { + // state + // .manager + // .insert_parent(*id, BuildState::new(Some(path.to_string()), None)); + // } + 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 } => { + // 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::Progress { + done, + expected, + running, + failed, + } => { + // 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 + ))?; + }; } _ => {} }, - action::Action::Stop { id } => { - // if let Some(bar) = map.get(&id) { - // bar.finish(); - // // bar.finish_with_message(format!("Transfer stopped (id: {})", id)); - // progress.remove(bar); - // map.remove(&id); - // } - // progress.println(format!("STOP (id: {})", id))?; - } - action::Action::Result { id, fields } => { - // progress.println(format!("RESULT (id: {}): {:?}", id, fields))?; - match fields { - // action::ResultFields::Progress { - // done, - // expected, - // running, - // failed, - // } => { - // // sleep(Duration::from_millis(1)); - // // if let Some(bar) = map.get(&id) { - // // let percentage = if expected == 0 { - // // 0 - // // } else { - // // (done * 100 / expected) as u64 - // // }; - // // bar.set_position(percentage); - // // if done >= expected { - // // bar.finish_with_message(format!("Completed transfer (id: {})", id)); - // // map.remove(&id); - // // } - // } - // } - _ => {} - } - } - _ => { - println!("{action:#?}"); - } } } diff --git a/src/nix_path.rs b/src/nix_path.rs new file mode 100644 index 0000000..ace0ab0 --- /dev/null +++ b/src/nix_path.rs @@ -0,0 +1,38 @@ +const NIX_STORE_PREFIX: &str = "/nix/store/"; +const NIX_HASH_LENGTH: usize = 32; +pub fn extract_package_name(path: &str) -> Option { + let without_prefix = path.strip_prefix(NIX_STORE_PREFIX)?; + + if without_prefix.len() <= NIX_HASH_LENGTH + 1 { + return None; + } + + let after_hash = &without_prefix[NIX_HASH_LENGTH + 1..]; + let parts: Vec<&str> = after_hash.split('-').collect(); + + if parts.is_empty() { + return None; + } + + let name_parts: Vec<&str> = parts + .iter() + .take_while(|part| !part.chars().next().map_or(false, |c| c.is_ascii_digit())) + .copied() + .collect(); + + if name_parts.is_empty() { + return None; + } + + Some(name_parts.join("-")) +} + +pub fn extract_full_name(path: &str) -> Option<&str> { + let without_prefix = path.strip_prefix(NIX_STORE_PREFIX)?; + + if without_prefix.len() <= NIX_HASH_LENGTH + 1 { + return None; + } + + Some(&without_prefix[NIX_HASH_LENGTH + 1..]) +} diff --git a/src/state_manager.rs b/src/state_manager.rs new file mode 100644 index 0000000..133478b --- /dev/null +++ b/src/state_manager.rs @@ -0,0 +1,107 @@ +use indicatif::ProgressBar; +use std::collections::HashMap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BuildEnumState { + Query, + Downloading, + Substituting, +} + +pub struct BuildState { + pub path: Option, + pub progress_bar: Option, + pub state: BuildEnumState, + ref_count: usize, +} + +impl BuildState { + pub fn new(path: Option, progress_bar: Option) -> Self { + Self { + path, + progress_bar, + state: BuildEnumState::Query, + ref_count: 1, + } + } +} + +pub struct StateManager { + states: HashMap, + id_to_canonical: HashMap, +} + +impl StateManager { + pub fn new() -> Self { + Self { + states: HashMap::new(), + id_to_canonical: HashMap::new(), + } + } + + pub fn insert_parent(&mut self, id: u64, state: BuildState) { + self.id_to_canonical.insert(id, id); // parent maps to itself + self.states.insert(id, state); + } + + pub fn add_child(&mut self, child_id: u64, parent_id: u64) { + if let Some(&canonical_id) = self.id_to_canonical.get(&parent_id) { + self.id_to_canonical.insert(child_id, canonical_id); + + if let Some(state) = self.states.get_mut(&canonical_id) { + state.ref_count += 1; + } + } + } + + pub fn get(&self, id: u64) -> Option<&BuildState> { + self.id_to_canonical + .get(&id) + .and_then(|canonical_id| self.states.get(canonical_id)) + } + + pub fn get_mut(&mut self, id: u64) -> Option<&mut BuildState> { + self.id_to_canonical + .get(&id) + .copied() + .and_then(|canonical_id| self.states.get_mut(&canonical_id)) + } + + pub fn stop(&mut self, id: u64) -> Option { + let canonical_id = self.id_to_canonical.get(&id).copied()?; + + self.id_to_canonical.remove(&id); + + if let Some(state) = self.states.get_mut(&canonical_id) { + state.ref_count -= 1; + + if state.ref_count == 0 { + return self.states.remove(&canonical_id); + } + } + + None + } + + pub fn get_canonical_id(&self, id: u64) -> Option { + self.id_to_canonical.get(&id).copied() + } + + pub fn contains(&self, id: u64) -> bool { + self.id_to_canonical.contains_key(&id) + } + + pub fn len(&self) -> usize { + self.states.len() + } + + pub fn is_empty(&self) -> bool { + self.states.is_empty() + } +} + +impl Default for StateManager { + fn default() -> Self { + Self::new() + } +}