ferris_files/config.rs
1use crate::args::Args;
2use crate::get_fd_limit;
3use std::collections::HashSet;
4use std::env;
5use std::error::Error;
6use std::fs::File;
7use std::io::{BufRead, BufReader};
8use std::path::PathBuf;
9
10/// Configuration structure containing runtime settings.
11///
12/// # Fields
13///
14/// * `num_threads` - Number of threads to use in parallel processing
15/// * `num_entries` - Number of entries to output at program completion
16/// * `batch_size` - Size of batches for processing file metadata
17/// * `root_path` - Base directory path to recursively find and size files
18/// * `skip_dirs` - Set of directory names to exclude from the search
19/// * `max_open_files` - Maximum number of open file handles used by this program
20/// * `verbose` - Bool to determine if errors collected during runtime will be printed
21///
22#[derive(Clone)]
23pub struct Config {
24 pub num_threads: usize,
25 pub num_entries: usize,
26 pub batch_size: usize,
27 pub root_path: PathBuf,
28 pub skip_dirs: HashSet<String>,
29 pub max_open_files: usize,
30 pub verbose: bool,
31}
32
33impl Config {
34 /// Builds a new Config instance from provided command line arguments.
35 ///
36 /// # Parameters
37 ///
38 /// * `args` - Reference to Args structure containing command line arguments
39 ///
40 /// # Returns
41 ///
42 /// * `Result<Config, Box<dyn Error>>` - New Config instance or error if construction fails
43 ///
44 /// # Details
45 ///
46 /// This function performs the following setup:
47 /// 1. Configures parallel processing based on available CPU cores
48 /// 2. Calls a library function to determine platform specific cap on open file descriptors
49 /// 3. Sets number of entries to output equal to provided command line arg or default of 10
50 /// 4. Sets batch size to match command line arg if specified or else default to 1000
51 /// 5. Sets verbose bool to match command line arg
52 /// 6. Sets up the root directory path for operations
53 /// 7. Loads directory exclusion rules if file containing dirs was supplied
54 ///
55 /// # Errors
56 ///
57 /// Returns an error if:
58 /// * Current directory cannot be determined when no target directory is specified
59 /// * Exclusion file cannot be opened or read
60 /// * Thread pool configuration fails (logged as error but doesn't halt execution)
61 ///
62 pub fn build(args: &Args) -> Result<Config, Box<dyn Error>> {
63 let num_threads = std::thread::available_parallelism()
64 .map(|n| n.get())
65 .unwrap_or(1);
66
67 println!("Preparing to scan using {} threads", num_threads);
68
69 let max_open_files = get_fd_limit();
70 println!("Limiting open file handles to {}", max_open_files);
71
72 let num_entries = args.num_entries;
73 let batch_size = args.batch_size;
74 let verbose = args.verbose;
75
76 let root_path = if let Some(target_dir) = &args.target_dir {
77 PathBuf::from(target_dir)
78 } else {
79 env::current_dir()?
80 };
81
82 let mut skip_dirs: HashSet<String> = HashSet::new();
83 if let Some(exclusion_file) = &args.exclusion_file {
84 let file = File::open(exclusion_file)
85 .expect("A path to an excluded directories file was provided but the file could not be read");
86
87 let reader = BufReader::new(file);
88 reader.lines().for_each(|line| match line {
89 Ok(dir) => {
90 skip_dirs.insert(dir);
91 }
92 Err(e) => log::error!("Error reading line: {}", e),
93 });
94 }
95
96 Ok(Config {
97 num_threads,
98 num_entries,
99 batch_size,
100 root_path,
101 skip_dirs,
102 max_open_files,
103 verbose
104 })
105 }
106}