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.
|
|
|
|
|
2019-01-05 18:17:22 +01:00
|
|
|
use crate::exec;
|
|
|
|
use crate::exit_codes::ExitCode;
|
|
|
|
use crate::fshelper;
|
2019-09-15 14:42:48 +02:00
|
|
|
use crate::internal::{opts::FdOptions, osstr_to_bytes, MAX_BUFFER_LENGTH};
|
2019-01-05 18:17:22 +01:00
|
|
|
use crate::output;
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2019-09-15 14:42:48 +02:00
|
|
|
use std::borrow::Cow;
|
2018-03-26 00:15:01 +02:00
|
|
|
use std::error::Error;
|
2019-09-15 14:42:48 +02:00
|
|
|
use std::ffi::OsStr;
|
2019-01-01 22:52:08 +01:00
|
|
|
use std::io;
|
2017-12-06 23:52:23 +01:00
|
|
|
use std::path::PathBuf;
|
2018-04-13 22:46:17 +02:00
|
|
|
use std::process;
|
2017-11-22 23:05:09 +01:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2019-01-26 01:01:01 +01:00
|
|
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
2018-04-13 22:46:17 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2017-10-10 08:01:17 +02:00
|
|
|
use std::thread;
|
|
|
|
use std::time;
|
|
|
|
|
2017-10-22 23:00:19 +02:00
|
|
|
use ignore::overrides::OverrideBuilder;
|
2018-04-13 22:46:17 +02:00
|
|
|
use ignore::{self, WalkBuilder};
|
2019-09-15 14:42:48 +02:00
|
|
|
use regex::bytes::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,
|
|
|
|
}
|
|
|
|
|
2018-09-30 15:01:23 +02:00
|
|
|
/// The Worker threads can result in a valid entry having PathBuf or an error.
|
|
|
|
pub enum WorkerResult {
|
|
|
|
Entry(PathBuf),
|
2018-09-30 22:56:32 +02:00
|
|
|
Error(ignore::Error),
|
2018-09-30 15:01:23 +02:00
|
|
|
}
|
|
|
|
|
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.
|
2019-09-13 22:26:27 +02:00
|
|
|
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) -> ExitCode {
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut path_iter = path_vec.iter();
|
2018-01-01 12:16:43 +01:00
|
|
|
let first_path_buf = path_iter
|
|
|
|
.next()
|
|
|
|
.expect("Error: Path vector can not be empty");
|
2017-10-10 08:01:17 +02:00
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
2017-10-22 23:00:19 +02:00
|
|
|
|
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() {
|
2018-10-27 16:30:29 +02:00
|
|
|
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
|
2017-10-22 23:00:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let overrides = override_builder.build().unwrap_or_else(|_| {
|
2018-10-03 15:53:59 +02:00
|
|
|
print_error_and_exit!("Mismatch in exclude patterns");
|
2017-10-22 23:00:19 +02:00
|
|
|
});
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
|
|
|
walker
|
2017-10-12 08:01:51 +02:00
|
|
|
.hidden(config.ignore_hidden)
|
2018-10-27 18:11:50 +02:00
|
|
|
.ignore(config.read_fdignore)
|
2018-02-21 21:41:52 +01:00
|
|
|
.parents(config.read_fdignore || config.read_vcsignore)
|
|
|
|
.git_ignore(config.read_vcsignore)
|
|
|
|
.git_global(config.read_vcsignore)
|
|
|
|
.git_exclude(config.read_vcsignore)
|
2017-10-22 23:00:19 +02:00
|
|
|
.overrides(overrides)
|
2017-10-12 08:01:51 +02:00
|
|
|
.follow_links(config.follow_links)
|
2017-12-06 23:52:23 +01:00
|
|
|
.max_depth(config.max_depth);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2018-02-21 21:41:52 +01:00
|
|
|
if config.read_fdignore {
|
|
|
|
walker.add_custom_ignore_filename(".fdignore");
|
|
|
|
}
|
|
|
|
|
2018-03-26 00:15:01 +02:00
|
|
|
for ignore_file in &config.ignore_files {
|
|
|
|
let result = walker.add_ignore(ignore_file);
|
2019-01-26 01:22:06 +01:00
|
|
|
match result {
|
|
|
|
Some(ignore::Error::Partial(_)) => (),
|
|
|
|
Some(err) => {
|
|
|
|
print_error!(
|
|
|
|
"{}",
|
|
|
|
format!(
|
|
|
|
"Malformed pattern in custom ignore file '{}': {}.",
|
|
|
|
ignore_file.to_string_lossy(),
|
|
|
|
err.description()
|
|
|
|
)
|
|
|
|
);
|
2018-03-26 00:15:01 +02:00
|
|
|
}
|
2019-01-26 01:22:06 +01:00
|
|
|
None => (),
|
2018-03-26 00:15:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
for path_entry in path_iter {
|
|
|
|
walker.add(path_entry.as_path());
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:16:53 +01:00
|
|
|
let parallel_walker = walker.threads(config.threads).build_parallel();
|
2017-11-22 23:05:09 +01:00
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
2018-01-24 19:44:17 +01:00
|
|
|
if config.ls_colors.is_some() && config.command.is_none() {
|
2019-01-26 01:12:55 +01:00
|
|
|
let wq = Arc::clone(&wants_to_quit);
|
2018-01-01 12:16:43 +01:00
|
|
|
ctrlc::set_handler(move || {
|
2019-09-15 13:05:53 +02:00
|
|
|
if wq.load(Ordering::Relaxed) {
|
|
|
|
// Ctrl-C has been pressed twice, exit NOW
|
|
|
|
process::exit(ExitCode::KilledBySigint.into());
|
|
|
|
} else {
|
|
|
|
wq.store(true, Ordering::Relaxed);
|
|
|
|
}
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
|
|
|
.unwrap();
|
2017-11-22 23:05:09 +01:00
|
|
|
}
|
2017-12-06 23:52:23 +01:00
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
// Spawn the thread that receives all results through the channel.
|
2019-01-26 01:12:55 +01:00
|
|
|
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
// Spawn the sender threads.
|
2019-01-26 01:12:55 +01:00
|
|
|
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
// Wait for the receiver thread to print out all results.
|
2019-09-13 22:26:27 +02:00
|
|
|
let exit_code = receiver_thread.join().unwrap();
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
if wants_to_quit.load(Ordering::Relaxed) {
|
|
|
|
process::exit(ExitCode::KilledBySigint.into());
|
|
|
|
}
|
2019-09-13 22:26:27 +02:00
|
|
|
|
|
|
|
exit_code
|
2019-01-26 01:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn spawn_receiver(
|
2019-01-26 01:12:55 +01:00
|
|
|
config: &Arc<FdOptions>,
|
|
|
|
wants_to_quit: &Arc<AtomicBool>,
|
2019-01-26 01:01:01 +01:00
|
|
|
rx: Receiver<WorkerResult>,
|
2019-09-13 22:26:27 +02:00
|
|
|
) -> thread::JoinHandle<ExitCode> {
|
2019-01-26 01:12:55 +01:00
|
|
|
let config = Arc::clone(config);
|
|
|
|
let wants_to_quit = Arc::clone(wants_to_quit);
|
|
|
|
|
|
|
|
let show_filesystem_errors = config.show_filesystem_errors;
|
|
|
|
let threads = config.threads;
|
2019-01-26 01:01:01 +01:00
|
|
|
|
|
|
|
thread::spawn(move || {
|
2017-10-14 18:04:11 +02:00
|
|
|
// This will be set to `Some` if the `--exec` argument was supplied.
|
2019-01-26 01:12:55 +01:00
|
|
|
if let Some(ref cmd) = config.command {
|
2018-11-12 18:43:40 +01:00
|
|
|
if cmd.in_batch_mode() {
|
2019-09-13 22:26:27 +02:00
|
|
|
exec::batch(rx, cmd, show_filesystem_errors)
|
2018-11-11 18:00:01 +01:00
|
|
|
} else {
|
|
|
|
let shared_rx = Arc::new(Mutex::new(rx));
|
|
|
|
|
|
|
|
let out_perm = Arc::new(Mutex::new(()));
|
|
|
|
|
|
|
|
// Each spawned job will store it's thread handle in here.
|
|
|
|
let mut handles = Vec::with_capacity(threads);
|
|
|
|
for _ in 0..threads {
|
|
|
|
let rx = Arc::clone(&shared_rx);
|
2019-01-29 23:09:45 +01:00
|
|
|
let cmd = Arc::clone(cmd);
|
2018-11-11 18:00:01 +01:00
|
|
|
let out_perm = Arc::clone(&out_perm);
|
|
|
|
|
|
|
|
// Spawn a job thread that will listen for and execute inputs.
|
|
|
|
let handle =
|
|
|
|
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
|
|
|
|
|
|
|
|
// Push the handle of the spawned thread into the vector for later joining.
|
|
|
|
handles.push(handle);
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
// Wait for all threads to exit before exiting the program.
|
|
|
|
for h in handles {
|
|
|
|
h.join().unwrap();
|
|
|
|
}
|
2019-09-13 22:26:27 +02:00
|
|
|
|
|
|
|
ExitCode::Success
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|
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.
|
2019-01-26 01:12:55 +01:00
|
|
|
let max_buffer_time = config
|
2018-01-01 12:16:43 +01:00
|
|
|
.max_buffer_time
|
|
|
|
.unwrap_or_else(|| time::Duration::from_millis(100));
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2019-01-01 22:52:08 +01:00
|
|
|
let stdout = io::stdout();
|
|
|
|
let mut stdout = stdout.lock();
|
|
|
|
|
2018-09-30 15:01:23 +02:00
|
|
|
for worker_result in rx {
|
|
|
|
match worker_result {
|
|
|
|
WorkerResult::Entry(value) => {
|
|
|
|
match mode {
|
|
|
|
ReceiverMode::Buffering => {
|
|
|
|
buffer.push(value);
|
|
|
|
|
|
|
|
// Have we reached the maximum buffer size or maximum buffering time?
|
|
|
|
if buffer.len() > MAX_BUFFER_LENGTH
|
|
|
|
|| time::Instant::now() - start > max_buffer_time
|
|
|
|
{
|
|
|
|
// Flush the buffer
|
|
|
|
for v in &buffer {
|
2019-01-01 22:52:08 +01:00
|
|
|
output::print_entry(
|
|
|
|
&mut stdout,
|
|
|
|
v,
|
2019-01-26 01:12:55 +01:00
|
|
|
&config,
|
|
|
|
&wants_to_quit,
|
2019-01-01 22:52:08 +01:00
|
|
|
);
|
2018-09-30 15:01:23 +02:00
|
|
|
}
|
|
|
|
buffer.clear();
|
|
|
|
|
|
|
|
// Start streaming
|
|
|
|
mode = ReceiverMode::Streaming;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ReceiverMode::Streaming => {
|
2019-01-26 01:12:55 +01:00
|
|
|
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-30 15:01:23 +02:00
|
|
|
WorkerResult::Error(err) => {
|
2018-10-22 14:20:08 +02:00
|
|
|
if show_filesystem_errors {
|
|
|
|
print_error!("{}", err);
|
|
|
|
}
|
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 {
|
2019-01-26 01:12:55 +01:00
|
|
|
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
2019-09-13 22:26:27 +02:00
|
|
|
|
|
|
|
ExitCode::Success
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
2019-01-26 01:01:01 +01:00
|
|
|
})
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
|
2019-01-26 01:01:01 +01:00
|
|
|
fn spawn_senders(
|
2019-01-26 01:12:55 +01:00
|
|
|
config: &Arc<FdOptions>,
|
|
|
|
wants_to_quit: &Arc<AtomicBool>,
|
2019-01-26 01:01:01 +01:00
|
|
|
pattern: Arc<Regex>,
|
|
|
|
parallel_walker: ignore::WalkParallel,
|
|
|
|
tx: Sender<WorkerResult>,
|
|
|
|
) {
|
2017-12-06 23:52:23 +01:00
|
|
|
parallel_walker.run(|| {
|
2019-01-26 01:12:55 +01:00
|
|
|
let config = Arc::clone(config);
|
2017-10-10 08:01:17 +02:00
|
|
|
let pattern = Arc::clone(&pattern);
|
|
|
|
let tx_thread = tx.clone();
|
2019-01-26 01:12:55 +01:00
|
|
|
let wants_to_quit = Arc::clone(wants_to_quit);
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
Box::new(move |entry_o| {
|
2018-01-03 09:26:11 +01:00
|
|
|
if wants_to_quit.load(Ordering::Relaxed) {
|
|
|
|
return ignore::WalkState::Quit;
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
let entry = match entry_o {
|
|
|
|
Ok(e) => e,
|
2018-09-30 15:01:23 +02:00
|
|
|
Err(err) => {
|
|
|
|
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
};
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
if entry.depth() == 0 {
|
2017-10-11 23:08:41 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
|
2019-04-26 03:17:42 +02:00
|
|
|
// Check the name first, since it doesn't require metadata
|
|
|
|
let entry_path = entry.path();
|
|
|
|
|
2019-09-15 14:42:48 +02:00
|
|
|
let search_str: Cow<OsStr> = if config.search_full_path {
|
2019-04-26 03:17:42 +02:00
|
|
|
match fshelper::path_absolute_form(entry_path) {
|
2019-09-15 14:42:48 +02:00
|
|
|
Ok(path_abs_buf) => Cow::Owned(path_abs_buf.as_os_str().to_os_string()),
|
2019-04-26 03:17:42 +02:00
|
|
|
Err(_) => {
|
|
|
|
print_error_and_exit!("Unable to retrieve absolute path.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-09-15 14:42:48 +02:00
|
|
|
match entry_path.file_name() {
|
|
|
|
Some(filename) => Cow::Borrowed(filename),
|
|
|
|
None => unreachable!(
|
|
|
|
"Encountered file system entry without a file name. This should only \
|
|
|
|
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
|
|
|
|
appear in a file system traversal."
|
|
|
|
),
|
|
|
|
}
|
2019-04-26 03:17:42 +02:00
|
|
|
};
|
|
|
|
|
2019-09-15 14:42:48 +02:00
|
|
|
if !pattern.is_match(&osstr_to_bytes(search_str.as_ref())) {
|
|
|
|
return ignore::WalkState::Continue;
|
2019-04-26 03:17:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out unwanted extensions.
|
|
|
|
if let Some(ref exts_regex) = config.extensions {
|
2019-09-15 14:42:48 +02:00
|
|
|
if let Some(path_str) = entry_path.file_name() {
|
|
|
|
if !exts_regex.is_match(&osstr_to_bytes(path_str)) {
|
2019-04-26 03:17:42 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-10 08:01:17 +02:00
|
|
|
// Filter out unwanted file types.
|
2018-02-25 11:11:14 +01:00
|
|
|
if let Some(ref file_types) = config.file_types {
|
|
|
|
if let Some(ref entry_type) = entry.file_type() {
|
2018-08-19 16:27:23 +02:00
|
|
|
if (!file_types.files && entry_type.is_file())
|
|
|
|
|| (!file_types.directories && entry_type.is_dir())
|
|
|
|
|| (!file_types.symlinks && entry_type.is_symlink())
|
2018-10-19 22:05:15 +02:00
|
|
|
|| (file_types.executables_only
|
|
|
|
&& !entry
|
|
|
|
.metadata()
|
|
|
|
.map(|m| fshelper::is_executable(&m))
|
|
|
|
.unwrap_or(false))
|
2018-08-19 17:05:04 +02:00
|
|
|
|| (file_types.empty_only && !fshelper::is_empty(&entry))
|
2018-02-25 11:11:14 +01:00
|
|
|
{
|
|
|
|
return ignore::WalkState::Continue;
|
2018-05-14 18:39:47 +02:00
|
|
|
} else if !(entry_type.is_file()
|
|
|
|
|| entry_type.is_dir()
|
2018-03-15 20:45:26 +01:00
|
|
|
|| entry_type.is_symlink())
|
|
|
|
{
|
|
|
|
// This is probably a block device, char device, fifo or socket. Skip it.
|
|
|
|
return ignore::WalkState::Continue;
|
2018-02-25 11:11:14 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2017-10-10 08:01:17 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 01:38:10 +02:00
|
|
|
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
2019-01-26 02:13:16 +01:00
|
|
|
if !config.size_constraints.is_empty() {
|
2018-04-25 08:25:02 +02:00
|
|
|
if entry_path.is_file() {
|
|
|
|
if let Ok(metadata) = entry_path.metadata() {
|
|
|
|
let file_size = metadata.len();
|
|
|
|
if config
|
|
|
|
.size_constraints
|
|
|
|
.iter()
|
|
|
|
.any(|sc| !sc.is_within(file_size))
|
|
|
|
{
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
|
|
|
} else {
|
2018-04-23 01:38:10 +02:00
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2018-04-25 08:25:02 +02:00
|
|
|
} else {
|
|
|
|
return ignore::WalkState::Continue;
|
2018-04-23 01:38:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 13:47:42 +02:00
|
|
|
// Filter out unwanted modification times
|
2018-10-10 12:13:19 +02:00
|
|
|
if !config.time_constraints.is_empty() {
|
|
|
|
let mut matched = false;
|
|
|
|
if entry_path.is_file() {
|
|
|
|
if let Ok(metadata) = entry_path.metadata() {
|
|
|
|
if let Ok(modified) = metadata.modified() {
|
|
|
|
matched = config
|
|
|
|
.time_constraints
|
|
|
|
.iter()
|
|
|
|
.all(|tf| tf.applies_to(&modified));
|
|
|
|
}
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
}
|
2018-10-10 12:13:19 +02:00
|
|
|
if !matched {
|
|
|
|
return ignore::WalkState::Continue;
|
|
|
|
}
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
|
2019-04-26 03:17:42 +02:00
|
|
|
// TODO: take care of the unwrap call
|
|
|
|
tx_thread
|
|
|
|
.send(WorkerResult::Entry(entry_path.to_owned()))
|
|
|
|
.unwrap();
|
2017-10-10 08:01:17 +02:00
|
|
|
|
|
|
|
ignore::WalkState::Continue
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|