Skip to main content

scirs2_core/logging/progress/
tracker.rs

1//! Enhanced Progress Tracker
2//!
3//! Provides rich progress tracking with multiple visualization styles, statistical analysis,
4//! and adaptive update rates.
5
6use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8
9use super::renderer::ProgressRenderer;
10use super::statistics::ProgressStats;
11
12/// Progress visualization style
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum ProgressStyle {
15    /// Simple percentage text (e.g., "45%")
16    Percentage,
17    /// Basic ASCII progress bar
18    Bar,
19    /// Unicode block characters for smoother bar
20    BlockBar,
21    /// Spinner with percentage
22    Spinner,
23    /// Detailed bar with additional statistics
24    DetailedBar,
25}
26
27/// Progress tracker configuration
28#[derive(Debug, Clone)]
29pub struct ProgressConfig {
30    /// The style of progress visualization
31    pub style: ProgressStyle,
32    /// Width of the progress bar in characters
33    pub width: usize,
34    /// Whether to show ETA
35    pub show_eta: bool,
36    /// Whether to show detailed statistics
37    pub show_statistics: bool,
38    /// Whether to show transfer rate (items/second)
39    pub show_speed: bool,
40    /// Whether to use adaptive update rate
41    pub adaptive_rate: bool,
42    /// Minimum update interval
43    pub min_update_interval: Duration,
44    /// Maximum update interval
45    pub max_update_interval: Duration,
46    /// The template for progress display
47    pub template: Option<String>,
48    /// Custom symbols for the progress bar
49    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/// Custom symbols for progress visualization
70#[derive(Debug, Clone)]
71pub struct ProgressSymbols {
72    /// Start of progress bar
73    pub start: String,
74    /// End of progress bar
75    pub end: String,
76    /// Filled section of progress bar
77    pub fill: String,
78    /// Empty section of progress bar
79    pub empty: String,
80    /// Spinner frames
81    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    /// Create symbols for block-style progress bar
103    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
125/// Enhanced progress tracker
126pub struct EnhancedProgressTracker {
127    /// The progress description
128    pub description: String,
129    /// Progress configuration
130    pub config: ProgressConfig,
131    /// Progress statistics
132    stats: Arc<Mutex<ProgressStats>>,
133    /// Start time
134    start_time: Instant,
135    /// Is the progress tracking active?
136    active: bool,
137    /// Should the progress bar be hidden?
138    hidden: bool,
139    /// Renderer for the progress bar
140    renderer: ProgressRenderer,
141}
142
143impl EnhancedProgressTracker {
144    /// Create a new progress tracker
145    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    /// Configure the progress tracker
162    pub fn with_config(mut self, config: ProgressConfig) -> Self {
163        self.config = config;
164        self
165    }
166
167    /// Use a specific progress style
168    pub const fn with_style(mut self, style: ProgressStyle) -> Self {
169        self.config.style = style;
170        self
171    }
172
173    /// Set custom symbols
174    pub fn with_symbols(mut self, symbols: ProgressSymbols) -> Self {
175        self.config.symbols = Some(symbols);
176        self
177    }
178
179    /// Show or hide ETA
180    pub const fn with_eta(mut self, show: bool) -> Self {
181        self.config.show_eta = show;
182        self
183    }
184
185    /// Show or hide statistics
186    pub const fn with_statistics(mut self, show: bool) -> Self {
187        self.config.show_statistics = show;
188        self
189    }
190
191    /// Start tracking progress
192    pub fn start(&mut self) {
193        self.active = true;
194        self.start_time = Instant::now();
195
196        // Initialize terminal if needed
197        if !self.hidden {
198            self.renderer.init();
199            self.render();
200        }
201    }
202
203    /// Update progress with current count
204    pub fn update(&mut self, processed: u64) {
205        if !self.active {
206            return;
207        }
208
209        let now = Instant::now();
210
211        // Update statistics
212        {
213            let mut stats = self.stats.lock().expect("Operation failed");
214            stats.update(processed, now);
215        }
216
217        // Determine if we should render an update
218        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    /// Increment progress by a specified amount
230    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    /// Finish progress tracking
244    pub fn finish(&mut self) {
245        if !self.active {
246            return;
247        }
248
249        self.active = false;
250
251        // Set processed to total
252        {
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        // Final render
262        if !self.hidden {
263            self.render();
264            self.renderer.finalize();
265        }
266    }
267
268    /// Hide the progress bar (useful for non-interactive environments)
269    pub fn hide(&mut self) {
270        self.hidden = true;
271    }
272
273    /// Show the progress bar
274    pub fn show(&mut self) {
275        self.hidden = false;
276
277        if self.active {
278            self.render();
279        }
280    }
281
282    /// Get the current progress statistics
283    pub fn stats(&self) -> ProgressStats {
284        self.stats.lock().expect("Operation failed").clone()
285    }
286
287    /// Determine if we should update based on fixed interval
288    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    /// Determine if we should update based on adaptive interval
295    fn should_update_adaptive(&self) -> bool {
296        let stats = self.stats.lock().expect("Operation failed");
297        let elapsed = stats.last_update.elapsed();
298
299        // Always update if we've exceeded the maximum interval
300        if elapsed >= self.config.max_update_interval {
301            return true;
302        }
303
304        // Always update if we've exceeded the minimum interval and progress has changed significantly
305        if elapsed >= self.config.min_update_interval {
306            // Calculate how much progress has been made
307            let progress_ratio = if stats.total > 0 {
308                stats.processed as f64 / stats.total as f64
309            } else {
310                0.0
311            };
312
313            // Adaptive update logic:
314            // - Update more frequently at the beginning and end
315            // - Update less frequently in the middle
316            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    /// Render the progress bar
329    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
383/// Builder for creating progress visualizations
384pub 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    /// Create a new progress builder
402    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    /// Set the progress style
421    pub const fn style(mut self, style: ProgressStyle) -> Self {
422        self.style = style;
423        self
424    }
425
426    /// Set the progress bar width
427    pub const fn width(mut self, width: usize) -> Self {
428        self.width = width;
429        self
430    }
431
432    /// Show or hide ETA
433    pub const fn show_eta(mut self, show: bool) -> Self {
434        self.show_eta = show;
435        self
436    }
437
438    /// Show or hide statistics
439    pub const fn show_statistics(mut self, show: bool) -> Self {
440        self.show_statistics = show;
441        self
442    }
443
444    /// Show or hide speed
445    pub const fn show_speed(mut self, show: bool) -> Self {
446        self.show_speed = show;
447        self
448    }
449
450    /// Enable or disable adaptive update rate
451    pub const fn adaptive_rate(mut self, enable: bool) -> Self {
452        self.adaptive_rate = enable;
453        self
454    }
455
456    /// Set the minimum update interval
457    pub const fn min_update_interval(mut self, interval: Duration) -> Self {
458        self.min_update_interval = interval;
459        self
460    }
461
462    /// Set the maximum update interval
463    pub const fn max_update_interval(mut self, interval: Duration) -> Self {
464        self.max_update_interval = interval;
465        self
466    }
467
468    /// Set a custom template
469    pub fn template(mut self, template: &str) -> Self {
470        self.template = Some(template.to_string());
471        self
472    }
473
474    /// Set custom symbols
475    pub fn symbols(mut self, symbols: ProgressSymbols) -> Self {
476        self.symbols = Some(symbols);
477        self
478    }
479
480    /// Hide the progress bar
481    pub const fn hidden(mut self, hidden: bool) -> Self {
482        self.hidden = hidden;
483        self
484    }
485
486    /// Build the progress tracker
487    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}