ipfrs_cli/
progress.rs

1//! Progress bar and spinner utilities for IPFRS CLI
2
3#![allow(dead_code)]
4
5use indicatif::{ProgressBar, ProgressStyle};
6use std::time::Duration;
7
8/// Create a progress bar for file operations
9pub fn file_progress(total: u64, message: &str) -> ProgressBar {
10    let pb = ProgressBar::new(total);
11    pb.set_style(
12        ProgressStyle::default_bar()
13            .template("{spinner:.green} {msg} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec})")
14            .expect("valid template")
15            .progress_chars("=>-"),
16    );
17    pb.set_message(message.to_string());
18    pb.enable_steady_tick(Duration::from_millis(100));
19    pb
20}
21
22/// Create a progress bar for block operations
23pub fn block_progress(total: u64, message: &str) -> ProgressBar {
24    let pb = ProgressBar::new(total);
25    pb.set_style(
26        ProgressStyle::default_bar()
27            .template("{spinner:.green} {msg} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} blocks")
28            .expect("valid template")
29            .progress_chars("=>-"),
30    );
31    pb.set_message(message.to_string());
32    pb.enable_steady_tick(Duration::from_millis(100));
33    pb
34}
35
36/// Create a spinner for operations with unknown duration
37pub fn spinner(message: &str) -> ProgressBar {
38    let pb = ProgressBar::new_spinner();
39    pb.set_style(
40        ProgressStyle::default_spinner()
41            .template("{spinner:.green} {msg} [{elapsed_precise}]")
42            .expect("valid template"),
43    );
44    pb.set_message(message.to_string());
45    pb.enable_steady_tick(Duration::from_millis(100));
46    pb
47}
48
49/// Create a download progress bar
50pub fn download_progress(total: u64, filename: &str) -> ProgressBar {
51    let pb = ProgressBar::new(total);
52    pb.set_style(
53        ProgressStyle::default_bar()
54            .template("{spinner:.green} Downloading {msg} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
55            .expect("valid template")
56            .progress_chars("=>-"),
57    );
58    pb.set_message(filename.to_string());
59    pb.enable_steady_tick(Duration::from_millis(100));
60    pb
61}
62
63/// Create an upload progress bar
64pub fn upload_progress(total: u64, filename: &str) -> ProgressBar {
65    let pb = ProgressBar::new(total);
66    pb.set_style(
67        ProgressStyle::default_bar()
68            .template("{spinner:.green} Uploading {msg} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
69            .expect("valid template")
70            .progress_chars("=>-"),
71    );
72    pb.set_message(filename.to_string());
73    pb.enable_steady_tick(Duration::from_millis(100));
74    pb
75}
76
77/// Create a multi-progress indicator for batch operations
78pub fn batch_progress(total: u64, message: &str) -> ProgressBar {
79    let pb = ProgressBar::new(total);
80    pb.set_style(
81        ProgressStyle::default_bar()
82            .template("{spinner:.green} {msg} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({percent}%)")
83            .expect("valid template")
84            .progress_chars("=>-"),
85    );
86    pb.set_message(message.to_string());
87    pb.enable_steady_tick(Duration::from_millis(100));
88    pb
89}
90
91/// Finish progress bar with success message
92pub fn finish_success(pb: &ProgressBar, message: &str) {
93    pb.set_style(
94        ProgressStyle::default_bar()
95            .template("{msg}")
96            .expect("valid template"),
97    );
98    pb.finish_with_message(format!("\x1b[32m✓\x1b[0m {}", message));
99}
100
101/// Finish progress bar with error message
102pub fn finish_error(pb: &ProgressBar, message: &str) {
103    pb.set_style(
104        ProgressStyle::default_bar()
105            .template("{msg}")
106            .expect("valid template"),
107    );
108    pb.finish_with_message(format!("\x1b[31m✗\x1b[0m {}", message));
109}
110
111/// Finish spinner with success
112pub fn finish_spinner_success(pb: &ProgressBar, message: &str) {
113    pb.finish_with_message(format!("\x1b[32m✓\x1b[0m {}", message));
114}
115
116/// Finish spinner with error
117pub fn finish_spinner_error(pb: &ProgressBar, message: &str) {
118    pb.finish_with_message(format!("\x1b[31m✗\x1b[0m {}", message));
119}
120
121/// Progress tracker for streaming operations
122pub struct StreamProgress {
123    pb: ProgressBar,
124    total: u64,
125    current: u64,
126}
127
128impl StreamProgress {
129    /// Create a new stream progress tracker
130    pub fn new(total: u64, message: &str) -> Self {
131        let pb = file_progress(total, message);
132        Self {
133            pb,
134            total,
135            current: 0,
136        }
137    }
138
139    /// Update progress with bytes written/read
140    pub fn update(&mut self, bytes: u64) {
141        self.current += bytes;
142        self.pb.set_position(self.current);
143    }
144
145    /// Set absolute position
146    pub fn set_position(&mut self, pos: u64) {
147        self.current = pos;
148        self.pb.set_position(pos);
149    }
150
151    /// Get current progress percentage
152    pub fn percentage(&self) -> f64 {
153        if self.total == 0 {
154            100.0
155        } else {
156            (self.current as f64 / self.total as f64) * 100.0
157        }
158    }
159
160    /// Finish with success
161    pub fn finish(self, message: &str) {
162        finish_success(&self.pb, message);
163    }
164
165    /// Finish with error
166    pub fn finish_error(self, message: &str) {
167        finish_error(&self.pb, message);
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_spinner_creation() {
177        let pb = spinner("Testing...");
178        pb.finish_with_message("Done");
179    }
180
181    #[test]
182    fn test_progress_creation() {
183        let pb = file_progress(1000, "Processing");
184        pb.set_position(500);
185        pb.finish_with_message("Complete");
186    }
187
188    #[test]
189    fn test_stream_progress() {
190        let mut sp = StreamProgress::new(100, "Streaming");
191        sp.update(25);
192        assert_eq!(sp.percentage(), 25.0);
193        sp.update(25);
194        assert_eq!(sp.percentage(), 50.0);
195        sp.finish("Done");
196    }
197}