use std::sync::Arc;
#[derive(Debug, Clone, Copy, Default)]
pub struct Progress {
pub bytes_processed: u64,
pub total_bytes: Option<u64>,
pub items_processed: usize,
pub total_items: Option<usize>,
}
impl Progress {
pub fn new(bytes_processed: u64, total_bytes: Option<u64>, items_processed: usize) -> Self {
Self {
bytes_processed,
total_bytes,
items_processed,
total_items: None,
}
}
#[must_use]
pub fn with_items(mut self, total_items: usize) -> Self {
self.total_items = Some(total_items);
self
}
pub fn percentage(&self) -> Option<f64> {
self.total_bytes.map(|total| {
if total == 0 {
100.0
} else {
(self.bytes_processed as f64 / total as f64) * 100.0
}
})
}
pub fn items_percentage(&self) -> Option<f64> {
self.total_items.map(|total| {
if total == 0 {
100.0
} else {
(self.items_processed as f64 / total as f64) * 100.0
}
})
}
pub fn is_complete(&self) -> bool {
self.total_bytes
.map(|total| self.bytes_processed >= total)
.unwrap_or(false)
}
pub fn remaining_bytes(&self) -> Option<u64> {
self.total_bytes
.map(|total| total.saturating_sub(self.bytes_processed))
}
}
pub type ProgressCallback = Arc<dyn Fn(Progress) + Send + Sync>;
pub fn no_progress() -> ProgressCallback {
Arc::new(|_| {})
}
pub fn stderr_progress() -> ProgressCallback {
Arc::new(|progress| {
if let Some(pct) = progress.percentage() {
eprintln!("Progress: {:.1}%", pct);
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_percentage() {
let progress = Progress::new(500, Some(1000), 50);
assert_eq!(progress.percentage(), Some(50.0));
}
#[test]
fn test_progress_percentage_unknown_total() {
let progress = Progress::new(500, None, 50);
assert_eq!(progress.percentage(), None);
}
#[test]
fn test_progress_percentage_zero_total() {
let progress = Progress::new(0, Some(0), 0);
assert_eq!(progress.percentage(), Some(100.0));
}
#[test]
fn test_progress_is_complete() {
let complete = Progress::new(1000, Some(1000), 100);
assert!(complete.is_complete());
let incomplete = Progress::new(500, Some(1000), 50);
assert!(!incomplete.is_complete());
let unknown = Progress::new(500, None, 50);
assert!(!unknown.is_complete());
}
#[test]
fn test_progress_remaining_bytes() {
let progress = Progress::new(300, Some(1000), 30);
assert_eq!(progress.remaining_bytes(), Some(700));
let unknown = Progress::new(300, None, 30);
assert_eq!(unknown.remaining_bytes(), None);
}
#[test]
fn test_progress_with_items() {
let progress = Progress::new(500, Some(1000), 50).with_items(100);
assert_eq!(progress.total_items, Some(100));
assert_eq!(progress.items_percentage(), Some(50.0));
}
#[test]
fn test_no_progress_callback() {
let callback = no_progress();
callback(Progress::default()); }
#[test]
fn test_progress_callback_type() {
use std::sync::atomic::{AtomicU64, Ordering};
let counter = Arc::new(AtomicU64::new(0));
let counter_clone = counter.clone();
let callback: ProgressCallback = Arc::new(move |progress| {
counter_clone.store(progress.bytes_processed, Ordering::SeqCst);
});
callback(Progress::new(42, None, 0));
assert_eq!(counter.load(Ordering::SeqCst), 42);
}
}