use std::fmt;
use crate::config::fetch_options::FetchPhase;
#[derive(Debug, Clone, PartialEq)]
pub struct Progress {
pub phase: FetchPhase,
pub bytes_downloaded: u64,
pub total_bytes: Option<u64>,
pub retry_count: u32,
pub performance_metrics: Option<PerformanceMetrics>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PerformanceMetrics {
pub current_rate_bps: Option<f64>,
pub average_rate_bps: Option<f64>,
pub bandwidth_limit_bps: Option<u64>,
pub bandwidth_utilization: Option<f64>,
pub phase_timings: PhaseTimings,
pub rate_adjustments: u32,
pub network_latency_ms: Option<u64>,
pub connection_time_ms: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PhaseTimings {
pub connecting_ms: u64,
pub downloading_ms: u64,
pub verifying_ms: u64,
pub committing_ms: u64,
}
impl PhaseTimings {
#[must_use]
pub fn total_ms(&self) -> u64 {
self.connecting_ms + self.downloading_ms + self.verifying_ms + self.committing_ms
}
}
impl Progress {
#[must_use]
pub fn percentage(&self) -> Option<f64> {
self.total_bytes.map(|total| {
if total == 0 {
if self.is_completed() { 100.0 } else { 0.0 }
} else {
(self.bytes_downloaded as f64 / total as f64) * 100.0
}
})
}
#[must_use]
pub fn is_completed(&self) -> bool {
self.phase == FetchPhase::Completed
}
#[must_use]
pub fn is_retrying(&self) -> bool {
self.retry_count > 0
}
}
impl fmt::Display for Progress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.percentage() {
Some(pct) => write!(
f,
"{}: {:.1}% ({}/{} bytes, retry {})",
self.phase,
pct,
self.bytes_downloaded,
self.total_bytes.unwrap_or(0),
self.retry_count
),
None => write!(
f,
"{}: {}/{} bytes (retry {})",
self.phase,
self.bytes_downloaded,
self.total_bytes.unwrap_or(0),
self.retry_count
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_percentage() {
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 50,
total_bytes: Some(100),
retry_count: 0,
performance_metrics: None,
};
assert_eq!(progress.percentage(), Some(50.0));
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 0,
total_bytes: Some(0),
retry_count: 0,
performance_metrics: None,
};
assert_eq!(progress.percentage(), Some(0.0));
let progress = Progress {
phase: FetchPhase::Completed,
bytes_downloaded: 0,
total_bytes: Some(0),
retry_count: 0,
performance_metrics: None,
};
assert_eq!(progress.percentage(), Some(100.0));
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 50,
total_bytes: None,
retry_count: 0,
performance_metrics: None,
};
assert_eq!(progress.percentage(), None);
}
#[test]
fn test_is_completed() {
let progress = Progress {
phase: FetchPhase::Completed,
bytes_downloaded: 100,
total_bytes: Some(100),
retry_count: 0,
performance_metrics: None,
};
assert!(progress.is_completed());
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 100,
total_bytes: Some(100),
retry_count: 0,
performance_metrics: None,
};
assert!(!progress.is_completed());
}
#[test]
fn test_is_retrying() {
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 50,
total_bytes: Some(100),
retry_count: 1,
performance_metrics: None,
};
assert!(progress.is_retrying());
let progress = Progress {
phase: FetchPhase::Downloading,
bytes_downloaded: 50,
total_bytes: Some(100),
retry_count: 0,
performance_metrics: None,
};
assert!(!progress.is_retrying());
}
#[test]
fn test_performance_metrics_default() {
let metrics = PerformanceMetrics::default();
assert!(metrics.current_rate_bps.is_none());
assert!(metrics.average_rate_bps.is_none());
assert!(metrics.bandwidth_limit_bps.is_none());
assert!(metrics.bandwidth_utilization.is_none());
assert_eq!(metrics.rate_adjustments, 0);
assert!(metrics.network_latency_ms.is_none());
assert!(metrics.connection_time_ms.is_none());
}
}