1use irontide_session::TorrentState;
8
9pub fn format_size(bytes: u64) -> String {
15 const KIB: u64 = 1024;
16 const MIB: u64 = 1024 * KIB;
17 const GIB: u64 = 1024 * MIB;
18 if bytes >= GIB {
19 format!("{:.2} GiB", bytes as f64 / GIB as f64)
20 } else if bytes >= MIB {
21 format!("{:.1} MiB", bytes as f64 / MIB as f64)
22 } else if bytes >= KIB {
23 format!("{:.1} KiB", bytes as f64 / KIB as f64)
24 } else {
25 format!("{bytes} B")
26 }
27}
28
29pub fn format_rate(bytes_per_sec: u64) -> String {
34 const KIB: u64 = 1024;
35 const MIB: u64 = 1024 * KIB;
36 if bytes_per_sec >= MIB {
37 format!("{:.1} MB/s", bytes_per_sec as f64 / MIB as f64)
38 } else if bytes_per_sec >= KIB {
39 format!("{:.1} KB/s", bytes_per_sec as f64 / KIB as f64)
40 } else {
41 format!("{bytes_per_sec} B/s")
42 }
43}
44
45pub fn format_state(state: &TorrentState, user_seed_mode: bool) -> &'static str {
50 if user_seed_mode && matches!(state, TorrentState::Downloading) {
51 return "seed only";
52 }
53 match state {
54 TorrentState::FetchingMetadata => "fetching metadata",
55 TorrentState::Checking => "checking",
56 TorrentState::Downloading => "downloading",
57 TorrentState::Complete => "complete",
58 TorrentState::Seeding => "seeding",
59 TorrentState::Paused => "paused",
60 TorrentState::Stopped => "stopped",
61 TorrentState::Sharing => "sharing",
62 }
63}
64
65pub fn format_ratio(uploaded: u64, downloaded: u64) -> String {
70 if downloaded == 0 && uploaded > 0 {
71 return "\u{221e}".to_string(); }
73 if downloaded == 0 {
74 return "0.00".to_string();
75 }
76 format!("{:.2}", uploaded as f64 / downloaded as f64)
77}
78
79pub fn format_eta(remaining_bytes: u64, rate_bps: u64) -> String {
85 if rate_bps == 0 {
86 return "\u{2014}".to_string(); }
88 let secs = remaining_bytes / rate_bps;
89 if secs >= 86400 {
90 let days = secs / 86400;
91 let hours = (secs % 86400) / 3600;
92 format!("{days}d {hours}h")
93 } else if secs >= 3600 {
94 let hours = secs / 3600;
95 let mins = (secs % 3600) / 60;
96 format!("{hours}h {mins}m")
97 } else if secs >= 60 {
98 let mins = secs / 60;
99 let s = secs % 60;
100 format!("{mins}m {s}s")
101 } else {
102 format!("{secs}s")
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_format_size_boundaries() {
112 assert_eq!(format_size(0), "0 B");
114 assert_eq!(format_size(512), "512 B");
116 assert_eq!(format_size(1024), "1.0 KiB");
118 assert_eq!(format_size(512 * 1024), "512.0 KiB");
120 assert_eq!(format_size(1_048_576), "1.0 MiB");
122 assert_eq!(format_size(512 * 1_048_576), "512.0 MiB");
124 assert_eq!(format_size(1_073_741_824), "1.00 GiB");
126 assert_eq!(format_size(512 * 1_073_741_824), "512.00 GiB");
128 }
129
130 #[test]
131 fn test_format_rate_boundaries() {
132 assert_eq!(format_rate(0), "0 B/s");
134 assert_eq!(format_rate(512), "512 B/s");
136 assert_eq!(format_rate(1024), "1.0 KB/s");
138 assert_eq!(format_rate(512 * 1024), "512.0 KB/s");
140 assert_eq!(format_rate(1_048_576), "1.0 MB/s");
142 assert_eq!(format_rate(1_073_741_824), "1024.0 MB/s");
144 }
145
146 #[test]
147 fn test_format_state_all_variants() {
148 assert_eq!(
150 format_state(&TorrentState::FetchingMetadata, false),
151 "fetching metadata"
152 );
153 assert_eq!(format_state(&TorrentState::Checking, false), "checking");
154 assert_eq!(
155 format_state(&TorrentState::Downloading, false),
156 "downloading"
157 );
158 assert_eq!(format_state(&TorrentState::Complete, false), "complete");
159 assert_eq!(format_state(&TorrentState::Seeding, false), "seeding");
160 assert_eq!(format_state(&TorrentState::Paused, false), "paused");
161 assert_eq!(format_state(&TorrentState::Stopped, false), "stopped");
162 assert_eq!(format_state(&TorrentState::Sharing, false), "sharing");
163
164 assert_eq!(
166 format_state(&TorrentState::Downloading, true),
167 "seed only"
168 );
169 assert_eq!(format_state(&TorrentState::Seeding, true), "seeding");
171 assert_eq!(format_state(&TorrentState::Paused, true), "paused");
172 assert_eq!(format_state(&TorrentState::Complete, true), "complete");
173 assert_eq!(format_state(&TorrentState::Sharing, true), "sharing");
174 assert_eq!(format_state(&TorrentState::Stopped, true), "stopped");
175 }
176
177 #[test]
178 fn test_format_ratio() {
179 assert_eq!(format_ratio(0, 0), "0.00");
181 assert_eq!(format_ratio(500, 0), "\u{221e}");
183 assert_eq!(format_ratio(150, 100), "1.50");
185 assert_eq!(format_ratio(1000, 1000), "1.00");
187 assert_eq!(format_ratio(1_000_000, 1_000), "1000.00");
189 assert_eq!(format_ratio(50, 100), "0.50");
191 }
192
193 #[test]
194 fn test_format_eta() {
195 assert_eq!(format_eta(0, 1), "0s");
197 assert_eq!(format_eta(1_000_000, 0), "\u{2014}");
199 assert_eq!(format_eta(30, 1), "30s");
201 assert_eq!(format_eta(45 * 60 + 12, 1), "45m 12s");
203 assert_eq!(format_eta(2 * 3600 + 15 * 60, 1), "2h 15m");
205 assert_eq!(format_eta(2 * 86400 + 15 * 3600, 1), "2d 15h");
207 assert_eq!(format_eta(100 * 1_048_576, 1_048_576), "1m 40s");
209 }
210}