1use crate::Result;
4use std::sync::Arc;
5
6#[async_trait::async_trait]
8pub trait ProgressReporter: Send + Sync {
9 async fn start(&self, message: &str, total: Option<u64>);
11
12 async fn update(&self, position: u64, message: Option<&str>);
14
15 async fn increment(&self, delta: u64);
17
18 async fn finish(&self, message: &str);
20
21 async fn finish_with_error(&self, message: &str);
23
24 async fn set_total(&self, total: u64);
26}
27
28#[derive(Debug, Clone)]
30pub struct ProgressStyle {
31 pub template: String,
33 pub progress_chars: String,
35 pub show_elapsed: bool,
37 pub show_eta: bool,
39 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 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 pub fn detailed() -> Self {
69 Self::default()
70 }
71
72 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#[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 pub fn new(style: ProgressStyle) -> Self {
95 Self {
96 bar: std::sync::Mutex::new(None),
97 style,
98 }
99 }
100
101 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 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
176pub 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
189pub 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
206pub struct ProgressContext {
208 reporter: Arc<dyn ProgressReporter>,
209 enabled: bool,
210}
211
212impl ProgressContext {
213 pub fn new(reporter: Arc<dyn ProgressReporter>, enabled: bool) -> Self {
215 Self { reporter, enabled }
216 }
217
218 pub fn disabled() -> Self {
220 Self {
221 reporter: Arc::new(NoOpProgressReporter),
222 enabled: false,
223 }
224 }
225
226 pub fn reporter(&self) -> &Arc<dyn ProgressReporter> {
228 &self.reporter
229 }
230
231 pub fn is_enabled(&self) -> bool {
233 self.enabled
234 }
235
236 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 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 pub async fn increment(&self, delta: u64) -> Result<()> {
254 if self.enabled {
255 self.reporter.increment(delta).await;
256 }
257 Ok(())
258 }
259
260 pub async fn finish(&self, message: &str) -> Result<()> {
262 if self.enabled {
263 self.reporter.finish(message).await;
264 }
265 Ok(())
266 }
267}