From b52297a1bb2d915b1310db8eb46f2ccd93920ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20=C3=96sterberg?= Date: Wed, 26 Aug 2026 20:35:13 +0000 Subject: [PATCH] perf: reuse file metadata --- src/find.rs | 75 ++++++++++++++++++++-------------------------------- src/main.rs | 35 +++++++++--------------- src/print.rs | 11 +------- 3 files changed, 43 insertions(+), 78 deletions(-) diff --git a/src/find.rs b/src/find.rs index d5bd192..685499f 100644 --- a/src/find.rs +++ b/src/find.rs @@ -38,43 +38,38 @@ impl Filter { self } - pub fn accept(&self, e: &DirEntry) -> bool { + pub fn accept(&self, e: &DirEntry) -> Option { let metadata: Metadata = match e.metadata() { Ok(metadata) => metadata, Err(err) => { log::warn!("Unable to obtain metadata for {:?}: {:?}", e.path(), err); - return false; + return None; } }; if let Mode::File = self.mode { if metadata.len() < self.min_size { - return false; + return None; } } if !metadata.is_file() { - return false; + return None; } - let accept_age: bool = Filter::filter_mod_time(&metadata, &self.mod_age); - - if !accept_age { - return false; + if !Filter::filter_mod_time(&metadata, &self.mod_age) { + return None; } - let file_name: String = match Filter::file_name(e) { - Some(name) => name, - None => return false, - }; + let file_name: String = Filter::file_name(e)?; if e.path().starts_with(PROC) { - return false; + return None; } match &self.pattern { - Some(pattern) => pattern.is_match(&file_name), - None => true, + Some(pattern) if !pattern.is_match(&file_name) => None, + _ => Some(metadata.len()), } } @@ -153,22 +148,11 @@ impl Default for Filter { } } -pub fn summarize(files: Vec) -> (u64, u64) { - let found: u64 = files.len() as u64; - let size: u64 = files - .iter() - .filter_map(|f| f.metadata().ok()) - .map(|m| m.len()) - .sum(); - - (found, size) -} - #[cfg(test)] mod tests { use crate::find::Filter; use crate::size::Size; - use crate::{cfg::Config, create_walker, find::summarize, walk_files}; + use crate::{cfg::Config, create_walker, walk_files}; use regex::Regex; use std::path::PathBuf; use std::str::FromStr; @@ -177,32 +161,18 @@ mod tests { const TEST_DIR: &str = "test_dirs"; const PROC: &str = "/proc"; - #[test] - fn test_stop_at_one_found_file() { - let dir = PathBuf::from(TEST_DIR); - let files: Vec = create_walker(&Config::default(), &dir) - .into_iter() - .filter_map(|e| e.ok()) - .take(1) - .collect(); - let result: (u64, u64) = summarize(files); - assert_eq!(1, result.0); - } - #[cfg(unix)] #[test] fn test_filter_by_file_size() { let dir = PathBuf::from(TEST_DIR); let filter = Filter::new().with_min_size(Size::Byte(100)); - let files: Vec = create_walker(&Config::default(), &dir) + let sizes: Vec = create_walker(&Config::default(), &dir) .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| filter.accept(&e)) + .filter_map(Result::ok) + .filter_map(|entry: DirEntry| filter.accept(&entry)) .collect(); - let result: (u64, u64) = summarize(files); - assert_eq!(1, result.0); - assert_eq!(100, result.1); + assert_eq!(vec![100], sizes); } #[cfg(target_os = "linux")] @@ -213,6 +183,19 @@ mod tests { assert_eq!(0, found); } + #[test] + fn filter_returns_size_from_accepted_metadata() { + let dir: PathBuf = PathBuf::from(TEST_DIR); + let entry: DirEntry = create_walker(&Config::default(), &dir) + .into_iter() + .filter_map(Result::ok) + .find(|entry: &DirEntry| entry.file_name() == "file2") + .expect("test fixture should contain file2"); + let filter: Filter = Filter::new().with_min_size(Size::Byte(1)); + + assert_eq!(Some(100), filter.accept(&entry)); + } + #[test] fn test_filter_by_file_pattern() { let dir = PathBuf::from(TEST_DIR); @@ -223,7 +206,7 @@ mod tests { let files: Vec = create_walker(&Config::default(), &dir) .into_iter() .filter_map(|e| e.ok()) - .filter(|f| filter.accept(&f)) + .filter(|f| filter.accept(f).is_some()) .collect(); assert_eq!(2, files.len()); diff --git a/src/main.rs b/src/main.rs index 0359422..8d43a33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ mod size; use crate::cfg::Config; use crate::dbg::dbg_info; -use crate::find::summarize; use crate::logger::setup_logging; use crate::print::{print_dir, print_file, print_summary}; use crate::structopt::StructOpt; @@ -46,19 +45,17 @@ fn main() { fn walk_files(cfg: &Config) -> (u64, u64) { let limit: usize = cfg.limit.unwrap_or(usize::MAX); let filter: Filter = cfg.into(); - let files: Vec = cfg - .paths() + cfg.paths() .iter() .map(|path: &PathBuf| create_walker(cfg, path)) - .into_iter() .flatten() - .filter_map(|e| e.ok()) - .filter(|e: &DirEntry| filter.accept(e)) + .filter_map(Result::ok) + .filter_map(|entry: DirEntry| filter.accept(&entry).map(|size| (entry, size))) .take(limit) - .inspect(|f| print_file(f, cfg)) - .collect(); - - summarize(files) + .fold((0, 0), |(found, total_size), (entry, size)| { + print_file(entry.path(), size, cfg); + (found + 1, total_size + size) + }) } fn walk_dirs(cfg: &Config) -> (u64, u64) { @@ -71,9 +68,12 @@ fn walk_dirs(cfg: &Config) -> (u64, u64) { .iter() .map(|path: &PathBuf| create_walker(cfg, path)) .flatten() - .filter_map(|e| e.ok()) - .filter(|e: &DirEntry| filter.accept(e)) - .map(|f: DirEntry| size_of(&f)) + .filter_map(Result::ok) + .filter_map(|entry: DirEntry| { + filter + .accept(&entry) + .map(|size| (entry.path().parent().unwrap().to_path_buf(), size)) + }) .for_each(|(dir, size)| update_size(&mut acc_size, dir, root, size)); let limit: usize = cfg.limit.unwrap_or(usize::MAX); @@ -104,15 +104,6 @@ fn update_size(acc_size: &mut HashMap, path: PathBuf, root: &Path, } } -fn size_of(entry: &DirEntry) -> (PathBuf, u64) { - let size: u64 = match entry.metadata() { - Ok(metadata) => metadata.len(), - Err(_) => 0, - }; - let parent: PathBuf = entry.path().parent().unwrap().to_path_buf(); - (parent, size) -} - fn create_walker(cfg: &Config, path: &Path) -> WalkDir { let walker = WalkDir::new(path) .follow_links(false) diff --git a/src/print.rs b/src/print.rs index 6d1ce4b..fc379d2 100644 --- a/src/print.rs +++ b/src/print.rs @@ -2,17 +2,8 @@ use crate::cfg::{Config, Mode}; use humansize::{file_size_opts as options, FileSize}; use itertools::Itertools; use std::path::{Path, PathBuf}; -use walkdir::DirEntry; -pub fn print_file(entry: &DirEntry, cfg: &Config) { - let file: &Path = entry.path(); - let size: u64 = match file.metadata() { - Ok(metadata) => metadata.len(), - Err(err) => { - log_error(err, file); - return; - } - }; +pub fn print_file(file: &Path, size: u64, cfg: &Config) { if cfg.plumbing_mode { print_plumbing(file, size) } else {