vault_audit_tools/utils/
progress.rs1use crate::utils::format::format_number;
2use std::io::{self, Write};
3use std::time::{Duration, Instant};
4
5pub struct ProgressBar {
7 total: Option<usize>,
8 current: usize,
9 last_update: Instant,
10 update_interval: Duration,
11 label: String,
12 started: Instant,
13 render_count: usize,
14}
15
16impl ProgressBar {
17 pub fn new(total: usize, label: &str) -> Self {
19 let mut pb = Self {
20 total: Some(total),
21 current: 0,
22 last_update: Instant::now(),
23 update_interval: Duration::from_millis(200), label: label.to_string(),
25 started: Instant::now(),
26 render_count: 0,
27 };
28 pb.render(); pb
30 }
31
32 pub fn new_spinner(label: &str) -> Self {
34 let mut pb = Self {
35 total: None,
36 current: 0,
37 last_update: Instant::now(),
38 update_interval: Duration::from_millis(200),
39 label: label.to_string(),
40 started: Instant::now(),
41 render_count: 0,
42 };
43 pb.render(); pb
45 }
46
47 pub fn update(&mut self, current: usize) {
49 self.current = current;
50
51 if self.last_update.elapsed() >= self.update_interval {
52 self.render();
53 self.last_update = Instant::now();
54 }
55 }
56
57 #[allow(dead_code)]
59 pub fn inc(&mut self) {
60 self.update(self.current + 1);
61 }
62
63 pub fn render(&mut self) {
65 self.render_count += 1;
66
67 if let Some(total) = self.total {
68 let percentage = if total > 0 {
69 (self.current as f64 / total as f64 * 100.0).min(100.0)
70 } else {
71 0.0
72 };
73
74 let bar_width = 40;
75 let filled = (bar_width as f64 * percentage / 100.0) as usize;
76 let empty = bar_width - filled;
77
78 let bar = format!("[{}{}]", "█".repeat(filled), "░".repeat(empty));
79
80 let elapsed = self.started.elapsed();
82 let eta_info = if self.current > 0 && percentage > 0.1 {
83 let estimated_total_time = elapsed.as_secs_f64() / (percentage / 100.0);
84 let remaining_time = estimated_total_time - elapsed.as_secs_f64();
85
86 if remaining_time > 0.0 {
87 let remaining_mins = (remaining_time / 60.0) as u64;
88 let remaining_secs = (remaining_time % 60.0) as u64;
89 format!(" ETA: {}:{:02}", remaining_mins, remaining_secs)
90 } else {
91 String::new()
92 }
93 } else {
94 String::new()
95 };
96
97 let speed_info = if elapsed.as_secs() > 0 {
99 let rate = self.current as f64 / elapsed.as_secs_f64();
100 format!(" ({}/s)", format_number(rate as usize))
101 } else {
102 String::new()
103 };
104
105 eprint!(
106 "\r{} {} {:>6.1}% ({}/{}){}{}",
107 self.label,
108 bar,
109 percentage,
110 format_number(self.current),
111 format_number(total),
112 speed_info,
113 eta_info
114 );
115 } else {
116 let spinner = ['|', '/', '-', '\\'];
118 let idx = self.render_count % spinner.len();
119
120 eprint!(
121 "\r{} {} {}",
122 self.label,
123 spinner[idx],
124 format_number(self.current)
125 );
126 }
127
128 let _ = io::stderr().flush();
129 }
130
131 pub fn finish(&mut self) {
133 self.render();
134 let elapsed = self.started.elapsed();
135 let rate = if elapsed.as_secs() > 0 {
136 format!(
137 "{}/s",
138 format_number(self.current / elapsed.as_secs() as usize)
139 )
140 } else {
141 String::new()
142 };
143
144 eprintln!(
145 " Done in {:.1}s {}",
146 elapsed.as_secs_f64(),
147 if rate.is_empty() {
148 String::new()
149 } else {
150 format!("({})", rate)
151 }
152 );
153 }
154
155 pub fn finish_with_message(&mut self, message: &str) {
157 self.render();
158 eprintln!(" {}", message);
159 }
160}