docker_image_pusher/registry/
progress.rs

1//! Progress tracking for uploads
2
3use crate::logging::Logger;
4use std::time::{Duration, Instant};
5
6#[derive(Clone)]
7pub struct ProgressTracker {
8    total_size: u64,
9    start_time: Instant,
10    last_update: Instant,
11    last_uploaded: u64,
12    output: Logger,
13    operation_name: String,
14}
15
16impl ProgressTracker {
17    pub fn new(total_size: u64, output: Logger, operation_name: String) -> Self {
18        Self {
19            total_size,
20            start_time: Instant::now(),
21            last_update: Instant::now(),
22            last_uploaded: 0,
23            output,
24            operation_name,
25        }
26    }
27
28    pub fn update(&mut self, uploaded: u64) {
29        let now = Instant::now();
30        let elapsed_since_last = now.duration_since(self.last_update);
31
32        // Update progress every 5 seconds or 10MB or every 5% of total
33        let size_threshold = std::cmp::min(10 * 1024 * 1024, self.total_size / 20); // 10MB or 5% of total
34
35        if elapsed_since_last >= Duration::from_secs(5)
36            || uploaded - self.last_uploaded >= size_threshold
37            || uploaded == self.total_size
38        {
39            let percent = (uploaded as f64 / self.total_size as f64 * 100.0) as u8;
40            let speed = if elapsed_since_last.as_secs() > 0 {
41                (uploaded - self.last_uploaded) / elapsed_since_last.as_secs()
42            } else {
43                0
44            };
45            self.output.progress(&format!(
46                "{}: {}% ({}/{}) - {} MB/s",
47                self.operation_name,
48                percent,
49                self.output.format_size(uploaded),
50                self.output.format_size(self.total_size),
51                speed / 1024 / 1024
52            ));
53            self.last_update = now;
54            self.last_uploaded = uploaded;
55        }
56    }
57
58    pub fn finish(&self) {
59        self.output.progress(&format!(
60            "{}: 100% ({}/{})",
61            self.operation_name,
62            self.output.format_size(self.total_size),
63            self.output.format_size(self.total_size)
64        ));
65
66        let total_elapsed = self.start_time.elapsed();
67        let avg_speed = if total_elapsed.as_secs() > 0 {
68            self.total_size / total_elapsed.as_secs()
69        } else {
70            self.total_size
71        };
72
73        self.output.success(&format!(
74            "{} completed in {} (avg speed: {})",
75            self.operation_name,
76            self.output.format_duration(total_elapsed),
77            self.output.format_size(avg_speed)
78        ));
79    }
80
81    pub fn set_phase(&mut self, phase: &str) {
82        self.operation_name = format!("{} - {}", self.operation_name, phase);
83    }
84
85    pub fn get_elapsed_time(&self) -> Duration {
86        self.start_time.elapsed()
87    }
88
89    pub fn get_estimated_remaining(&self, uploaded: u64) -> Option<Duration> {
90        let elapsed = self.start_time.elapsed();
91        if uploaded > 0 && elapsed.as_secs() > 0 {
92            let speed = uploaded / elapsed.as_secs();
93            if speed > 0 {
94                let remaining_bytes = self.total_size.saturating_sub(uploaded);
95                return Some(Duration::from_secs(remaining_bytes / speed));
96            }
97        }
98        None
99    }
100}