1use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8use tokio::sync::broadcast;
9use tracing::{debug, info, warn};
10
11#[derive(Clone)]
13pub struct ProgressReporter {
14 progress: Arc<Mutex<ProgressState>>,
15 events: broadcast::Sender<ProgressEvent>,
16}
17
18#[derive(Debug, Clone)]
20pub struct ProgressState {
21 pub current_step: String,
22 pub step_progress: f64,
23 pub total_steps: usize,
24 pub completed_steps: usize,
25 pub current_operation: String,
26 pub start_time: Instant,
27 pub estimated_duration: Option<Duration>,
28 pub bytes_processed: u64,
29 pub total_bytes: u64,
30 pub items_processed: usize,
31 pub total_items: usize,
32 pub is_cancelled: bool,
33 pub error: Option<String>,
34}
35
36#[derive(Debug, Clone)]
38pub enum ProgressEvent {
39 StepStarted {
40 step: String,
41 step_number: usize,
42 },
43 StepProgress {
44 progress: f64,
45 message: String,
46 },
47 StepCompleted {
48 step: String,
49 duration_ms: u64,
50 },
51 OverallProgress {
52 percent: f64,
53 message: String,
54 },
55 DataProcessed {
56 bytes: u64,
57 total: u64,
58 },
59 ItemProcessed {
60 item: String,
61 current: usize,
62 total: usize,
63 },
64 Error {
65 message: String,
66 step: String,
67 },
68 Completed {
69 total_duration_ms: u64,
70 },
71 Cancelled,
72}
73
74#[derive(Debug, Clone, serde::Serialize)]
76pub struct InstallationPlan {
77 pub pack_id: String,
78 pub total_size_mb: f64,
79 pub estimated_duration_seconds: u64,
80 pub total_dependencies: usize,
81 pub steps: Vec<PlanStep>,
82 pub cache_status: CacheStatus,
83}
84
85#[derive(Debug, Clone, serde::Serialize)]
86pub struct PlanStep {
87 pub step_number: usize,
88 pub name: String,
89 pub description: String,
90 pub estimated_duration_ms: u64,
91 pub size_mb: f64,
92}
93
94#[derive(Debug, Clone, serde::Serialize)]
95pub struct CacheStatus {
96 pub is_cached: bool,
97 pub cached_size_mb: Option<f64>,
98 pub cache_hit: bool,
99}
100
101impl ProgressReporter {
102 pub fn new() -> Self {
104 let (tx, _) = broadcast::channel(100);
105 Self {
106 progress: Arc::new(Mutex::new(ProgressState::new())),
107 events: tx,
108 }
109 }
110
111 pub fn for_operation(operation_name: &str) -> Self {
113 let reporter = Self::new();
114 reporter.start_operation(operation_name);
115 reporter
116 }
117
118 pub fn start_operation(&self, operation_name: &str) {
120 let mut state = self.progress.lock().unwrap();
121 state.current_operation = operation_name.to_string();
122 state.start_time = Instant::now();
123 state.current_step = "Initializing".to_string();
124 state.step_progress = 0.0;
125 state.is_cancelled = false;
126 state.error = None;
127
128 debug!("Starting operation: {}", operation_name);
129 self.broadcast_event(ProgressEvent::StepStarted {
130 step: "Initializing".to_string(),
131 step_number: 0,
132 });
133 }
134
135 pub fn start_step(&self, step_name: &str, step_number: usize) {
137 let mut state = self.progress.lock().unwrap();
138 state.current_step = step_name.to_string();
139 state.step_progress = 0.0;
140
141 info!("Starting step {}: {}", step_number, step_name);
142 self.broadcast_event(ProgressEvent::StepStarted {
143 step: step_name.to_string(),
144 step_number,
145 });
146 }
147
148 pub fn update_step_progress(&self, progress: f64, message: &str) {
150 let mut state = self.progress.lock().unwrap();
151 state.step_progress = progress.clamp(0.0, 100.0);
152
153 let overall_progress = if state.total_steps > 0 {
154 (state.completed_steps as f64 + progress / 100.0) / state.total_steps as f64 * 100.0
155 } else {
156 progress
157 };
158
159 self.broadcast_event(ProgressEvent::StepProgress {
160 progress,
161 message: message.to_string(),
162 });
163
164 self.broadcast_event(ProgressEvent::OverallProgress {
165 percent: overall_progress.clamp(0.0, 100.0),
166 message: format!("{}: {}%", state.current_step, overall_progress as u32),
167 });
168 }
169
170 pub fn update_data_progress(&self, bytes_processed: u64, total_bytes: u64) {
172 let mut state = self.progress.lock().unwrap();
173 state.bytes_processed = bytes_processed;
174 state.total_bytes = total_bytes;
175
176 let _progress = if total_bytes > 0 {
177 (bytes_processed as f64 / total_bytes as f64) * 100.0
178 } else {
179 0.0
180 };
181
182 self.broadcast_event(ProgressEvent::DataProcessed {
183 bytes: bytes_processed,
184 total: total_bytes,
185 });
186 }
187
188 pub fn update_item_progress(&self, item: &str, current: usize, total: usize) {
190 let mut state = self.progress.lock().unwrap();
191 state.items_processed = current;
192 state.total_items = total;
193
194 self.broadcast_event(ProgressEvent::ItemProcessed {
195 item: item.to_string(),
196 current,
197 total,
198 });
199 }
200
201 pub fn complete_step(&self, step_name: &str) {
203 let mut state = self.progress.lock().unwrap();
204 state.completed_steps += 1;
205 state.step_progress = 100.0;
206
207 let duration = state.start_time.elapsed();
208 info!("Completed step {}: {}ms", step_name, duration.as_millis());
209
210 self.broadcast_event(ProgressEvent::StepCompleted {
211 step: step_name.to_string(),
212 duration_ms: duration.as_millis() as u64,
213 });
214 }
215
216 pub fn report_error(&self, message: &str, step: &str) {
218 let mut state = self.progress.lock().unwrap();
219 state.error = Some(message.to_string());
220 state.is_cancelled = true;
221
222 warn!("Error in step {}: {}", step, message);
223 self.broadcast_event(ProgressEvent::Error {
224 message: message.to_string(),
225 step: step.to_string(),
226 });
227 }
228
229 pub fn complete(&self) {
231 let state = self.progress.lock().unwrap();
232 let total_duration = state.start_time.elapsed();
233
234 info!("Operation completed in {}ms", total_duration.as_millis());
235 self.broadcast_event(ProgressEvent::Completed {
236 total_duration_ms: total_duration.as_millis() as u64,
237 });
238 }
239
240 pub fn cancel(&self) {
242 let mut state = self.progress.lock().unwrap();
243 state.is_cancelled = true;
244
245 warn!("Operation cancelled");
246 self.broadcast_event(ProgressEvent::Cancelled);
247 }
248
249 pub fn is_cancelled(&self) -> bool {
251 let state = self.progress.lock().unwrap();
252 state.is_cancelled
253 }
254
255 pub fn get_state(&self) -> ProgressState {
257 self.progress.lock().unwrap().clone()
258 }
259
260 pub fn set_total_steps(&self, total: usize) {
262 let mut state = self.progress.lock().unwrap();
263 state.total_steps = total;
264 info!("Total steps for operation: {}", total);
265 }
266
267 pub fn set_estimated_duration(&self, duration: Duration) {
269 let mut state = self.progress.lock().unwrap();
270 state.estimated_duration = Some(duration);
271 }
272
273 pub fn subscribe(&self) -> broadcast::Receiver<ProgressEvent> {
275 self.events.subscribe()
276 }
277
278 fn broadcast_event(&self, event: ProgressEvent) {
280 let _ = self.events.send(event);
281 }
282}
283
284impl ProgressState {
285 pub fn new() -> Self {
287 Self {
288 current_step: "Not started".to_string(),
289 step_progress: 0.0,
290 total_steps: 0,
291 completed_steps: 0,
292 current_operation: "Unknown".to_string(),
293 start_time: Instant::now(),
294 estimated_duration: None,
295 bytes_processed: 0,
296 total_bytes: 0,
297 items_processed: 0,
298 total_items: 0,
299 is_cancelled: false,
300 error: None,
301 }
302 }
303
304 pub fn overall_progress(&self) -> f64 {
306 if self.total_steps == 0 {
307 self.step_progress
308 } else {
309 ((self.completed_steps as f64 + self.step_progress / 100.0) / self.total_steps as f64)
310 * 100.0
311 }
312 }
313
314 pub fn elapsed(&self) -> Duration {
316 self.start_time.elapsed()
317 }
318
319 pub fn estimated_time_remaining(&self) -> Option<Duration> {
321 if let Some(estimated) = self.estimated_duration {
322 Some(estimated)
323 } else if self.total_steps > 0 && self.completed_steps > 0 {
324 let elapsed = self.elapsed();
325 let avg_step_time = elapsed / self.completed_steps as u32;
326 let remaining_steps = self.total_steps - self.completed_steps;
327 Some(avg_step_time * remaining_steps as u32)
328 } else {
329 None
330 }
331 }
332
333 pub fn is_completed(&self) -> bool {
335 self.total_steps > 0 && self.completed_steps >= self.total_steps
336 }
337}
338
339pub struct ProgressDisplay {
341 reporter: ProgressReporter,
342 show_detailed: bool,
343}
344
345impl ProgressDisplay {
346 pub fn new(reporter: ProgressReporter, show_detailed: bool) -> Self {
347 Self {
348 reporter,
349 show_detailed,
350 }
351 }
352
353 pub fn display(&self) {
355 let state = self.reporter.get_state();
356
357 if self.show_detailed {
358 println!(
359 "📦 {} - {:.1}% complete",
360 state.current_operation,
361 state.overall_progress()
362 );
363 println!(
364 " Step: {} ({:.1}%)",
365 state.current_step, state.step_progress
366 );
367 println!(
368 " Progress: {}/{} steps completed",
369 state.completed_steps, state.total_steps
370 );
371
372 if state.total_bytes > 0 {
373 println!(
374 " Data: {}/{} MB ({:.1}%)",
375 state.bytes_processed / 1_048_576,
376 state.total_bytes / 1_048_576,
377 (state.bytes_processed as f64 / state.total_bytes as f64) * 100.0
378 );
379 }
380
381 if let Some(remaining) = state.estimated_time_remaining() {
382 println!(" Estimated remaining: {:.0}s", remaining.as_secs_f64());
383 }
384 } else {
385 println!(
386 "📦 {}: {:.1}% - {} ({}/{})",
387 state.current_operation,
388 state.overall_progress(),
389 state.current_step,
390 state.completed_steps,
391 state.total_steps
392 );
393 }
394 }
395
396 pub fn display_bar(&self) {
398 let state = self.reporter.get_state();
399 let overall = state.overall_progress();
400 let bar_width = 40;
401
402 let filled_width = (overall / 100.0 * bar_width as f64) as usize;
403 let empty_width = bar_width - filled_width;
404
405 let filled = "â–ˆ".repeat(filled_width);
406 let empty = "â–‘".repeat(empty_width);
407
408 println!(
409 "📦 {} |{}{}| {:.1}% ({}/{})",
410 state.current_operation,
411 filled,
412 empty,
413 overall,
414 state.completed_steps,
415 state.total_steps
416 );
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_progress_state_creation() {
426 let state = ProgressState::new();
427 assert_eq!(state.current_step, "Not started");
428 assert_eq!(state.step_progress, 0.0);
429 assert_eq!(state.total_steps, 0);
430 assert_eq!(state.completed_steps, 0);
431 assert!(!state.is_cancelled);
432 assert!(state.error.is_none());
433 }
434
435 #[test]
436 fn test_progress_calculation() {
437 let mut state = ProgressState::new();
438 state.total_steps = 5;
439 state.completed_steps = 2;
440 state.step_progress = 50.0;
441
442 assert_eq!(state.overall_progress(), 50.0); }
444
445 #[test]
446 fn test_completion_check() {
447 let mut state = ProgressState::new();
448 assert!(!state.is_completed());
449
450 state.total_steps = 3;
451 state.completed_steps = 2;
452 assert!(!state.is_completed());
453
454 state.completed_steps = 3;
455 assert!(state.is_completed());
456 }
457
458 #[tokio::test]
459 async fn test_progress_reporter() {
460 let reporter = ProgressReporter::new();
461
462 reporter.start_test_operation("test");
463 reporter.set_total_steps(3);
464
465 reporter.start_step("Step 1", 1);
467 reporter.update_step_progress(25.0, "Processing...");
468 reporter.complete_step("Step 1");
469
470 reporter.start_step("Step 2", 2);
471 reporter.update_step_progress(75.0, "Almost done");
472 reporter.complete_step("Step 2");
473
474 reporter.start_step("Step 3", 3);
475 reporter.update_step_progress(100.0, "Complete");
476 reporter.complete_step("Step 3");
477
478 let state = reporter.get_state();
479 assert_eq!(state.completed_steps, 3);
480 assert_eq!(state.total_steps, 3);
481 assert!(state.is_completed());
482 }
483
484 impl ProgressReporter {
485 fn start_test_operation(&self, operation_name: &str) {
487 self.start_operation(operation_name);
488 }
489 }
490}