2017-10-21 10:16:03 +02:00
|
|
|
// Copyright (c) 2017 fd developers
|
|
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
|
|
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
|
|
// at your option. All files in the project carrying such
|
|
|
|
// notice may not be copied, modified, or distributed except
|
|
|
|
// according to those terms.
|
|
|
|
|
2017-11-22 23:05:09 +01:00
|
|
|
extern crate ctrlc;
|
|
|
|
|
2017-10-25 21:33:44 +02:00
|
|
|
use exec;
|
2017-10-10 08:01:17 +02:00
|
|
|
use fshelper;
|
2017-10-18 20:04:34 +02:00
|
|
|
use internal::{error, FdOptions};
|
2017-10-10 08:01:17 +02:00
|
|
|
use output;
|
|
|
|
|
2017-10-14 19:57:15 +02:00
|
|
|
use std::path::Path;
|
2017-10-14 18:04:11 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2017-11-22 23:05:09 +01:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2017-10-10 08:01:17 +02:00
|
|
|
use std::sync::mpsc::channel;
|
|
|
|
use std::thread;
|
|
|
|
use std::time;
|
|
|
|
|
|
|
|
use ignore::{self, WalkBuilder};
|
2017-10-22 23:00:19 +02:00
|
|
|
use ignore::overrides::OverrideBuilder;
|
2017-10-14 18:04:11 +02:00
|
|
|
use regex::Regex;
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
/// The receiver thread can either be buffering results or directly streaming to the console.
|
|
|
|
enum ReceiverMode {
|
|
|
|
/// Receiver is still buffering in order to sort the results, if the search finishes fast
|
|
|
|
/// enough.
|
|
|
|
Buffering,
|
|
|
|
|
|
|
|
/// Receiver is directly printing results to the output.
|
|
|
|
Streaming,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The type of file to search for.
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub enum FileType {
|
|
|
|
Any,
|
|
|
|
RegularFile,
|
|
|
|
Directory,
|
|
|
|
SymLink,
|
|
|
|
}
|
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
|
|
|
///
|
|
|
|
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
|
|
|
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
|
|
|
/// path will simply be written to standard output.
|
2017-10-18 20:04:34 +02:00
|
|
|
pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
2017-10-10 08:01:17 +02:00
|
|
|
let (tx, rx) = channel();
|
2017-10-14 18:04:11 +02:00
|
|
|
let threads = config.threads;
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-22 23:00:19 +02:00
|
|
|
let mut override_builder = OverrideBuilder::new(root);
|
|
|
|
|
2017-10-26 21:13:56 +02:00
|
|
|
for pattern in &config.exclude_patterns {
|
2017-10-22 23:00:19 +02:00
|
|
|
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");
|
|
|
|
});
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
let walker = WalkBuilder::new(root)
|
2017-10-12 08:01:51 +02:00
|
|
|
.hidden(config.ignore_hidden)
|
|
|
|
.ignore(config.read_ignore)
|
2017-11-21 22:54:00 +01:00
|
|
|
.git_ignore(config.read_gitignore)
|
|
|
|
.parents(config.read_ignore || config.read_gitignore)
|
|
|
|
.git_global(config.read_gitignore)
|
|
|
|
.git_exclude(config.read_gitignore)
|
2017-10-22 23:00:19 +02:00
|
|
|
.overrides(overrides)
|
2017-10-12 08:01:51 +02:00
|
|
|
.follow_links(config.follow_links)
|
|
|
|
.max_depth(config.max_depth)
|
2017-10-14 18:04:11 +02:00
|
|
|
.threads(threads)
|
2017-10-12 08:01:51 +02:00
|
|
|
.build_parallel();
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-11-22 23:05:09 +01:00
|
|
|
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
|
|
|
|
|
|
|
if let Some(_) = config.ls_colors {
|
|
|
|
let wq = Arc::clone(&wants_to_quit);
|
|
|
|
ctrlc::set_handler(move || { wq.store(true, Ordering::Relaxed); }).unwrap();
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
// Spawn the thread that receives all results through the channel.
|
|
|
|
let rx_config = Arc::clone(&config);
|
|
|
|
let receiver_thread = thread::spawn(move || {
|
2017-10-14 18:04:11 +02:00
|
|
|
// This will be set to `Some` if the `--exec` argument was supplied.
|
|
|
|
if let Some(ref cmd) = rx_config.command {
|
|
|
|
let shared_rx = Arc::new(Mutex::new(rx));
|
2017-10-14 23:59:36 +02:00
|
|
|
|
|
|
|
let out_perm = Arc::new(Mutex::new(()));
|
|
|
|
|
2017-10-25 21:33:44 +02:00
|
|
|
// TODO: the following line is a workaround to replace the `unsafe` block that was
|
|
|
|
// previously used here to avoid the (unnecessary?) cloning of the command. The
|
|
|
|
// `unsafe` block caused problems on some platforms (SIGILL instructions on Linux) and
|
|
|
|
// therefore had to be removed.
|
|
|
|
let cmd = Arc::new(cmd.clone());
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
// Each spawned job will store it's thread handle in here.
|
|
|
|
let mut handles = Vec::with_capacity(threads);
|
|
|
|
for _ in 0..threads {
|
2017-10-22 11:47:05 +02:00
|
|
|
let rx = Arc::clone(&shared_rx);
|
|
|
|
let cmd = Arc::clone(&cmd);
|
|
|
|
let out_perm = Arc::clone(&out_perm);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-14 20:04:04 +02:00
|
|
|
// Spawn a job thread that will listen for and execute inputs.
|
2017-10-18 20:04:34 +02:00
|
|
|
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
// Push the handle of the spawned thread into the vector for later joining.
|
|
|
|
handles.push(handle);
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
// Wait for all threads to exit before exiting the program.
|
2017-10-14 20:04:04 +02:00
|
|
|
for h in handles {
|
|
|
|
h.join().unwrap();
|
|
|
|
}
|
2017-10-14 18:04:11 +02:00
|
|
|
} else {
|
|
|
|
let start = time::Instant::now();
|
|
|
|
|
|
|
|
let mut buffer = vec![];
|
|
|
|
|
|
|
|
// Start in buffering mode
|
|
|
|
let mut mode = ReceiverMode::Buffering;
|
|
|
|
|
|
|
|
// Maximum time to wait before we start streaming to the console.
|
|
|
|
let max_buffer_time = rx_config.max_buffer_time.unwrap_or_else(
|
|
|
|
|| time::Duration::from_millis(100),
|
|
|
|
);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
for value in rx {
|
|
|
|
match mode {
|
|
|
|
ReceiverMode::Buffering => {
|
|
|
|
buffer.push(value);
|
|
|
|
|
|
|
|
// Have we reached the maximum time?
|
|
|
|
if time::Instant::now() - start > max_buffer_time {
|
|
|
|
// Flush the buffer
|
|
|
|
for v in &buffer {
|
2017-11-22 23:05:09 +01:00
|
|
|
output::print_entry(&v, &rx_config, &wants_to_quit);
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
buffer.clear();
|
|
|
|
|
|
|
|
// Start streaming
|
|
|
|
mode = ReceiverMode::Streaming;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ReceiverMode::Streaming => {
|
2017-11-22 23:05:09 +01:00
|
|
|
output::print_entry(&value, &rx_config, &wants_to_quit);
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
|
|
|
|
// anything to the console, yet. In this case, sort the results and print them:
|
|
|
|
if !buffer.is_empty() {
|
|
|
|
buffer.sort();
|
|
|
|
for value in buffer {
|
2017-11-22 23:05:09 +01:00
|
|
|
output::print_entry(&value, &rx_config, &wants_to_quit);
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Spawn the sender threads.
|
|
|
|
walker.run(|| {
|
|
|
|
let config = Arc::clone(&config);
|
|
|
|
let pattern = Arc::clone(&pattern);
|
|
|
|
let tx_thread = tx.clone();
|
2017-10-11 23:08:41 +02:00
|
|
|
let root = root.to_owned();
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
Box::new(move |entry_o| {
|
|
|
|
let entry = match entry_o {
|
|
|
|
Ok(e) => e,
|
|
|
|
Err(_) => return ignore::WalkState::Continue,
|
|
|
|
};
|
|
|
|
|
2017-10-11 23:08:41 +02:00
|
|
|
let entry_path = entry.path();
|
|
|
|
|
|
|
|
if entry_path == root {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
// Filter out unwanted file types.
|
|
|
|
match config.file_type {
|
|
|
|
FileType::Any => (),
|
2017-10-12 08:01:51 +02:00
|
|
|
FileType::RegularFile => {
|
2017-10-23 17:47:45 +02:00
|
|
|
if entry.file_type().map_or(true, |ft| !ft.is_file()) {
|
2017-10-12 08:01:51 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FileType::Directory => {
|
2017-10-23 17:47:45 +02:00
|
|
|
if entry.file_type().map_or(true, |ft| !ft.is_dir()) {
|
2017-10-12 08:01:51 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FileType::SymLink => {
|
2017-10-23 17:47:45 +02:00
|
|
|
if entry.file_type().map_or(true, |ft| !ft.is_symlink()) {
|
2017-10-12 08:01:51 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out unwanted extensions.
|
|
|
|
if let Some(ref filter_ext) = config.extension {
|
2017-10-11 23:08:41 +02:00
|
|
|
let entry_ext = entry_path.extension().map(
|
|
|
|
|e| e.to_string_lossy().to_lowercase(),
|
|
|
|
);
|
2017-10-10 08:01:17 +02:00
|
|
|
if entry_ext.map_or(true, |ext| ext != *filter_ext) {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 08:01:51 +02:00
|
|
|
let search_str_o = if config.search_full_path {
|
2017-10-22 11:47:05 +02:00
|
|
|
match fshelper::path_absolute_form(entry_path) {
|
2017-10-18 20:04:34 +02:00
|
|
|
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
|
|
|
Err(_) => error("Error: unable to get full path."),
|
|
|
|
}
|
2017-10-12 08:01:51 +02:00
|
|
|
} else {
|
2017-10-11 23:08:41 +02:00
|
|
|
entry_path.file_name().map(|f| f.to_string_lossy())
|
2017-10-12 08:01:51 +02:00
|
|
|
};
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
if let Some(search_str) = search_str_o {
|
2017-10-18 20:04:34 +02:00
|
|
|
if pattern.is_match(&*search_str) {
|
2017-10-11 23:08:41 +02:00
|
|
|
// TODO: take care of the unwrap call
|
2017-10-18 20:04:34 +02:00
|
|
|
tx_thread.send(entry_path.to_owned()).unwrap()
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ignore::WalkState::Continue
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
// Drop the initial sender. If we don't do this, the receiver will block even
|
|
|
|
// if all threads have finished, since there is still one sender around.
|
|
|
|
drop(tx);
|
|
|
|
|
|
|
|
// Wait for the receiver thread to print out all results.
|
|
|
|
receiver_thread.join().unwrap();
|
|
|
|
}
|