use anyhow::Error;
use crossbeam_channel::{bounded, Sender};
use itertools::Itertools;
use std::io::{BufWriter, Write};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
pub mod args;
pub mod filetype;
pub mod glob;
pub mod interrupt;
pub mod regex;
pub mod walk;
use args::Args;
use walk::{Entry, WalkState};
const BATCH_SIZE: usize = 256;
const CHAN_MULT: usize = 4;
struct BatchSender {
buf: Vec<Entry>,
tx: Sender<Vec<Entry>>,
closed: bool,
}
impl BatchSender {
fn new(tx: Sender<Vec<Entry>>) -> Self {
Self { buf: Vec::with_capacity(BATCH_SIZE), tx, closed: false }
}
fn push(&mut self, entry: Entry) -> bool {
self.buf.push(entry);
if self.buf.len() >= BATCH_SIZE {
self.flush()
} else {
true
}
}
fn flush(&mut self) -> bool {
if self.buf.is_empty() {
return !self.closed;
}
let batch =
std::mem::replace(&mut self.buf, Vec::with_capacity(BATCH_SIZE));
if self.tx.send(batch).is_err() {
self.closed = true;
return false;
}
true
}
}
impl Drop for BatchSender {
fn drop(&mut self) {
self.flush();
}
}
pub fn run<W, F>(args: &Args, make_out: F) -> Result<(), Error>
where
W: Write,
F: FnOnce() -> W + Send + 'static,
{
let shutdown = Arc::new(AtomicBool::new(false));
interrupt::setup_interrupt_handler(&shutdown)?;
let glob_name =
glob::build_glob_set(args.name.as_deref(), args.case_insensitive)?;
let glob_enabled = args.name.is_some();
let regex_name =
regex::build_regex_set(args.regex.as_deref(), args.case_insensitive)?;
let regex_enabled = args.regex.is_some();
let (tx, rx) = bounded::<Vec<Entry>>(CHAN_MULT * (args.threads - 1));
let print_thread = thread::spawn(move || {
let mut stdout = BufWriter::with_capacity(256 * 1024, make_out());
for batch in rx {
for entry in batch {
if glob_enabled && !glob_name.is_match(entry.file_name()) {
continue;
}
if regex_enabled
&& !regex_name.is_match(®ex::path_to_bytes(&entry.path))
{
continue;
}
#[cfg(unix)]
stdout
.write_all(entry.path.as_os_str().as_bytes())
.unwrap_or(());
#[cfg(not(unix))]
stdout
.write_all(entry.path.to_string_lossy().as_bytes())
.unwrap_or(());
stdout.write_all(b"\n").unwrap_or(());
}
}
stdout.flush().unwrap_or(());
});
let unique_paths: Vec<&Path> =
args.path.iter().map(PathBuf::as_path).unique().collect();
let filetype_proto = filetype::FileType::new(&args.file_type);
walk::walk_parallel(args, &unique_paths, || {
let filetype = filetype_proto;
let shutdown = Arc::clone(&shutdown);
let mut batch = BatchSender::new(tx.clone());
move |entry: Entry| {
if shutdown.load(Ordering::Relaxed) {
return WalkState::Quit;
}
if filetype.ignore_filetype(entry.file_type, &entry.path) {
return WalkState::Continue;
}
if !batch.push(entry) {
return WalkState::Quit;
}
WalkState::Continue
}
});
drop(tx);
print_thread.join().unwrap();
Ok(())
}