this repo has no description
1//! Shared helpers for container formats (tar, zip, 7z).
2//!
3//! Single-stream codecs know the total input size up front from file
4//! metadata. Container formats take N paths and recurse into directories, so
5//! they have to pre-walk the input to get a meaningful progress total.
6
7use std::path::Path;
8
9/// Sum sizes of all regular files reachable from the given paths, recursing
10/// into directories. Uses `fs::metadata` (follows symlinks) so that the
11/// file-vs-directory judgment matches `Path::is_file` / `Path::is_dir`
12/// semantics used by each backend's walker — otherwise a symlink to a file
13/// would contribute 0 to the total while the walker reads (and bar-ticks)
14/// the full target. Best-effort: anything we can't stat (permission denied,
15/// racy deletion, broken symlink) is counted as zero; the bar may finish
16/// short rather than fail the run.
17pub fn total_input_bytes<P: AsRef<Path>>(paths: &[P]) -> u64 {
18 paths.iter().map(|p| sum_path(p.as_ref())).sum()
19}
20
21fn sum_path(path: &Path) -> u64 {
22 let Ok(meta) = std::fs::metadata(path) else {
23 return 0;
24 };
25 if meta.is_file() {
26 return meta.len();
27 }
28 if meta.is_dir() {
29 let Ok(entries) = std::fs::read_dir(path) else {
30 return 0;
31 };
32 return entries.flatten().map(|e| sum_path(&e.path())).sum();
33 }
34 0
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use assert_fs::prelude::*;
41
42 #[test]
43 fn sums_single_file() {
44 let dir = assert_fs::TempDir::new().unwrap();
45 let f = dir.child("a.txt");
46 f.write_str("hello").unwrap();
47 assert_eq!(total_input_bytes(&[f.path().to_path_buf()]), 5);
48 }
49
50 #[test]
51 fn sums_directory_recursively() {
52 let dir = assert_fs::TempDir::new().unwrap();
53 dir.child("a.txt").write_str("abc").unwrap();
54 dir.child("sub/b.txt").write_str("defgh").unwrap();
55 assert_eq!(total_input_bytes(&[dir.path().to_path_buf()]), 8);
56 }
57
58 #[test]
59 fn missing_path_counts_zero() {
60 assert_eq!(
61 total_input_bytes(&[std::path::PathBuf::from("/nope/xx")]),
62 0
63 );
64 }
65
66 /// Symlinks to regular files must contribute their target's size so the
67 /// bar total matches what the walkers actually read. Regression for
68 /// tar/zip/7z bars overshooting past 100% on directories containing
69 /// symlinks.
70 #[cfg(unix)]
71 #[test]
72 fn follows_symlink_to_file() {
73 let dir = assert_fs::TempDir::new().unwrap();
74 dir.child("target.txt").write_str("abcdefghij").unwrap();
75 std::os::unix::fs::symlink(dir.path().join("target.txt"), dir.path().join("link.txt"))
76 .unwrap();
77 // target.txt (10) + link.txt following to target (10) = 20
78 assert_eq!(total_input_bytes(&[dir.path().to_path_buf()]), 20);
79 }
80}