1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
pub mod file_operations;
pub mod dir_operations;
pub mod path_manipulation;

use std::collections::HashSet;
use std::fs;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::fs::OpenOptions;

/// The `DirectorySearcher` struct in Rust provides functionality to search directories for specific
/// files while maintaining a blacklist of paths.
/// 
/// Properties:
/// 
/// * `blacklist`: The `blacklist` property in the `DirectorySearcher` struct is a HashSet that stores
/// strings representing paths that should be excluded from the search. This blacklist is used to skip
/// certain directories during the search process.
/// * `blacklist_file_write`: The `blacklist_file_write` property in the `DirectorySearcher` struct is
/// of type `File`. It is used to store a file handle for writing to the blacklist file. This file
/// handle is used to append entries to the blacklist file when needed, such as when a path is added to
pub struct DirectorySearcher{
    blacklist: HashSet<String>,
    blacklist_file_write: File,
}

impl DirectorySearcher {
    /// The function creates a new instance of DirectorySearcher with a blacklist HashSet and a file for
    /// writing blacklist entries.
    /// 
    /// Returns:
    /// 
    /// A new instance of the `DirectorySearcher` struct is being returned.
    pub fn new() -> Self {
        let file= match OpenOptions::new().append(true).open("src\\blacklist.txt") {
            Ok(file) => file,
            Err(_e) => file_operations::new_file_return_append()
        };
        DirectorySearcher{
            blacklist: HashSet::new(),
            blacklist_file_write: file,
        }
    }
    /// The function `update_blacklist` reads a file named "blacklist.txt" and updates a blacklist
    /// collection with its contents.
    pub fn update_blacklist(&mut self) {
        let file: File = match OpenOptions::new().read(true).open("src\\blacklist.txt") {
            Ok(file) => file,
            Err(_e) => file_operations::new_file_return()
        };
        let reader: BufReader<File> = BufReader::new(file);
        self.blacklist.clear();
        for line in reader.lines() {
            if let Ok(line) = line {
                self.blacklist.insert(line);
            }
        }
    }

    /// The function searches for a specific string in file paths within a given root directory while
    /// avoiding blacklisted paths.
    /// 
    /// Arguments:
    /// 
    /// * `root_path`: The `root_path` parameter is a reference to a string that represents the root
    /// path from which the search operation will start. This path is the starting point for searching
    /// for files or directories containing the specified search term.
    /// * `be_searched_`: The `be_searched_` parameter in the `search` function is a reference to a
    /// string that represents the keyword or phrase that is being searched for within the paths. This
    /// parameter is expected to be in its original form without any modifications.
    /// 
    /// Returns:
    /// 
    /// A vector of strings containing the paths that match the search criteria.
    pub(crate) fn search(&mut self, root_path: &str, be_searched_: &str) -> Vec<String> {
        if self.blacklist.contains(&root_path.to_string()) {
            return Vec::new();
        }
        let be_searched: String = be_searched_.to_lowercase();
        let paths: Vec<String> = self.get_paths(&root_path.trim());
        let mut matchers: Vec<String> = Vec::new();
        let mut dirs: Vec<String> = Vec::new();
        for i in paths.iter() {
            if dir_operations::is_dir(i) {
                dirs.push(i.to_string());
            }
            let breakers: Vec<&str> = i.split("\\").collect();
            if breakers[breakers.len() - 1].contains(&be_searched) {
                matchers.push(i.to_string());
            }
        }
        dirs.into_iter().for_each(|dir| matchers.append(&mut self.search(&dir, &be_searched)));
        matchers
    }

    /// The function `get_paths` reads directory contents and returns a vector of file paths, handling
    /// errors by writing to a blacklist file if necessary.
    /// 
    /// Arguments:
    /// 
    /// * `path`: The `path` parameter in the `get_paths` function is a reference to a string (`&str`)
    /// which represents the directory path from which you want to read the directory entries.
    /// 
    /// Returns:
    /// 
    /// The `get_paths` function returns a `Vec<String>`. If the `fs::read_dir(path)` call is
    /// successful, it populates the vector with paths obtained from the directory entries. If there is
    /// an error, it writes the path to a blacklist file and returns an empty vector.
    fn get_paths(&mut self, path: &str) -> Vec<String> {
        if let Ok(paths) = fs::read_dir(path) {
            let mut paths_: Vec<String> = Vec::new();
            for path in paths {
                paths_.push(path.unwrap().path().display().to_string());
            }
            return paths_; 
        } else {
            let _ = self.blacklist_file_write.write(path.as_bytes()).unwrap();
            let _ = self.blacklist_file_write.write(b"\n").unwrap();
            return Vec::new();
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn is_working() {
        let mut searcher = DirectorySearcher::new();
        searcher.search("F:", "java");
    }
}