vx_installer/
progress.rs

1//! Progress reporting utilities for installation operations
2
3use crate::Result;
4use std::sync::Arc;
5
6/// Progress reporting interface for installation operations
7#[async_trait::async_trait]
8pub trait ProgressReporter: Send + Sync {
9    /// Start a new progress operation
10    async fn start(&self, message: &str, total: Option<u64>);
11
12    /// Update progress with current position
13    async fn update(&self, position: u64, message: Option<&str>);
14
15    /// Increment progress by a delta
16    async fn increment(&self, delta: u64);
17
18    /// Finish the progress operation
19    async fn finish(&self, message: &str);
20
21    /// Finish with an error message
22    async fn finish_with_error(&self, message: &str);
23
24    /// Set the total size (useful when total is unknown initially)
25    async fn set_total(&self, total: u64);
26}
27
28/// Progress style configuration
29#[derive(Debug, Clone)]
30pub struct ProgressStyle {
31    /// Template for progress display
32    pub template: String,
33    /// Characters used for progress bar
34    pub progress_chars: String,
35    /// Whether to show elapsed time
36    pub show_elapsed: bool,
37    /// Whether to show ETA
38    pub show_eta: bool,
39    /// Whether to show transfer rate
40    pub show_rate: bool,
41}
42
43impl Default for ProgressStyle {
44    fn default() -> Self {
45        Self {
46            template: "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})".to_string(),
47            progress_chars: "#>-".to_string(),
48            show_elapsed: true,
49            show_eta: true,
50            show_rate: true,
51        }
52    }
53}
54
55impl ProgressStyle {
56    /// Create a simple progress style
57    pub fn simple() -> Self {
58        Self {
59            template: "{wide_bar} {pos}/{len}".to_string(),
60            progress_chars: "=>-".to_string(),
61            show_elapsed: false,
62            show_eta: false,
63            show_rate: false,
64        }
65    }
66
67    /// Create a detailed progress style with all information
68    pub fn detailed() -> Self {
69        Self::default()
70    }
71
72    /// Create a minimal progress style
73    pub fn minimal() -> Self {
74        Self {
75            template: "{spinner} {msg}".to_string(),
76            progress_chars: "⠁⠂⠄⡀⢀⠠⠐⠈".to_string(),
77            show_elapsed: false,
78            show_eta: false,
79            show_rate: false,
80        }
81    }
82}
83
84/// Console-based progress reporter using indicatif
85#[cfg(feature = "progress")]
86pub struct ConsoleProgressReporter {
87    bar: std::sync::Mutex<Option<indicatif::ProgressBar>>,
88    style: ProgressStyle,
89}
90
91#[cfg(feature = "progress")]
92impl ConsoleProgressReporter {
93    /// Create a new console progress reporter
94    pub fn new(style: ProgressStyle) -> Self {
95        Self {
96            bar: std::sync::Mutex::new(None),
97            style,
98        }
99    }
100
101    /// Create with default style
102    pub fn default() -> Self {
103        Self::new(ProgressStyle::default())
104    }
105}
106
107#[cfg(feature = "progress")]
108#[async_trait::async_trait]
109impl ProgressReporter for ConsoleProgressReporter {
110    async fn start(&self, message: &str, total: Option<u64>) {
111        use indicatif::{ProgressBar, ProgressStyle as IndicatifStyle};
112
113        let bar = if let Some(total) = total {
114            ProgressBar::new(total)
115        } else {
116            ProgressBar::new_spinner()
117        };
118
119        let style = IndicatifStyle::with_template(&self.style.template)
120            .unwrap_or_else(|_| IndicatifStyle::default_bar())
121            .progress_chars(&self.style.progress_chars);
122
123        bar.set_style(style);
124        bar.set_message(message.to_string());
125
126        // Store the bar
127        if let Ok(mut bar_guard) = self.bar.lock() {
128            *bar_guard = Some(bar);
129        }
130    }
131
132    async fn update(&self, position: u64, message: Option<&str>) {
133        if let Ok(bar_guard) = self.bar.lock() {
134            if let Some(ref bar) = *bar_guard {
135                bar.set_position(position);
136                if let Some(msg) = message {
137                    bar.set_message(msg.to_string());
138                }
139            }
140        }
141    }
142
143    async fn increment(&self, delta: u64) {
144        if let Ok(bar_guard) = self.bar.lock() {
145            if let Some(ref bar) = *bar_guard {
146                bar.inc(delta);
147            }
148        }
149    }
150
151    async fn finish(&self, message: &str) {
152        if let Ok(mut bar_guard) = self.bar.lock() {
153            if let Some(bar) = bar_guard.take() {
154                bar.finish_with_message(message.to_string());
155            }
156        }
157    }
158
159    async fn finish_with_error(&self, message: &str) {
160        if let Ok(mut bar_guard) = self.bar.lock() {
161            if let Some(bar) = bar_guard.take() {
162                bar.finish_with_message(format!("❌ {}", message));
163            }
164        }
165    }
166
167    async fn set_total(&self, total: u64) {
168        if let Ok(bar_guard) = self.bar.lock() {
169            if let Some(ref bar) = *bar_guard {
170                bar.set_length(total);
171            }
172        }
173    }
174}
175
176/// No-op progress reporter for when progress reporting is disabled
177pub struct NoOpProgressReporter;
178
179#[async_trait::async_trait]
180impl ProgressReporter for NoOpProgressReporter {
181    async fn start(&self, _message: &str, _total: Option<u64>) {}
182    async fn update(&self, _position: u64, _message: Option<&str>) {}
183    async fn increment(&self, _delta: u64) {}
184    async fn finish(&self, _message: &str) {}
185    async fn finish_with_error(&self, _message: &str) {}
186    async fn set_total(&self, _total: u64) {}
187}
188
189/// Create a progress reporter based on configuration
190pub fn create_progress_reporter(style: ProgressStyle, enabled: bool) -> Arc<dyn ProgressReporter> {
191    if enabled {
192        #[cfg(feature = "progress")]
193        {
194            Arc::new(ConsoleProgressReporter::new(style))
195        }
196        #[cfg(not(feature = "progress"))]
197        {
198            let _ = style;
199            Arc::new(NoOpProgressReporter)
200        }
201    } else {
202        Arc::new(NoOpProgressReporter)
203    }
204}
205
206/// Progress context for tracking multiple operations
207pub struct ProgressContext {
208    reporter: Arc<dyn ProgressReporter>,
209    enabled: bool,
210}
211
212impl ProgressContext {
213    /// Create a new progress context
214    pub fn new(reporter: Arc<dyn ProgressReporter>, enabled: bool) -> Self {
215        Self { reporter, enabled }
216    }
217
218    /// Create a disabled progress context
219    pub fn disabled() -> Self {
220        Self {
221            reporter: Arc::new(NoOpProgressReporter),
222            enabled: false,
223        }
224    }
225
226    /// Get the progress reporter
227    pub fn reporter(&self) -> &Arc<dyn ProgressReporter> {
228        &self.reporter
229    }
230
231    /// Check if progress reporting is enabled
232    pub fn is_enabled(&self) -> bool {
233        self.enabled
234    }
235
236    /// Start a new progress operation
237    pub async fn start(&self, message: &str, total: Option<u64>) -> Result<()> {
238        if self.enabled {
239            self.reporter.start(message, total).await;
240        }
241        Ok(())
242    }
243
244    /// Update progress
245    pub async fn update(&self, position: u64, message: Option<&str>) -> Result<()> {
246        if self.enabled {
247            self.reporter.update(position, message).await;
248        }
249        Ok(())
250    }
251
252    /// Increment progress
253    pub async fn increment(&self, delta: u64) -> Result<()> {
254        if self.enabled {
255            self.reporter.increment(delta).await;
256        }
257        Ok(())
258    }
259
260    /// Finish progress
261    pub async fn finish(&self, message: &str) -> Result<()> {
262        if self.enabled {
263            self.reporter.finish(message).await;
264        }
265        Ok(())
266    }
267}