Skip to main content

pacenotes_installer/
size.rs

1//! Byte counts as a person reads them, in binary units with the short labels,
2//! so they match what Explorer says about the same file.
3
4const KB: u64 = 1024;
5const MB: u64 = 1024 * KB;
6const GB: u64 = 1024 * MB;
7
8/// `0 B`, `997 B`, `12.1 kB`, `41.6 MB`, `1.4 GB`: one decimal from kB up, in
9/// integers, since one `{:.1}` on an `f64` links ~16 kB of float formatting.
10pub fn human(bytes: u64) -> String {
11    let (divisor, unit) = unit_for(bytes);
12    if divisor == 1 {
13        return format!("{bytes} {unit}");
14    }
15    let (whole, tenth) = tenths(bytes, divisor);
16    format!("{whole}.{tenth} {unit}")
17}
18
19/// `12.4 / 41.6 MB`, both in the total's unit so they compare at a glance;
20/// an unknown (zero) total shows only what arrived.
21pub fn human_progress(done: u64, total: u64) -> String {
22    if total == 0 {
23        return human(done);
24    }
25    let (divisor, unit) = unit_for(total);
26    if divisor == 1 {
27        return format!("{done} / {total} {unit}");
28    }
29    let (done_whole, done_tenth) = tenths(done, divisor);
30    let (total_whole, total_tenth) = tenths(total, divisor);
31    format!("{done_whole}.{done_tenth} / {total_whole}.{total_tenth} {unit}")
32}
33
34/// `bytes / divisor` as a whole part and one decimal digit, rounded half up as `{:.1}` did.
35fn tenths(bytes: u64, divisor: u64) -> (u64, u64) {
36    let scaled = (bytes * 10 + divisor / 2) / divisor;
37    (scaled / 10, scaled % 10)
38}
39
40fn unit_for(bytes: u64) -> (u64, &'static str) {
41    match bytes {
42        0..KB => (1, "B"),
43        KB..MB => (KB, "kB"),
44        MB..GB => (MB, "MB"),
45        _ => (GB, "GB"),
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    /// 41.6 MB is what Explorer shows for the live catalogue's largest package.
52    #[test]
53    fn a_real_package_reads_as_explorer_shows_it() {
54        assert_eq!(super::human(43_609_253), "41.6 MB");
55    }
56
57    #[test]
58    fn a_small_package_is_counted_in_bytes_and_kb() {
59        assert_eq!(super::human(5_861), "5.7 kB");
60        assert_eq!(super::human(997), "997 B");
61        assert_eq!(super::human(0), "0 B");
62    }
63
64    #[test]
65    fn the_unit_turns_over_at_the_boundary() {
66        assert_eq!(super::human(1023), "1023 B");
67        assert_eq!(super::human(1024), "1.0 kB");
68        assert_eq!(super::human(1024 * 1024 - 1), "1024.0 kB");
69        assert_eq!(super::human(1024 * 1024), "1.0 MB");
70        assert_eq!(super::human(1024 * 1024 * 1024), "1.0 GB");
71    }
72
73    /// Per-number units would show "996.0 kB / 1.0 MB" and read as no progress.
74    #[test]
75    fn a_transfer_shows_both_numbers_in_the_total_s_unit() {
76        assert_eq!(
77            super::human_progress(13_000_000, 43_609_253),
78            "12.4 / 41.6 MB"
79        );
80        assert_eq!(super::human_progress(1_020_000, 1_048_576), "1.0 / 1.0 MB");
81        assert_eq!(super::human_progress(500, 5_861), "0.5 / 5.7 kB");
82    }
83
84    #[test]
85    fn an_unknown_total_still_reports_what_arrived() {
86        assert_eq!(super::human_progress(43_609_253, 0), "41.6 MB");
87    }
88}