1use std::fmt;
2use std::num::NonZeroU64;
3use std::time::Duration;
4
5use rust_decimal::Decimal;
6
7use crate::{SizeStandard, format_parts};
8
9#[derive(Debug, Clone, Copy)]
13pub struct DownloadAcceleration {
14 bytes_per_second_sq: Decimal,
15 standard: SizeStandard,
16}
17
18impl PartialEq for DownloadAcceleration {
19 #[inline]
20 fn eq(&self, other: &Self) -> bool {
21 self.bytes_per_second_sq == other.bytes_per_second_sq
22 }
23}
24
25impl PartialOrd for DownloadAcceleration {
26 #[inline]
27 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
28 self.bytes_per_second_sq
29 .partial_cmp(&other.bytes_per_second_sq)
30 }
31}
32
33impl DownloadAcceleration {
34 #[inline]
52 pub fn from_raw(bytes_per_second_sq: i64, standard: SizeStandard) -> Self {
53 Self {
54 bytes_per_second_sq: Decimal::from(bytes_per_second_sq),
55 standard,
56 }
57 }
58
59 pub fn new(
81 initial_speed: NonZeroU64,
82 final_speed: NonZeroU64,
83 duration: Duration,
84 standard: SizeStandard,
85 ) -> Self {
86 let seconds = Decimal::from(duration.as_secs())
87 + Decimal::from(duration.subsec_nanos()) / Decimal::from(1_000_000_000);
88 let speed_diff = Decimal::from(final_speed.get()) - Decimal::from(initial_speed.get());
89 let bytes_per_second_sq = if seconds.is_zero() {
90 Decimal::ZERO
91 } else {
92 speed_diff / seconds
93 };
94
95 Self {
96 bytes_per_second_sq,
97 standard,
98 }
99 }
100
101 #[inline]
105 pub fn as_decimal(&self) -> Decimal {
106 self.bytes_per_second_sq
107 }
108
109 #[inline]
113 pub fn as_i64(&self) -> i64 {
114 self.bytes_per_second_sq.floor().try_into().unwrap_or(0)
115 }
116
117 pub fn get_si_parts(&self) -> (String, &'static str) {
121 const UNITS: &[&str] = &[
122 "B/s²", "KB/s²", "MB/s²", "GB/s²", "TB/s²", "PB/s²", "EB/s²", "ZB/s²", "YB/s²",
123 ];
124 self.format_parts(Decimal::from(1000), UNITS)
125 }
126
127 pub fn get_iec_parts(&self) -> (String, &'static str) {
131 const UNITS: &[&str] = &[
132 "B/s²", "KiB/s²", "MiB/s²", "GiB/s²", "TiB/s²", "PiB/s²", "EiB/s²", "ZiB/s²", "YiB/s²",
133 ];
134 self.format_parts(Decimal::from(1024), UNITS)
135 }
136
137 fn format_parts(
138 &self,
139 base: Decimal,
140 units: &'static [&'static str],
141 ) -> (String, &'static str) {
142 let mut value = Decimal::from(self.bytes_per_second_sq);
143 let is_negative = value.is_sign_negative();
144
145 if is_negative {
146 value.set_sign_positive(true);
147 }
148
149 let (formatted_value, unit) = format_parts(value, base, units);
150 let formatted_value = if is_negative {
151 format!("-{}", formatted_value)
152 } else {
153 formatted_value
154 };
155
156 (formatted_value, unit)
157 }
158}
159
160impl fmt::Display for DownloadAcceleration {
161 #[inline]
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 let (value, unit) = match self.standard {
164 SizeStandard::SI => self.get_si_parts(),
165 SizeStandard::IEC => self.get_iec_parts(),
166 };
167 write!(f, "{} {}", value, unit)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::DownloadAcceleration;
174 use crate::SizeStandard;
175 use std::num::NonZeroU64;
176 use std::time::Duration;
177
178 #[test]
179 fn test_si_acceleration() {
180 let acc = DownloadAcceleration::from_raw(512, SizeStandard::SI);
181 assert_eq!(acc.to_string(), "512.0 B/s²");
182
183 let one_second = Duration::from_secs(1);
184 let acc = DownloadAcceleration::new(
185 NonZeroU64::new(100).unwrap(),
186 NonZeroU64::new(1100).unwrap(),
187 one_second,
188 SizeStandard::SI,
189 );
190 assert_eq!(acc.to_string(), "1.00 KB/s²");
191
192 let acc = DownloadAcceleration::new(
193 NonZeroU64::new(1).unwrap(),
194 NonZeroU64::new(10000).unwrap(),
195 one_second,
196 SizeStandard::SI,
197 );
198 assert_eq!(acc.to_string(), "10.00 KB/s²");
199
200 let acc = DownloadAcceleration::new(
201 NonZeroU64::new(1000).unwrap(),
202 NonZeroU64::new(11000).unwrap(),
203 one_second,
204 SizeStandard::SI,
205 );
206 assert_eq!(acc.to_string(), "10.0 KB/s²");
207
208 let acc_neg = DownloadAcceleration::from_raw(-1500, SizeStandard::SI);
210 assert_eq!(acc_neg.to_string(), "-1.50 KB/s²");
211 }
212
213 #[test]
214 fn test_iec_acceleration() {
215 let one_second = Duration::from_secs(1);
216 let acc = DownloadAcceleration::new(
217 NonZeroU64::new(100).unwrap(),
218 NonZeroU64::new(1124).unwrap(),
219 one_second,
220 SizeStandard::IEC,
221 );
222 assert_eq!(acc.to_string(), "1.00 KiB/s²");
223
224 let acc_neg = DownloadAcceleration::from_raw(-1500, SizeStandard::IEC);
226 assert_eq!(acc_neg.to_string(), "-1.46 KiB/s²");
227
228 let acc = DownloadAcceleration::new(
229 NonZeroU64::new(1024).unwrap(),
230 NonZeroU64::new(11 * 1024).unwrap(),
231 one_second,
232 SizeStandard::IEC,
233 );
234 assert_eq!(acc.to_string(), "10.0 KiB/s²");
235
236 let acc = DownloadAcceleration::new(
237 NonZeroU64::new(10 * 1024).unwrap(),
238 NonZeroU64::new(110 * 1024).unwrap(),
239 one_second,
240 SizeStandard::IEC,
241 );
242 assert_eq!(acc.to_string(), "100.0 KiB/s²");
243 }
244}