sf_cli/
progress.rs

1//! Progress tracking for file operations
2
3use indicatif::{ProgressBar, ProgressStyle};
4use std::time::Duration;
5
6/// Progress tracker for file operations
7pub struct ProgressTracker {
8    bar: ProgressBar,
9}
10
11impl ProgressTracker {
12    /// Create a new progress tracker with total size
13    pub fn new(total_size: u64, operation: &str) -> Self {
14        let bar = ProgressBar::new(total_size);
15        bar.set_style(
16            ProgressStyle::default_bar()
17                .template(&format!(
18                    "{} [{{elapsed_precise}}] [{{bar:40.cyan/blue}}] {{bytes}}/{{total_bytes}} ({{eta}})",
19                    operation
20                ))
21                .unwrap()
22                .progress_chars("█▉▊▋▌▍▎▏  "),
23        );
24        bar.enable_steady_tick(Duration::from_millis(100));
25
26        Self { bar }
27    }
28
29    /// Create a spinner for operations without known size
30    pub fn new_spinner(operation: &str) -> Self {
31        let bar = ProgressBar::new_spinner();
32        bar.set_style(
33            ProgressStyle::default_spinner()
34                .template(&format!("{} [{{elapsed_precise}}] {{spinner}} {{msg}}", operation))
35                .unwrap(),
36        );
37        bar.enable_steady_tick(Duration::from_millis(100));
38
39        Self { bar }
40    }
41
42    /// Update progress by adding bytes processed
43    pub fn inc(&self, bytes: u64) {
44        self.bar.inc(bytes);
45    }
46
47    /// Set current position
48    pub fn set_position(&self, pos: u64) {
49        self.bar.set_position(pos);
50    }
51
52    /// Set message for spinner
53    pub fn set_message(&self, msg: &str) {
54        self.bar.set_message(msg.to_string());
55    }
56
57    /// Mark operation as finished
58    pub fn finish(&self, message: &str) {
59        self.bar.finish_with_message(message.to_string());
60    }
61
62    /// Mark operation as finished and clear
63    pub fn finish_and_clear(&self) {
64        self.bar.finish_and_clear();
65    }
66}
67
68impl Drop for ProgressTracker {
69    fn drop(&mut self) {
70        self.bar.finish_and_clear();
71    }
72}
73
74/// Progress callback function type
75pub type ProgressCallback = Box<dyn Fn(u64, u64) + Send + Sync>;
76
77/// Create a progress callback that updates a progress tracker
78pub fn create_progress_callback(tracker: &ProgressTracker) -> impl Fn(u64) + '_ {
79    move |bytes_processed| {
80        tracker.inc(bytes_processed);
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use std::thread;
88    use std::time::Duration;
89
90    #[test]
91    fn test_progress_tracker() {
92        let tracker = ProgressTracker::new(1000, "Testing");
93        
94        // Simulate progress
95        for _i in 0..10 {
96            tracker.inc(100);
97            thread::sleep(Duration::from_millis(10));
98        }
99        
100        tracker.finish("Complete");
101    }
102
103    #[test]
104    fn test_spinner() {
105        let tracker = ProgressTracker::new_spinner("Processing");
106        
107        tracker.set_message("Working...");
108        thread::sleep(Duration::from_millis(50));
109        
110        tracker.set_message("Almost done...");
111        thread::sleep(Duration::from_millis(50));
112        
113        tracker.finish("Done");
114    }
115}