unistore_progress/
event.rs

1//! 【事件类型】- 进度事件定义
2//!
3//! 职责:
4//! - 定义进度更新事件
5//! - 提供事件数据访问接口
6
7use crate::deps::Duration;
8
9/// 进度事件
10#[derive(Debug, Clone)]
11pub struct ProgressEvent {
12    /// 当前完成数
13    pub current: u64,
14    /// 总数
15    pub total: u64,
16    /// 进度消息
17    pub message: String,
18    /// 已用时间
19    pub elapsed: Duration,
20    /// 预计剩余时间
21    pub eta: Option<Duration>,
22    /// 是否已完成
23    pub finished: bool,
24}
25
26impl ProgressEvent {
27    /// 获取完成百分比(0.0 - 100.0)
28    pub fn percentage(&self) -> f64 {
29        if self.total == 0 {
30            return 0.0;
31        }
32        (self.current as f64 / self.total as f64) * 100.0
33    }
34
35    /// 获取进度比例(0.0 - 1.0)
36    pub fn ratio(&self) -> f64 {
37        if self.total == 0 {
38            return 0.0;
39        }
40        self.current as f64 / self.total as f64
41    }
42
43    /// 获取消息引用
44    pub fn message(&self) -> &str {
45        &self.message
46    }
47
48    /// 是否已完成
49    pub fn is_finished(&self) -> bool {
50        self.finished
51    }
52
53    /// 获取处理速率(每秒处理数)
54    pub fn rate(&self) -> f64 {
55        let secs = self.elapsed.as_secs_f64();
56        if secs == 0.0 {
57            return 0.0;
58        }
59        self.current as f64 / secs
60    }
61
62    /// 格式化为人类可读的字符串
63    pub fn to_string_human(&self) -> String {
64        let pct = self.percentage();
65        let rate = self.rate();
66
67        let eta_str = match self.eta {
68            Some(eta) => format_duration(eta),
69            None => "计算中...".to_string(),
70        };
71
72        if self.finished {
73            format!(
74                "✓ 完成 {}/{} (用时: {})",
75                self.current,
76                self.total,
77                format_duration(self.elapsed)
78            )
79        } else {
80            format!(
81                "{:.1}% ({}/{}) - {:.1}/s - ETA: {} - {}",
82                pct, self.current, self.total, rate, eta_str, self.message
83            )
84        }
85    }
86}
87
88/// 格式化持续时间为人类可读格式
89fn format_duration(d: Duration) -> String {
90    let secs = d.as_secs();
91    if secs < 60 {
92        format!("{}s", secs)
93    } else if secs < 3600 {
94        format!("{}m {}s", secs / 60, secs % 60)
95    } else {
96        format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_percentage() {
106        let event = ProgressEvent {
107            current: 50,
108            total: 100,
109            message: "test".to_string(),
110            elapsed: Duration::from_secs(10),
111            eta: Some(Duration::from_secs(10)),
112            finished: false,
113        };
114        assert!((event.percentage() - 50.0).abs() < 0.001);
115    }
116
117    #[test]
118    fn test_ratio() {
119        let event = ProgressEvent {
120            current: 25,
121            total: 100,
122            message: "test".to_string(),
123            elapsed: Duration::from_secs(5),
124            eta: None,
125            finished: false,
126        };
127        assert!((event.ratio() - 0.25).abs() < 0.001);
128    }
129
130    #[test]
131    fn test_rate() {
132        let event = ProgressEvent {
133            current: 100,
134            total: 200,
135            message: "test".to_string(),
136            elapsed: Duration::from_secs(10),
137            eta: None,
138            finished: false,
139        };
140        assert!((event.rate() - 10.0).abs() < 0.001);
141    }
142}