//! Progress reporting types for a run: per-root and overall counters, //! and the reconcile prologue's own meter. /// Where one root's pipeline is in its life cycle. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RootPhase { /// The parallel walk is discovering and writing file metadata. Walking, /// The walk finished; content extraction is draining this root's /// pending rows. Extracting, Done, } /// Progress for one indexing root. Each root runs its own walker and its /// own extraction cursor; the GUI shows one row per root. #[derive(Debug, Clone)] pub struct RootProgress { pub root: String, pub phase: RootPhase, /// Files the walk has seen so far. Final and exact once the root leaves /// [`RootPhase::Walking`] — see [`RootProgress::walk_denominator`]. pub walked: usize, /// What to divide `walked` by; `None` until one lands. Exact on a root /// walked before; a first-time root falls back to the `find` scan, which /// counts tree *entries* and so reads high. Read it through /// [`RootProgress::walk_denominator`]. pub walk_total: Option, /// Rows with searchable text: extracted in earlier runs plus this one. pub extracted: usize, /// The root's whole searchable set: pending + already-extracted rows when /// the walk finished — the count of files that have or will have text, /// not of files under the root. pub extract_total: usize, pub current_file: Option, /// Threads busy right now / pool size, for the pool this root's current /// phase is running. Both zero once the root is done: its threads are /// gone, and a dead pool's size would read "0/44 workers". pub active_workers: usize, pub total_workers: usize, } impl RootProgress { /// This root's walk-phase contribution to a progress denominator. /// /// Once the walk ends `walked` is final and exact, so `walk_total` is /// dropped: keeping it (it can read 1.6x high) is what stopped the bar /// reaching 100%. pub fn walk_denominator(&self) -> Option { match self.phase { // Never below what has already been walked: an overtaken // denominator pins the bar at 100% mid-walk. RootPhase::Walking => self.walk_total.map(|t| t.max(self.walked)), RootPhase::Extracting | RootPhase::Done => Some(self.walked), } } } /// Files processed and the run's total, across every root. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OverallProgress { /// Both halves of the work: every file the walks have seen, plus every /// row with searchable text. A file is counted once for each, so this is /// a work-units figure rather than a file count. pub processed: usize, /// `None` while a still-walking root has no count yet; no root past its /// walk can withhold one, so a run always gains a total in the end. pub total: Option, } impl OverallProgress { /// Completed share, clamped to 1. `None` when there is nothing to /// divide by — an unknown total, or a run with no work in it at all. pub fn fraction(&self) -> Option { match self.total { Some(total) if total > 0 => Some((self.processed as f64 / total as f64).min(1.0)), _ => None, } } } /// Aggregate every root's progress into the one pair the status bar shows. /// `extract_total` is exact the moment a root's walk ends; before that a /// root contributes only its walk. pub fn overall_progress(roots: &[RootProgress]) -> OverallProgress { let processed = roots.iter().map(|r| r.walked + r.extracted).sum(); let mut total = Some(0usize); for r in roots { match (total, r.walk_denominator()) { (Some(acc), Some(walk)) => total = Some(acc + walk + r.extract_total), _ => { total = None; break; } } } OverallProgress { processed, total } } /// How far a configuration reconciliation has got. Reported by both places /// one can run: a full run's prologue /// ([`IndexingService::reconcile_stored_config`]) and the coordinator's /// between-runs pass ([`crate::scope::advance`]). #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct ReconcileProgress { /// Stored rows re-tested against the current configuration so far. pub examined: usize, /// Rows in the index, counted once when the scan starts. `None` while /// the plan is doing whole-range work that reads no rows at all. pub total: Option, pub deleted: usize, /// Rows whose content state or stored text was re-decided. pub recontented: usize, } impl ReconcileProgress { /// Completed share, clamped to 1. `None` when there is nothing to divide /// by, matching [`OverallProgress::fraction`]. pub fn fraction(&self) -> Option { match self.total { Some(total) if total > 0 => Some((self.examined as f64 / total as f64).min(1.0)), _ => None, } } }