unistore-progress 0.1.0

Progress tracking capability for UniStore
Documentation
//! 【事件类型】- 进度事件定义
//!
//! 职责:
//! - 定义进度更新事件
//! - 提供事件数据访问接口

use crate::deps::Duration;

/// 进度事件
#[derive(Debug, Clone)]
pub struct ProgressEvent {
    /// 当前完成数
    pub current: u64,
    /// 总数
    pub total: u64,
    /// 进度消息
    pub message: String,
    /// 已用时间
    pub elapsed: Duration,
    /// 预计剩余时间
    pub eta: Option<Duration>,
    /// 是否已完成
    pub finished: bool,
}

impl ProgressEvent {
    /// 获取完成百分比(0.0 - 100.0)
    pub fn percentage(&self) -> f64 {
        if self.total == 0 {
            return 0.0;
        }
        (self.current as f64 / self.total as f64) * 100.0
    }

    /// 获取进度比例(0.0 - 1.0)
    pub fn ratio(&self) -> f64 {
        if self.total == 0 {
            return 0.0;
        }
        self.current as f64 / self.total as f64
    }

    /// 获取消息引用
    pub fn message(&self) -> &str {
        &self.message
    }

    /// 是否已完成
    pub fn is_finished(&self) -> bool {
        self.finished
    }

    /// 获取处理速率(每秒处理数)
    pub fn rate(&self) -> f64 {
        let secs = self.elapsed.as_secs_f64();
        if secs == 0.0 {
            return 0.0;
        }
        self.current as f64 / secs
    }

    /// 格式化为人类可读的字符串
    pub fn to_string_human(&self) -> String {
        let pct = self.percentage();
        let rate = self.rate();

        let eta_str = match self.eta {
            Some(eta) => format_duration(eta),
            None => "计算中...".to_string(),
        };

        if self.finished {
            format!(
                "✓ 完成 {}/{} (用时: {})",
                self.current,
                self.total,
                format_duration(self.elapsed)
            )
        } else {
            format!(
                "{:.1}% ({}/{}) - {:.1}/s - ETA: {} - {}",
                pct, self.current, self.total, rate, eta_str, self.message
            )
        }
    }
}

/// 格式化持续时间为人类可读格式
fn format_duration(d: Duration) -> String {
    let secs = d.as_secs();
    if secs < 60 {
        format!("{}s", secs)
    } else if secs < 3600 {
        format!("{}m {}s", secs / 60, secs % 60)
    } else {
        format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_percentage() {
        let event = ProgressEvent {
            current: 50,
            total: 100,
            message: "test".to_string(),
            elapsed: Duration::from_secs(10),
            eta: Some(Duration::from_secs(10)),
            finished: false,
        };
        assert!((event.percentage() - 50.0).abs() < 0.001);
    }

    #[test]
    fn test_ratio() {
        let event = ProgressEvent {
            current: 25,
            total: 100,
            message: "test".to_string(),
            elapsed: Duration::from_secs(5),
            eta: None,
            finished: false,
        };
        assert!((event.ratio() - 0.25).abs() < 0.001);
    }

    #[test]
    fn test_rate() {
        let event = ProgressEvent {
            current: 100,
            total: 200,
            message: "test".to_string(),
            elapsed: Duration::from_secs(10),
            eta: None,
            finished: false,
        };
        assert!((event.rate() - 10.0).abs() < 0.001);
    }
}