scirs2_core/logging/progress/
tracker.rs1use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8
9use super::renderer::ProgressRenderer;
10use super::statistics::ProgressStats;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum ProgressStyle {
15 Percentage,
17 Bar,
19 BlockBar,
21 Spinner,
23 DetailedBar,
25}
26
27#[derive(Debug, Clone)]
29pub struct ProgressConfig {
30 pub style: ProgressStyle,
32 pub width: usize,
34 pub show_eta: bool,
36 pub show_statistics: bool,
38 pub show_speed: bool,
40 pub adaptive_rate: bool,
42 pub min_update_interval: Duration,
44 pub max_update_interval: Duration,
46 pub template: Option<String>,
48 pub symbols: Option<ProgressSymbols>,
50}
51
52impl Default for ProgressConfig {
53 fn default() -> Self {
54 Self {
55 style: ProgressStyle::BlockBar,
56 width: 40,
57 show_eta: true,
58 show_statistics: true,
59 show_speed: true,
60 adaptive_rate: true,
61 min_update_interval: Duration::from_millis(100),
62 max_update_interval: Duration::from_secs(1),
63 template: None,
64 symbols: None,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct ProgressSymbols {
72 pub start: String,
74 pub end: String,
76 pub fill: String,
78 pub empty: String,
80 pub spinner: Vec<String>,
82}
83
84impl Default for ProgressSymbols {
85 fn default() -> Self {
86 Self {
87 start: "[".to_string(),
88 end: "]".to_string(),
89 fill: "=".to_string(),
90 empty: " ".to_string(),
91 spinner: vec![
92 "-".to_string(),
93 "\\".to_string(),
94 "|".to_string(),
95 "/".to_string(),
96 ],
97 }
98 }
99}
100
101impl ProgressSymbols {
102 pub fn blocks() -> Self {
104 Self {
105 start: "│".to_string(),
106 end: "│".to_string(),
107 fill: "█".to_string(),
108 empty: " ".to_string(),
109 spinner: vec![
110 "⠋".to_string(),
111 "⠙".to_string(),
112 "⠹".to_string(),
113 "⠸".to_string(),
114 "⠼".to_string(),
115 "⠴".to_string(),
116 "⠦".to_string(),
117 "⠧".to_string(),
118 "⠇".to_string(),
119 "⠏".to_string(),
120 ],
121 }
122 }
123}
124
125pub struct EnhancedProgressTracker {
127 pub description: String,
129 pub config: ProgressConfig,
131 stats: Arc<Mutex<ProgressStats>>,
133 start_time: Instant,
135 active: bool,
137 hidden: bool,
139 renderer: ProgressRenderer,
141}
142
143impl EnhancedProgressTracker {
144 pub fn new(description: &str, total: u64) -> Self {
146 let config = ProgressConfig::default();
147 let stats = Arc::new(Mutex::new(ProgressStats::new(total)));
148 let renderer = ProgressRenderer::new();
149
150 Self {
151 description: description.to_string(),
152 config,
153 stats,
154 start_time: Instant::now(),
155 active: false,
156 hidden: false,
157 renderer,
158 }
159 }
160
161 pub fn with_config(mut self, config: ProgressConfig) -> Self {
163 self.config = config;
164 self
165 }
166
167 pub const fn with_style(mut self, style: ProgressStyle) -> Self {
169 self.config.style = style;
170 self
171 }
172
173 pub fn with_symbols(mut self, symbols: ProgressSymbols) -> Self {
175 self.config.symbols = Some(symbols);
176 self
177 }
178
179 pub const fn with_eta(mut self, show: bool) -> Self {
181 self.config.show_eta = show;
182 self
183 }
184
185 pub const fn with_statistics(mut self, show: bool) -> Self {
187 self.config.show_statistics = show;
188 self
189 }
190
191 pub fn start(&mut self) {
193 self.active = true;
194 self.start_time = Instant::now();
195
196 if !self.hidden {
198 self.renderer.init();
199 self.render();
200 }
201 }
202
203 pub fn update(&mut self, processed: u64) {
205 if !self.active {
206 return;
207 }
208
209 let now = Instant::now();
210
211 {
213 let mut stats = self.stats.lock().expect("Operation failed");
214 stats.update(processed, now);
215 }
216
217 let should_render = if self.config.adaptive_rate {
219 self.should_update_adaptive()
220 } else {
221 self.should_update_fixed()
222 };
223
224 if should_render && !self.hidden {
225 self.render();
226 }
227 }
228
229 pub fn increment(&mut self, amount: u64) {
231 if !self.active {
232 return;
233 }
234
235 let processed = {
236 let stats = self.stats.lock().expect("Operation failed");
237 stats.processed + amount
238 };
239
240 self.update(processed);
241 }
242
243 pub fn finish(&mut self) {
245 if !self.active {
246 return;
247 }
248
249 self.active = false;
250
251 {
253 let mut stats = self.stats.lock().expect("Operation failed");
254 let total = stats.total;
255 stats.processed = total;
256 stats.percentage = 100.0;
257 stats.eta = Duration::from_secs(0);
258 stats.update(total, Instant::now());
259 }
260
261 if !self.hidden {
263 self.render();
264 self.renderer.finalize();
265 }
266 }
267
268 pub fn hide(&mut self) {
270 self.hidden = true;
271 }
272
273 pub fn show(&mut self) {
275 self.hidden = false;
276
277 if self.active {
278 self.render();
279 }
280 }
281
282 pub fn stats(&self) -> ProgressStats {
284 self.stats.lock().expect("Operation failed").clone()
285 }
286
287 fn should_update_fixed(&self) -> bool {
289 let stats = self.stats.lock().expect("Operation failed");
290 let elapsed = stats.last_update.elapsed();
291 elapsed >= self.config.min_update_interval
292 }
293
294 fn should_update_adaptive(&self) -> bool {
296 let stats = self.stats.lock().expect("Operation failed");
297 let elapsed = stats.last_update.elapsed();
298
299 if elapsed >= self.config.max_update_interval {
301 return true;
302 }
303
304 if elapsed >= self.config.min_update_interval {
306 let progress_ratio = if stats.total > 0 {
308 stats.processed as f64 / stats.total as f64
309 } else {
310 0.0
311 };
312
313 let position_factor = 4.0 * progress_ratio * (1.0 - progress_ratio);
317 let threshold = self.config.min_update_interval.as_secs_f64()
318 + position_factor
319 * (self.config.max_update_interval.as_secs_f64()
320 - self.config.min_update_interval.as_secs_f64());
321
322 elapsed.as_secs_f64() >= threshold
323 } else {
324 false
325 }
326 }
327
328 fn render(&mut self) {
330 if self.hidden {
331 return;
332 }
333
334 let stats = self.stats.lock().expect("Operation failed");
335 let symbols = self.config.symbols.clone().unwrap_or_default();
336
337 match self.config.style {
338 ProgressStyle::Percentage => {
339 self.renderer.renderpercentage(&self.description, &stats);
340 }
341 ProgressStyle::Bar => {
342 self.renderer.render_basic(
343 &self.description,
344 &stats,
345 self.config.width,
346 self.config.show_eta,
347 &symbols,
348 );
349 }
350 ProgressStyle::BlockBar => {
351 let block_symbols = ProgressSymbols::blocks();
352 self.renderer.render_basic(
353 &self.description,
354 &stats,
355 self.config.width,
356 self.config.show_eta,
357 &block_symbols,
358 );
359 }
360 ProgressStyle::Spinner => {
361 self.renderer.render_spinner(
362 &self.description,
363 &stats,
364 self.config.show_eta,
365 &symbols,
366 );
367 }
368 ProgressStyle::DetailedBar => {
369 self.renderer.render_detailed(
370 &self.description,
371 &stats,
372 self.config.width,
373 self.config.show_eta,
374 self.config.show_statistics,
375 self.config.show_speed,
376 &symbols,
377 );
378 }
379 }
380 }
381}
382
383pub struct ProgressBuilder {
385 description: String,
386 total: u64,
387 style: ProgressStyle,
388 width: usize,
389 show_eta: bool,
390 show_statistics: bool,
391 show_speed: bool,
392 adaptive_rate: bool,
393 min_update_interval: Duration,
394 max_update_interval: Duration,
395 template: Option<String>,
396 symbols: Option<ProgressSymbols>,
397 hidden: bool,
398}
399
400impl ProgressBuilder {
401 pub fn new(description: &str, total: u64) -> Self {
403 Self {
404 description: description.to_string(),
405 total,
406 style: ProgressStyle::BlockBar,
407 width: 40,
408 show_eta: true,
409 show_statistics: true,
410 show_speed: true,
411 adaptive_rate: true,
412 min_update_interval: Duration::from_millis(100),
413 max_update_interval: Duration::from_secs(1),
414 template: None,
415 symbols: None,
416 hidden: false,
417 }
418 }
419
420 pub const fn style(mut self, style: ProgressStyle) -> Self {
422 self.style = style;
423 self
424 }
425
426 pub const fn width(mut self, width: usize) -> Self {
428 self.width = width;
429 self
430 }
431
432 pub const fn show_eta(mut self, show: bool) -> Self {
434 self.show_eta = show;
435 self
436 }
437
438 pub const fn show_statistics(mut self, show: bool) -> Self {
440 self.show_statistics = show;
441 self
442 }
443
444 pub const fn show_speed(mut self, show: bool) -> Self {
446 self.show_speed = show;
447 self
448 }
449
450 pub const fn adaptive_rate(mut self, enable: bool) -> Self {
452 self.adaptive_rate = enable;
453 self
454 }
455
456 pub const fn min_update_interval(mut self, interval: Duration) -> Self {
458 self.min_update_interval = interval;
459 self
460 }
461
462 pub const fn max_update_interval(mut self, interval: Duration) -> Self {
464 self.max_update_interval = interval;
465 self
466 }
467
468 pub fn template(mut self, template: &str) -> Self {
470 self.template = Some(template.to_string());
471 self
472 }
473
474 pub fn symbols(mut self, symbols: ProgressSymbols) -> Self {
476 self.symbols = Some(symbols);
477 self
478 }
479
480 pub const fn hidden(mut self, hidden: bool) -> Self {
482 self.hidden = hidden;
483 self
484 }
485
486 pub fn build(self) -> EnhancedProgressTracker {
488 let config = ProgressConfig {
489 style: self.style,
490 width: self.width,
491 show_eta: self.show_eta,
492 show_statistics: self.show_statistics,
493 show_speed: self.show_speed,
494 adaptive_rate: self.adaptive_rate,
495 min_update_interval: self.min_update_interval,
496 max_update_interval: self.max_update_interval,
497 template: self.template,
498 symbols: self.symbols,
499 };
500
501 let mut tracker =
502 EnhancedProgressTracker::new(&self.description, self.total).with_config(config);
503
504 if self.hidden {
505 tracker.hide();
506 }
507
508 tracker
509 }
510}