···11+//! Shared helpers for container formats (tar, zip, 7z).
22+//!
33+//! Single-stream codecs know the total input size up front from file
44+//! metadata. Container formats take N paths and recurse into directories, so
55+//! they have to pre-walk the input to get a meaningful progress total.
66+77+use std::path::Path;
88+99+/// Sum sizes of all regular files reachable from the given paths, recursing
1010+/// into directories. Best-effort: anything we can't stat (permission denied,
1111+/// racy deletion) is counted as zero; the bar may finish short rather than
1212+/// fail the run.
1313+pub fn total_input_bytes<P: AsRef<Path>>(paths: &[P]) -> u64 {
1414+ paths.iter().map(|p| sum_path(p.as_ref())).sum()
1515+}
1616+1717+fn sum_path(path: &Path) -> u64 {
1818+ let Ok(meta) = std::fs::symlink_metadata(path) else {
1919+ return 0;
2020+ };
2121+ if meta.is_file() {
2222+ return meta.len();
2323+ }
2424+ if meta.is_dir() {
2525+ let Ok(entries) = std::fs::read_dir(path) else {
2626+ return 0;
2727+ };
2828+ return entries.flatten().map(|e| sum_path(&e.path())).sum();
2929+ }
3030+ 0
3131+}
3232+3333+#[cfg(test)]
3434+mod tests {
3535+ use super::*;
3636+ use assert_fs::prelude::*;
3737+3838+ #[test]
3939+ fn sums_single_file() {
4040+ let dir = assert_fs::TempDir::new().unwrap();
4141+ let f = dir.child("a.txt");
4242+ f.write_str("hello").unwrap();
4343+ assert_eq!(total_input_bytes(&[f.path().to_path_buf()]), 5);
4444+ }
4545+4646+ #[test]
4747+ fn sums_directory_recursively() {
4848+ let dir = assert_fs::TempDir::new().unwrap();
4949+ dir.child("a.txt").write_str("abc").unwrap();
5050+ dir.child("sub/b.txt").write_str("defgh").unwrap();
5151+ assert_eq!(total_input_bytes(&[dir.path().to_path_buf()]), 8);
5252+ }
5353+5454+ #[test]
5555+ fn missing_path_counts_zero() {
5656+ assert_eq!(
5757+ total_input_bytes(&[std::path::PathBuf::from("/nope/xx")]),
5858+ 0
5959+ );
6060+ }
6161+}