diff --git a/src/entry.rs b/src/entry.rs new file mode 100644 index 0000000..7d78e2e --- /dev/null +++ b/src/entry.rs @@ -0,0 +1,62 @@ +use std::{ + fs::{FileType, Metadata}, + path::{Path, PathBuf}, +}; + +use once_cell::unsync::OnceCell; + +enum DirEntryInner { + Normal(ignore::DirEntry), + BrokenSymlink(PathBuf), +} + +pub struct DirEntry { + inner: DirEntryInner, + metadata: OnceCell>, +} + +impl DirEntry { + pub fn normal(e: ignore::DirEntry) -> Self { + Self { + inner: DirEntryInner::Normal(e), + metadata: OnceCell::new(), + } + } + + pub fn broken_symlink(path: PathBuf) -> Self { + Self { + inner: DirEntryInner::BrokenSymlink(path), + metadata: OnceCell::new(), + } + } + + pub fn path(&self) -> &Path { + match &self.inner { + DirEntryInner::Normal(e) => e.path(), + DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(), + } + } + + pub fn file_type(&self) -> Option { + match &self.inner { + DirEntryInner::Normal(e) => e.file_type(), + DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()), + } + } + + pub fn metadata(&self) -> Option<&Metadata> { + self.metadata + .get_or_init(|| match &self.inner { + DirEntryInner::Normal(e) => e.metadata().ok(), + DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(), + }) + .as_ref() + } + + pub fn depth(&self) -> Option { + match &self.inner { + DirEntryInner::Normal(e) => Some(e.depth()), + DirEntryInner::BrokenSymlink(_) => None, + } + } +} diff --git a/src/exec/job.rs b/src/exec/job.rs index 0effc44..45107df 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -1,8 +1,8 @@ -use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crossbeam_channel::Receiver; +use crate::entry::DirEntry; use crate::error::print_error; use crate::exit_codes::{merge_exitcodes, ExitCode}; use crate::walk::WorkerResult; @@ -26,8 +26,8 @@ pub fn job( // Obtain the next result from the receiver, else if the channel // has closed, exit from the loop - let value: PathBuf = match lock.recv() { - Ok(WorkerResult::Entry(path)) => path, + let value: DirEntry = match lock.recv() { + Ok(WorkerResult::Entry(val)) => val, Ok(WorkerResult::Error(err)) => { if show_filesystem_errors { print_error(err.to_string()); @@ -40,7 +40,7 @@ pub fn job( // Drop the lock so that other threads can read from the receiver. drop(lock); // Generate a command, execute it and store its exit code. - results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output)) + results.push(cmd.generate_and_execute(value.path(), Arc::clone(&out_perm), buffer_output)) } // Returns error in case of any error. merge_exitcodes(results) @@ -54,7 +54,7 @@ pub fn batch( limit: usize, ) -> ExitCode { let paths = rx.iter().filter_map(|value| match value { - WorkerResult::Entry(path) => Some(path), + WorkerResult::Entry(val) => Some(val.path().to_owned()), WorkerResult::Error(err) => { if show_filesystem_errors { print_error(err.to_string()); diff --git a/src/filesystem.rs b/src/filesystem.rs index 6680ae5..c99bab8 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf}; use normpath::PathExt; -use crate::walk; +use crate::entry; pub fn path_absolute_form(path: &Path) -> io::Result { if path.is_absolute() { @@ -51,7 +51,7 @@ pub fn is_executable(_: &fs::Metadata) -> bool { false } -pub fn is_empty(entry: &walk::DirEntry) -> bool { +pub fn is_empty(entry: &entry::DirEntry) -> bool { if let Some(file_type) = entry.file_type() { if file_type.is_dir() { if let Ok(mut entries) = fs::read_dir(entry.path()) { diff --git a/src/filetypes.rs b/src/filetypes.rs index fc0962d..f767767 100644 --- a/src/filetypes.rs +++ b/src/filetypes.rs @@ -1,5 +1,5 @@ +use crate::entry; use crate::filesystem; -use crate::walk; /// Whether or not to show #[derive(Default)] @@ -14,7 +14,7 @@ pub struct FileTypes { } impl FileTypes { - pub fn should_ignore(&self, entry: &walk::DirEntry) -> bool { + pub fn should_ignore(&self, entry: &entry::DirEntry) -> bool { if let Some(ref entry_type) = entry.file_type() { (!self.files && entry_type.is_file()) || (!self.directories && entry_type.is_dir()) diff --git a/src/main.rs b/src/main.rs index 4f4ab14..9c9ef23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod app; mod config; +mod entry; mod error; mod exec; mod exit_codes; diff --git a/src/output.rs b/src/output.rs index bcb9505..37ed357 100644 --- a/src/output.rs +++ b/src/output.rs @@ -5,6 +5,7 @@ use std::path::Path; use lscolors::{Indicator, LsColors, Style}; use crate::config::Config; +use crate::entry::DirEntry; use crate::error::print_error; use crate::exit_codes::ExitCode; use crate::filesystem::strip_current_dir; @@ -14,11 +15,12 @@ fn replace_path_separator(path: &str, new_path_separator: &str) -> String { } // TODO: this function is performance critical and can probably be optimized -pub fn print_entry(stdout: &mut W, entry: &Path, config: &Config) { +pub fn print_entry(stdout: &mut W, entry: &DirEntry, config: &Config) { + let path = entry.path(); let path = if config.strip_cwd_prefix { - strip_current_dir(entry) + strip_current_dir(path) } else { - entry + path }; let r = if let Some(ref ls_colors) = config.ls_colors { diff --git a/src/walk.rs b/src/walk.rs index d69c813..0402a0c 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -1,8 +1,7 @@ use std::ffi::OsStr; -use std::fs::{FileType, Metadata}; use std::io; use std::mem; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; @@ -13,10 +12,10 @@ use anyhow::{anyhow, Result}; use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender}; use ignore::overrides::OverrideBuilder; use ignore::{self, WalkBuilder}; -use once_cell::unsync::OnceCell; use regex::bytes::Regex; use crate::config::Config; +use crate::entry::DirEntry; use crate::error::print_error; use crate::exec; use crate::exit_codes::{merge_exitcodes, ExitCode}; @@ -36,7 +35,7 @@ enum ReceiverMode { /// The Worker threads can result in a valid entry having PathBuf or an error. pub enum WorkerResult { - Entry(PathBuf), + Entry(DirEntry), Error(ignore::Error), } @@ -187,7 +186,7 @@ struct ReceiverBuffer { /// The deadline to switch to streaming mode. deadline: Instant, /// The buffer of quickly received paths. - buffer: Vec, + buffer: Vec, /// Result count. num_results: usize, } @@ -244,20 +243,20 @@ impl ReceiverBuffer { /// Wait for a result or state change. fn poll(&mut self) -> Result<(), ExitCode> { match self.recv() { - Ok(WorkerResult::Entry(path)) => { + Ok(WorkerResult::Entry(dir_entry)) => { if self.config.quiet { return Err(ExitCode::HasResults(true)); } match self.mode { ReceiverMode::Buffering => { - self.buffer.push(path); + self.buffer.push(dir_entry); if self.buffer.len() > MAX_BUFFER_LENGTH { self.stream()?; } } ReceiverMode::Streaming => { - self.print(&path)?; + self.print(&dir_entry)?; self.flush()?; } } @@ -286,8 +285,8 @@ impl ReceiverBuffer { } /// Output a path. - fn print(&mut self, path: &Path) -> Result<(), ExitCode> { - output::print_entry(&mut self.stdout, path, &self.config); + fn print(&mut self, entry: &DirEntry) -> Result<(), ExitCode> { + output::print_entry(&mut self.stdout, entry, &self.config); if self.interrupt_flag.load(Ordering::Relaxed) { // Ignore any errors on flush, because we're about to exit anyway @@ -313,7 +312,7 @@ impl ReceiverBuffer { /// Stop looping. fn stop(&mut self) -> Result<(), ExitCode> { if self.mode == ReceiverMode::Buffering { - self.buffer.sort(); + self.buffer.sort_by(|e1, e2| e1.path().cmp(e2.path())); self.stream()?; } @@ -404,62 +403,6 @@ fn spawn_receiver( }) } -enum DirEntryInner { - Normal(ignore::DirEntry), - BrokenSymlink(PathBuf), -} - -pub struct DirEntry { - inner: DirEntryInner, - metadata: OnceCell>, -} - -impl DirEntry { - fn normal(e: ignore::DirEntry) -> Self { - Self { - inner: DirEntryInner::Normal(e), - metadata: OnceCell::new(), - } - } - - fn broken_symlink(path: PathBuf) -> Self { - Self { - inner: DirEntryInner::BrokenSymlink(path), - metadata: OnceCell::new(), - } - } - - pub fn path(&self) -> &Path { - match &self.inner { - DirEntryInner::Normal(e) => e.path(), - DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(), - } - } - - pub fn file_type(&self) -> Option { - match &self.inner { - DirEntryInner::Normal(e) => e.file_type(), - DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()), - } - } - - pub fn metadata(&self) -> Option<&Metadata> { - self.metadata - .get_or_init(|| match &self.inner { - DirEntryInner::Normal(e) => e.metadata().ok(), - DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(), - }) - .as_ref() - } - - pub fn depth(&self) -> Option { - match &self.inner { - DirEntryInner::Normal(e) => Some(e.depth()), - DirEntryInner::BrokenSymlink(_) => None, - } - } -} - fn spawn_senders( config: &Arc, quit_flag: &Arc, @@ -610,7 +553,7 @@ fn spawn_senders( } } - let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned())); + let send_result = tx_thread.send(WorkerResult::Entry(entry)); if send_result.is_err() { return ignore::WalkState::Quit;