diff --git a/Cargo.lock b/Cargo.lock index 13cb849f..c706211c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" @@ -135,9 +135,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -510,9 +510,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] diff --git a/completions/_dust b/completions/_dust index 06a99cb5..b1fe76d1 100644 --- a/completions/_dust +++ b/completions/_dust @@ -37,6 +37,12 @@ _dust() { '--output-format=[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.]:FORMAT:(si b k m g t kb mb gb tb)' \ '-S+[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \ '--stack-size=[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \ +'-M+[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => \[curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)]: : ' \ +'--mtime=[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => \[curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)]: : ' \ +'-A+[just like -mtime, but based on file access time]: : ' \ +'--atime=[just like -mtime, but based on file access time]: : ' \ +'-y+[just like -mtime, but based on file change time]: : ' \ +'--ctime=[just like -mtime, but based on file change time]: : ' \ '-p[Subdirectories will not have their path shortened]' \ '--full-paths[Subdirectories will not have their path shortened]' \ '-L[dereference sym links - Treat sym links as directories and go into them]' \ diff --git a/completions/_dust.ps1 b/completions/_dust.ps1 index 2b87bfca..29637ca0 100644 --- a/completions/_dust.ps1 +++ b/completions/_dust.ps1 @@ -43,6 +43,12 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock { [CompletionResult]::new('--output-format', 'output-format', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.') [CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)') [CompletionResult]::new('--stack-size', 'stack-size', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)') + [CompletionResult]::new('-M', 'M ', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)') + [CompletionResult]::new('--mtime', 'mtime', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)') + [CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time') + [CompletionResult]::new('--atime', 'atime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time') + [CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time') + [CompletionResult]::new('--ctime', 'ctime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time') [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened') [CompletionResult]::new('--full-paths', 'full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened') [CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them') diff --git a/completions/dust.bash b/completions/dust.bash index b0fbf354..2f656292 100644 --- a/completions/dust.bash +++ b/completions/dust.bash @@ -19,7 +19,7 @@ _dust() { case "${cmd}" in dust) - opts="-d -T -n -p -X -I -L -x -s -r -c -C -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -j -h -V --depth --threads --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --force-colors --no-percent-bars --bars-on-right --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --no-progress --print-errors --only-dir --only-file --output-format --stack-size --output-json --help --version [PATH]..." + opts="-d -T -n -p -X -I -L -x -s -r -c -C -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -j -M -A -y -h -V --depth --threads --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --force-colors --no-percent-bars --bars-on-right --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --no-progress --print-errors --only-dir --only-file --output-format --stack-size --output-json --mtime --atime --ctime --help --version [PATH]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -135,6 +135,30 @@ _dust() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --mtime) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -M) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --atime) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -A) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --ctime) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -y) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/completions/dust.elv b/completions/dust.elv index 76c43b2f..a2571a96 100644 --- a/completions/dust.elv +++ b/completions/dust.elv @@ -40,6 +40,12 @@ set edit:completion:arg-completer[dust] = {|@words| cand --output-format 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.' cand -S 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)' cand --stack-size 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)' + cand -M '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)' + cand --mtime '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)' + cand -A 'just like -mtime, but based on file access time' + cand --atime 'just like -mtime, but based on file access time' + cand -y 'just like -mtime, but based on file change time' + cand --ctime 'just like -mtime, but based on file change time' cand -p 'Subdirectories will not have their path shortened' cand --full-paths 'Subdirectories will not have their path shortened' cand -L 'dereference sym links - Treat sym links as directories and go into them' diff --git a/completions/dust.fish b/completions/dust.fish index 22523221..6c333d4b 100644 --- a/completions/dust.fish +++ b/completions/dust.fish @@ -9,6 +9,9 @@ complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. complete -c dust -s w -l terminal_width -d 'Specify width of output overriding the auto detection of terminal width' -r complete -c dust -s o -l output-format -d 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.' -r -f -a "{si '',b '',k '',m '',g '',t '',kb '',mb '',gb '',tb ''}" complete -c dust -s S -l stack-size -d 'Specify memory to use as stack size - use if you see: \'fatal runtime error: stack overflow\' (default low memory=1048576, high memory=1073741824)' -r +complete -c dust -s M -l mtime -d '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)' -r +complete -c dust -s A -l atime -d 'just like -mtime, but based on file access time' -r +complete -c dust -s y -l ctime -d 'just like -mtime, but based on file change time' -r complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened' complete -c dust -s L -l dereference-links -d 'dereference sym links - Treat sym links as directories and go into them' complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory' diff --git a/man-page/dust.1 b/man-page/dust.1 index 474337b4..59326698 100644 --- a/man-page/dust.1 +++ b/man-page/dust.1 @@ -4,7 +4,7 @@ .SH NAME Dust \- Like du but more intuitive .SH SYNOPSIS -\fBdust\fR [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-T\fR|\fB\-\-threads\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-I\fR|\fB\-\-ignore\-all\-in\-file\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-C\fR|\fB\-\-force\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-B\fR|\fB\-\-bars\-on\-right\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore_hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file_types\fR] [\fB\-w\fR|\fB\-\-terminal_width\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-\-print\-errors\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fB\-o\fR|\fB\-\-output\-format\fR] [\fB\-S\fR|\fB\-\-stack\-size\fR] [\fB\-j\fR|\fB\-\-output\-json\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIPATH\fR] +\fBdust\fR [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-T\fR|\fB\-\-threads\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-I\fR|\fB\-\-ignore\-all\-in\-file\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-C\fR|\fB\-\-force\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-B\fR|\fB\-\-bars\-on\-right\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore_hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file_types\fR] [\fB\-w\fR|\fB\-\-terminal_width\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-\-print\-errors\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fB\-o\fR|\fB\-\-output\-format\fR] [\fB\-S\fR|\fB\-\-stack\-size\fR] [\fB\-j\fR|\fB\-\-output\-json\fR] [\fB\-M\fR|\fB\-\-mtime\fR] [\fB\-A\fR|\fB\-\-atime\fR] [\fB\-y\fR|\fB\-\-ctime\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIPATH\fR] .SH DESCRIPTION Like du but more intuitive .SH OPTIONS @@ -103,6 +103,15 @@ Specify memory to use as stack size \- use if you see: \*(Aqfatal runtime error: \fB\-j\fR, \fB\-\-output\-json\fR Output the directory tree as json to the current directory .TP +\fB\-M\fR, \fB\-\-mtime\fR ++/\-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and \-n => (𝑐𝑢𝑟𝑟−𝑛, +∞) +.TP +\fB\-A\fR, \fB\-\-atime\fR +just like \-mtime, but based on file access time +.TP +\fB\-y\fR, \fB\-\-ctime\fR +just like \-mtime, but based on file change time +.TP \fB\-h\fR, \fB\-\-help\fR Print help .TP diff --git a/src/cli.rs b/src/cli.rs index 17180582..7587cb06 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -259,4 +259,31 @@ pub fn build_cli() -> Command { .action(clap::ArgAction::SetTrue) .help("Output the directory tree as json to the current directory"), ) + .arg( + Arg::new("mtime") + .short('M') + .long("mtime") + .num_args(1) + .allow_hyphen_values(true) + .value_parser(value_parser!(String)) + .help("+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr−(n+1)), n => [curr−(n+1), curr−n), and -n => (𝑐𝑢𝑟𝑟−𝑛, +∞)") + ) + .arg( + Arg::new("atime") + .short('A') + .long("atime") + .num_args(1) + .allow_hyphen_values(true) + .value_parser(value_parser!(String)) + .help("just like -mtime, but based on file access time") + ) + .arg( + Arg::new("ctime") + .short('y') + .long("ctime") + .num_args(1) + .allow_hyphen_values(true) + .value_parser(value_parser!(String)) + .help("just like -mtime, but based on file change time") + ) } diff --git a/src/config.rs b/src/config.rs index 89d42680..4a08cf74 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use chrono::{Local, TimeZone}; use clap::ArgMatches; use config_file::FromConfigFile; use regex::Regex; @@ -6,8 +7,11 @@ use std::io::IsTerminal; use std::path::Path; use std::path::PathBuf; +use crate::dir_walker::Operater; use crate::display::get_number_format; +pub static DAY_SECEONDS: i64 = 24 * 60 * 60; + #[derive(Deserialize, Default)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] @@ -132,6 +136,61 @@ impl Config { pub fn get_output_json(&self, options: &ArgMatches) -> bool { Some(true) == self.output_json || options.get_flag("output_json") } + + pub fn get_modified_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { + get_filter_time_operator( + options.get_one::("mtime"), + get_current_date_epoch_seconds(), + ) + } + + pub fn get_accessed_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { + get_filter_time_operator( + options.get_one::("atime"), + get_current_date_epoch_seconds(), + ) + } + + pub fn get_created_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { + get_filter_time_operator( + options.get_one::("ctime"), + get_current_date_epoch_seconds(), + ) + } +} + +fn get_current_date_epoch_seconds() -> i64 { + // calcurate current date epoch seconds + let now = Local::now(); + let current_date = now.date_naive(); + + let current_date_time = current_date.and_hms_opt(0, 0, 0).unwrap(); + Local + .from_local_datetime(¤t_date_time) + .unwrap() + .timestamp() +} + +fn get_filter_time_operator( + option_value: Option<&String>, + current_date_epoch_seconds: i64, +) -> (Operater, i64) { + match option_value { + Some(val) => { + let time = current_date_epoch_seconds + - val + .parse::() + .unwrap_or_else(|_| panic!("invalid data format")) + .abs() + * DAY_SECEONDS; + match val.chars().next().expect("Value should not be empty") { + '+' => (Operater::LessThan, time - DAY_SECEONDS), + '-' => (Operater::GreaterThan, time), + _ => (Operater::Equal, time - DAY_SECEONDS), + } + } + None => (Operater::GreaterThan, 0), + } } fn convert_min_size(input: &str) -> Option { @@ -191,8 +250,22 @@ pub fn get_config() -> Config { mod tests { #[allow(unused_imports)] use super::*; + use chrono::{Datelike, Timelike}; use clap::{value_parser, Arg, ArgMatches, Command}; + #[test] + fn test_get_current_date_epoch_seconds() { + let epoch_seconds = get_current_date_epoch_seconds(); + let dt = Local.timestamp_opt(epoch_seconds, 0).unwrap(); + + assert_eq!(dt.hour(), 0); + assert_eq!(dt.minute(), 0); + assert_eq!(dt.second(), 0); + assert_eq!(dt.date_naive().day(), Local::now().date_naive().day()); + assert_eq!(dt.date_naive().month(), Local::now().date_naive().month()); + assert_eq!(dt.date_naive().year(), Local::now().date_naive().year()); + } + #[test] fn test_conversion() { assert_eq!(convert_min_size("55"), Some(55)); diff --git a/src/dir_walker.rs b/src/dir_walker.rs index 351c11a0..f7e813e1 100644 --- a/src/dir_walker.rs +++ b/src/dir_walker.rs @@ -7,6 +7,7 @@ use crate::progress::Operation; use crate::progress::PAtomicInfo; use crate::progress::RuntimeErrors; use crate::progress::ORDERING; +use crate::utils::is_filtered_out_due_to_file_time; use crate::utils::is_filtered_out_due_to_invert_regex; use crate::utils::is_filtered_out_due_to_regex; use rayon::iter::ParallelBridge; @@ -20,11 +21,22 @@ use crate::node::build_node; use std::fs::DirEntry; use crate::platform::get_metadata; + +#[derive(Debug)] +pub enum Operater { + Equal = 0, + LessThan = 1, + GreaterThan = 2, +} + pub struct WalkData<'a> { pub ignore_directories: HashSet, pub filter_regex: &'a [Regex], pub invert_filter_regex: &'a [Regex], pub allowed_filesystems: HashSet, + pub filter_modified_time: (Operater, i64), + pub filter_accessed_time: (Operater, i64), + pub filter_changed_time: (Operater, i64), pub use_apparent_size: bool, pub by_filecount: bool, pub ignore_hidden: bool, @@ -99,13 +111,27 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.'); let is_ignored_path = walk_data.ignore_directories.contains(&entry.path()); - if !walk_data.allowed_filesystems.is_empty() { - let size_inode_device = get_metadata(&entry.path(), false); - - if let Some((_size, Some((_id, dev)))) = size_inode_device { - if !walk_data.allowed_filesystems.contains(&dev) { - return true; - } + let size_inode_device = get_metadata(&entry.path(), false); + if let Some((_size, Some((_id, dev)), (modified_time, accessed_time, changed_time))) = + size_inode_device + { + if !walk_data.allowed_filesystems.is_empty() + && !walk_data.allowed_filesystems.contains(&dev) + { + return true; + } + if entry.path().is_file() + && [ + (&walk_data.filter_modified_time, modified_time), + (&walk_data.filter_accessed_time, accessed_time), + (&walk_data.filter_changed_time, changed_time), + ] + .iter() + .any(|(filter_time, actual_time)| { + is_filtered_out_due_to_file_time(filter_time, *actual_time) + }) + { + return true; } } @@ -130,6 +156,7 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { let prog_data = &walk_data.progress_data; let errors = &walk_data.errors; + if errors.lock().unwrap().abort { return None; } @@ -161,13 +188,10 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { let node = build_node( entry.path(), vec![], - walk_data.filter_regex, - walk_data.invert_filter_regex, - walk_data.use_apparent_size, data.is_symlink(), data.is_file(), - walk_data.by_filecount, depth, + walk_data, ); prog_data.num_files.fetch_add(1, ORDERING); @@ -216,17 +240,7 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { } vec![] }; - build_node( - dir, - children, - walk_data.filter_regex, - walk_data.invert_filter_regex, - walk_data.use_apparent_size, - false, - false, - walk_data.by_filecount, - depth, - ) + build_node(dir, children, false, false, depth, walk_data) } mod tests { diff --git a/src/main.rs b/src/main.rs index 12f7105f..f62a996a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -211,11 +211,18 @@ fn main() { indicator.spawn(output_format.clone()) } + let filter_modified_time = config.get_modified_time_operator(&options); + let filter_accessed_time = config.get_accessed_time_operator(&options); + let filter_changed_time = config.get_created_time_operator(&options); + let walk_data = WalkData { ignore_directories: ignored_full_path, filter_regex: &filter_regexs, invert_filter_regex: &invert_filter_regexs, allowed_filesystems, + filter_modified_time, + filter_accessed_time, + filter_changed_time, use_apparent_size: config.get_apparent_size(&options), by_filecount, ignore_hidden, diff --git a/src/node.rs b/src/node.rs index 78ecbf06..08b28f3a 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,8 +1,9 @@ +use crate::dir_walker::WalkData; use crate::platform::get_metadata; +use crate::utils::is_filtered_out_due_to_file_time; use crate::utils::is_filtered_out_due_to_invert_regex; use crate::utils::is_filtered_out_due_to_regex; -use regex::Regex; use std::cmp::Ordering; use std::path::PathBuf; @@ -19,14 +20,14 @@ pub struct Node { pub fn build_node( dir: PathBuf, children: Vec, - filter_regex: &[Regex], - invert_filter_regex: &[Regex], - use_apparent_size: bool, is_symlink: bool, is_file: bool, - by_filecount: bool, depth: usize, + walk_data: &WalkData, ) -> Option { + let use_apparent_size = walk_data.use_apparent_size; + let by_filecount = walk_data.by_filecount; + get_metadata(&dir, use_apparent_size).map(|data| { let inode_device = if is_symlink && !use_apparent_size { None @@ -34,11 +35,19 @@ pub fn build_node( data.1 }; - let size = if is_filtered_out_due_to_regex(filter_regex, &dir) - || is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir) + let size = if is_filtered_out_due_to_regex(walk_data.filter_regex, &dir) + || is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &dir) || (is_symlink && !use_apparent_size) || by_filecount && !is_file - { + || [ + (&walk_data.filter_modified_time, data.2 .0), + (&walk_data.filter_accessed_time, data.2 .1), + (&walk_data.filter_changed_time, data.2 .2), + ] + .iter() + .any(|(filter_time, actual_time)| { + is_filtered_out_due_to_file_time(filter_time, *actual_time) + }) { 0 } else if by_filecount { 1 diff --git a/src/platform.rs b/src/platform.rs index 3071bf6e..83c9ff53 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -10,15 +10,29 @@ fn get_block_size() -> u64 { 512 } +type InodeAndDevice = (u64, u64); +type FileTime = (i64, i64, i64); + #[cfg(target_family = "unix")] -pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> { +pub fn get_metadata( + d: &Path, + use_apparent_size: bool, +) -> Option<(u64, Option, FileTime)> { use std::os::unix::fs::MetadataExt; match d.metadata() { Ok(md) => { if use_apparent_size { - Some((md.len(), Some((md.ino(), md.dev())))) + Some(( + md.len(), + Some((md.ino(), md.dev())), + (md.mtime(), md.atime(), md.ctime()), + )) } else { - Some((md.blocks() * get_block_size(), Some((md.ino(), md.dev())))) + Some(( + md.blocks() * get_block_size(), + Some((md.ino(), md.dev())), + (md.mtime(), md.atime(), md.ctime()), + )) } } Err(_e) => None, @@ -26,7 +40,10 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u } #[cfg(target_family = "windows")] -pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> { +pub fn get_metadata( + d: &Path, + use_apparent_size: bool, +) -> Option<(u64, Option, FileTime)> { // On windows opening the file to get size, file ID and volume can be very // expensive because 1) it causes a few system calls, and more importantly 2) it can cause // windows defender to scan the file. @@ -93,7 +110,7 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u fn get_metadata_expensive( d: &Path, use_apparent_size: bool, - ) -> Option<(u64, Option<(u64, u64)>)> { + ) -> Option<(u64, Option, FileTime)> { use winapi_util::file::information; let h = handle_from_path_limited(d).ok()?; @@ -104,11 +121,21 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u Some(( d.size_on_disk().ok()?, Some((info.file_index(), info.volume_serial_number())), + ( + info.last_write_time().unwrap() as i64, + info.last_access_time().unwrap() as i64, + info.creation_time().unwrap() as i64, + ), )) } else { Some(( info.file_size(), Some((info.file_index(), info.volume_serial_number())), + ( + info.last_write_time().unwrap() as i64, + info.last_access_time().unwrap() as i64, + info.creation_time().unwrap() as i64, + ), )) } } @@ -142,7 +169,15 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u || md.file_attributes() == FILE_ATTRIBUTE_NORMAL) && !((attr_filtered & IS_PROBABLY_ONEDRIVE != 0) && use_apparent_size) { - Some((md.len(), None)) + Some(( + md.len(), + None, + ( + md.last_write_time() as i64, + md.last_access_time() as i64, + md.creation_time() as i64, + ), + )) } else { get_metadata_expensive(d, use_apparent_size) } diff --git a/src/utils.rs b/src/utils.rs index 30ea705c..4619197a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,9 @@ use platform::get_metadata; use std::collections::HashSet; use std::path::{Path, PathBuf}; +use crate::config::DAY_SECEONDS; + +use crate::dir_walker::Operater; use crate::platform; use regex::Regex; @@ -36,7 +39,7 @@ pub fn get_filesystem_devices<'a, P: IntoIterator>(paths: P) paths .into_iter() .filter_map(|p| match get_metadata(p, false) { - Some((_size, Some((_id, dev)))) => Some(dev), + Some((_size, Some((_id, dev)), _time)) => Some(dev), _ => None, }) .collect() @@ -62,6 +65,16 @@ pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool } } +pub fn is_filtered_out_due_to_file_time(filter_time: &(Operater, i64), actual_time: i64) -> bool { + match filter_time { + (Operater::Equal, bound_time) => { + !(actual_time >= *bound_time && actual_time < *bound_time + DAY_SECEONDS) + } + (Operater::GreaterThan, bound_time) => actual_time < *bound_time, + (Operater::LessThan, bound_time) => actual_time > *bound_time, + } +} + pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool { filter_regex .iter()