use std::{ sync::{ atomic::{AtomicBool, Ordering}, Arc, }, time::Duration, }; use compact_str::{format_compact, CompactString}; use gix::progress::{ prodash::messages::MessageLevel, Count, Id, NestedProgress, Progress, Step, StepShared, Unit, UNKNOWN, }; use tokio::time; use tracing::{error, info}; pub(super) struct TracingProgress { name: CompactString, id: Id, max: Option, unit: Option, step: StepShared, trigger: Arc, } const EMIT_LOG_EVERY_S: f32 = 0.5; const SEP: &str = "::"; impl TracingProgress { /// Create a new instanCompactce from `name`. pub fn new(name: &str) -> Self { let trigger = Arc::new(AtomicBool::new(true)); tokio::spawn({ let mut interval = time::interval(Duration::from_secs_f32(EMIT_LOG_EVERY_S)); interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip); let trigger = Arc::clone(&trigger); async move { while Arc::strong_count(&trigger) > 1 { trigger.store(true, Ordering::Relaxed); interval.tick().await; } } }); Self { name: CompactString::new(name), id: UNKNOWN, max: None, step: Default::default(), unit: None, trigger, } } fn log_progress(&self, step: Step) { if self.trigger.swap(false, Ordering::Relaxed) { match (self.max, &self.unit) { (max, Some(unit)) => { info!("{} → {}", self.name, unit.display(step, max, None)) } (Some(max), None) => info!("{} → {} / {}", self.name, step, max), (None, None) => info!("{} → {}", self.name, step), } } } } impl Count for TracingProgress { fn set(&self, step: Step) { self.step.store(step, Ordering::Relaxed); self.log_progress(step); } fn step(&self) -> Step { self.step.load(Ordering::Relaxed) } fn inc_by(&self, step_to_inc: Step) { self.log_progress( self.step .fetch_add(step_to_inc, Ordering::Relaxed) .wrapping_add(step_to_inc), ); } fn counter(&self) -> StepShared { Arc::clone(&self.step) } } impl Progress for TracingProgress { fn init(&mut self, max: Option, unit: Option) { self.max = max; self.unit = unit; } fn unit(&self) -> Option { self.unit.clone() } fn max(&self) -> Option { self.max } fn set_max(&mut self, max: Option) -> Option { let prev = self.max; self.max = max; prev } fn set_name(&mut self, name: String) { self.name = self .name .split("::") .next() .map(|parent| format_compact!("{parent}{SEP}{name}")) .unwrap_or_else(|| name.into()); } fn name(&self) -> Option { self.name.split(SEP).nth(1).map(ToOwned::to_owned) } fn id(&self) -> Id { self.id } fn message(&self, level: MessageLevel, message: String) { let name = &self.name; match level { MessageLevel::Info => info!("ℹ{name} → {message}"), MessageLevel::Failure => error!("𐄂{name} → {message}"), MessageLevel::Success => info!("✓{name} → {message}"), } } } impl NestedProgress for TracingProgress { type SubProgress = TracingProgress; fn add_child(&mut self, name: impl Into) -> Self::SubProgress { self.add_child_with_id(name, UNKNOWN) } fn add_child_with_id(&mut self, name: impl Into, id: Id) -> Self::SubProgress { Self { name: format_compact!("{}{}{}", self.name, SEP, Into::::into(name)), id, step: Arc::default(), max: None, unit: None, trigger: Arc::clone(&self.trigger), } } }