extern crate ctrlc;
use exec;
use fshelper;
use internal::{error, FdOptions, EXITCODE_SIGINT, MAX_BUFFER_LENGTH};
use output;
use std::process;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::thread;
use std::time;
use ignore::{self, WalkBuilder};
use ignore::overrides::OverrideBuilder;
use regex::Regex;
enum ReceiverMode {
Buffering,
Streaming,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum FileType {
RegularFile,
Directory,
SymLink,
}
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter
.next()
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
let threads = config.threads;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
error(&format!("Error: malformed exclude pattern '{}'", pattern));
}
}
let overrides = override_builder.build().unwrap_or_else(|_| {
error("Mismatch in exclude patterns");
});
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_gitignore)
.parents(config.read_ignore || config.read_gitignore)
.git_global(config.read_gitignore)
.git_exclude(config.read_gitignore)
.overrides(overrides)
.follow_links(config.follow_links)
.max_depth(config.max_depth);
for path_entry in path_iter {
walker.add(path_entry.as_path());
}
let parallel_walker = walker.threads(threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
let receiver_wtq = Arc::clone(&wants_to_quit);
let sender_wtq = Arc::clone(&wants_to_quit);
if config.ls_colors.is_some() {
let wq = Arc::clone(&receiver_wtq);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
}).unwrap();
}
let rx_config = Arc::clone(&config);
let receiver_thread = thread::spawn(move || {
if let Some(ref cmd) = rx_config.command {
let shared_rx = Arc::new(Mutex::new(rx));
let out_perm = Arc::new(Mutex::new(()));
let cmd = Arc::new(cmd.clone());
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let rx = Arc::clone(&shared_rx);
let cmd = Arc::clone(&cmd);
let out_perm = Arc::clone(&out_perm);
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
handles.push(handle);
}
for h in handles {
h.join().unwrap();
}
} else {
let start = time::Instant::now();
let mut buffer = vec![];
let mut mode = ReceiverMode::Buffering;
let max_buffer_time = rx_config
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
for value in rx {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
}
buffer.clear();
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
if !buffer.is_empty() {
buffer.sort();
for value in buffer {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
});
parallel_walker.run(|| {
let config = Arc::clone(&config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let wants_to_quit = Arc::clone(&sender_wtq);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
return ignore::WalkState::Quit;
}
let entry = match entry_o {
Ok(e) => e,
Err(_) => return ignore::WalkState::Continue,
};
let entry_path = entry.path();
if entry.depth() == 0 {
return ignore::WalkState::Continue;
}
if (entry.file_type().map_or(false, |ft| ft.is_file())
&& !config.file_types.contains(&FileType::RegularFile))
|| (entry.file_type().map_or(false, |ft| ft.is_dir())
&& !config.file_types.contains(&FileType::Directory))
|| (entry.file_type().map_or(false, |ft| ft.is_symlink())
&& !config.file_types.contains(&FileType::SymLink))
{
return ignore::WalkState::Continue;
}
if let Some(ref filter_exts) = config.extensions {
let entry_ext = entry_path
.extension()
.map(|e| e.to_string_lossy().to_lowercase());
if entry_ext.map_or(true, |ext| !filter_exts.contains(&ext)) {
return ignore::WalkState::Continue;
}
}
let search_str_o = if config.search_full_path {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
Err(_) => error("Error: unable to get full path."),
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
};
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
tx_thread.send(entry_path.to_owned()).unwrap()
}
}
ignore::WalkState::Continue
})
});
drop(tx);
receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(EXITCODE_SIGINT);
}
}