use std::error::Error; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::{env, fmt, io, time}; use walkdir::{DirEntry, WalkDir}; fn main() -> Result<(), Box> { let start = time::Instant::now(); let args: Vec = env::args().collect(); let root = parse_path(&args).expect("not a valid path"); let dir = WalkDir::new(&root); let (files, dirs): (Vec, Vec) = { let pool = pool(dir).expect("unable to retrieve entries from WalkDir"); partition_from(pool).expect("unable to partition files from directories") }; let (fs_count, dr_count) = (files.len(), dirs.len()); let (file_counter, total_size) = file_count(files); { println!("++ File size distribution for : {} ++\n", &root.display()); println!("Files @ 0B : {:4}", file_counter[0]); println!("Files > 1B - 1,023B : {:4}", file_counter[1]); println!("Files > 1KB - 1,023KB : {:4}", file_counter[2]); println!("Files > 1MB - 1,023MB : {:4}", file_counter[3]); println!("Files > 1GB - 1,023GB : {:4}", file_counter[4]); println!("Files > 1TB+ : {:4}\n", file_counter[5]); println!("Files encountered: {}", fs_count); println!("Directories traversed: {}", dr_count); println!( "Total size of all files: {}\n", Filesize::::from(total_size) ); } let end = time::Instant::now(); println!("Run time: {:?}\n", end.duration_since(start)); Ok(()) } fn parse_path(args: &[String]) -> Result<&Path, io::Error> { // If there's no `args` entered, the executable will search it's own path. match args.len() { 1 => Ok(Path::new(&args[0])), _ => Ok(Path::new(&args[1])), } } fn pool(dir: WalkDir) -> Result, Box> { // Check each item for errors and drop possible invalid `DirEntry`s Ok(dir.into_iter().filter_map(|e| e.ok()).collect()) } fn partition_from(pool: Vec) -> Result<(Vec, Vec), Box> { // Read `Path` from `DirEntry`, checking if `Path` is a file or directory. Ok(pool .into_iter() .map(|e| e.into_path()) .partition(|path| path.is_file())) } fn file_count(files: Vec) -> ([u64; 6], u64) { let mut counter: [u64; 6] = [0; 6]; for file in &files { match Filesize::::from(file).bytes { 0 => counter[0] += 1, // Empty file 1..=1_023 => counter[1] += 1, // 1 byte to 0.99KB 1_024..=1_048_575 => counter[2] += 1, // 1 kilo to 0.99MB 1_048_576..=1_073_741_823 => counter[3] += 1, // 1 mega to 0.99GB 1_073_741_824..=1_099_511_627_775 => counter[4] += 1, // 1 giga to 0.99TB 1_099_511_627_776..=std::u64::MAX => counter[5] += 1, // 1 terabyte or larger } } let total_file_size = files .iter() .fold(0, |acc, file| acc + Filesize::::from(file).bytes); (counter, total_file_size) } trait SizeUnit: Copy { fn singular_name() -> String; fn num_byte_in_unit() -> u64; } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] struct Bytes; impl SizeUnit for Bytes { fn singular_name() -> String { "B".to_string() } fn num_byte_in_unit() -> u64 { 1 } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] struct Kilobytes; impl SizeUnit for Kilobytes { fn singular_name() -> String { "KB".to_string() } fn num_byte_in_unit() -> u64 { 1_024 } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] struct Filesize { bytes: u64, unit: PhantomData, } impl From for Filesize where T: SizeUnit, { fn from(n: u64) -> Self { Filesize { bytes: n * T::num_byte_in_unit(), unit: PhantomData, } } } impl From> for u64 where T: SizeUnit, { fn from(fsz: Filesize) -> u64 { ((fsz.bytes as f64) / (T::num_byte_in_unit() as f64)) as u64 } } impl fmt::Display for Filesize where T: SizeUnit, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // convert value in associated units to float let size_val = ((self.bytes as f64) / (T::num_byte_in_unit() as f64)) as u64; // plural? let name_plural = match size_val { 1 => "", _ => "s", }; write!( f, "{} {}{}", (self.bytes as f64) / (T::num_byte_in_unit() as f64), T::singular_name(), name_plural ) } } // Can be expanded for From, or any type that has an alias for Metadata impl From<&PathBuf> for Filesize where T: SizeUnit, { fn from(f: &PathBuf) -> Self { Filesize { bytes: f .metadata() .expect("error with metadata from pathbuf into filesize") .len(), unit: PhantomData, } } }