dirsize/
scanning.rs

1use crate::structs::Dir;
2use log::{debug, warn};
3use rayon::prelude::*;
4use std::fs;
5use std::io::ErrorKind;
6use std::path::{Path, PathBuf};
7use std::time::{Duration, Instant};
8
9/// Scans a directory recursively and finds all files contained within the directory and makes a directory tree (Dir)
10///
11/// Args:
12/// - path_to_dir - The path to the directory
13///
14/// Examples:
15///
16/// ```
17/// use std::path::Path;
18///
19/// let root = Path::new(r".");
20/// let result = dirsize::scanning::make_dir_tree(root.to_path_buf());
21///
22/// println!("{}", result.display_default());
23/// for f in result.contents.unwrap().iter() {
24///     println!("{}", f.display_default())
25/// }
26/// ```
27pub fn make_dir_tree(path_to_dir: PathBuf) -> Dir {
28    let mut contents = Vec::new();
29    let r_dir = match fs::read_dir(&path_to_dir) {
30        Ok(dir) => dir,
31        Err(err) => {
32            warn!(
33                "Error occured when trying to read {} error: {}",
34                path_to_dir.display(),
35                err
36            );
37            return Dir::new(0, path_to_dir, None, false);
38        }
39    };
40
41    for entry in r_dir {
42        let entry = entry.unwrap();
43        let path = entry.path();
44
45        if path.is_dir() {
46            debug!("{} is a directory", path.display());
47            contents.push(make_dir_tree(path));
48        } else {
49            let size = match fs::metadata(&path) {
50                Ok(file) => file.len(),
51                Err(err) => match err.kind() {
52                    ErrorKind::PermissionDenied => {
53                        warn!(
54                            "Permission denied when accesing file/directory: {}",
55                            path.display()
56                        );
57                        continue;
58                    }
59                    _ => {
60                        warn!(
61                            "Error occured when trying to read {} error: {}",
62                            path.display(),
63                            err
64                        );
65                        continue;
66                    }
67                },
68            };
69            debug!("{} is a file with size: {} bytes", path.display(), size);
70            let is_file = path.is_file();
71            contents.push(Dir::new(size, path, None, is_file));
72        }
73    }
74    let sizes: Vec<u64> = contents.iter().map(|x: &Dir| x.size).collect();
75    let dir = Dir::new(sizes.iter().sum(), path_to_dir, Some(contents), false);
76    dir
77}
78
79pub fn make_dir_tree_parallel(path_to_dir: PathBuf) -> Dir {
80    let r_dir = match fs::read_dir(&path_to_dir) {
81        Ok(dir) => dir,
82        Err(err) => {
83            warn!(
84                "Error occured when trying to read {} error: {}",
85                path_to_dir.display(),
86                err
87            );
88            return Dir::new(0, path_to_dir, None, false);
89        }
90    };
91    let contents = r_dir
92        .into_iter()
93        .par_bridge()
94        .filter_map(|entry| {
95            let entry = entry.unwrap();
96            let path = entry.path();
97
98            if path.is_dir() {
99                debug!("{} is a directory", path.display());
100                Some(make_dir_tree(path))
101            } else {
102                let size = match fs::metadata(&path) {
103                    Ok(file) => file.len(),
104                    Err(err) => match err.kind() {
105                        ErrorKind::PermissionDenied => {
106                            warn!(
107                                "Permission denied when accesing file/directory: {}",
108                                path.display()
109                            );
110                            return None;
111                        }
112                        _ => {
113                            warn!(
114                                "Error occured when trying to read {} error: {}",
115                                path.display(),
116                                err
117                            );
118                            return None;
119                        }
120                    },
121                };
122                debug!("{} is a file with size: {} bytes", path.display(), size);
123                let is_file = path.is_file();
124                Some(Dir::new(size, path, None, is_file))
125            }
126        })
127        .collect::<Vec<Dir>>();
128
129    let sizes: Vec<u64> = contents.iter().map(|x: &Dir| x.size).collect();
130    let dir = Dir::new(sizes.iter().sum(), path_to_dir, Some(contents), false);
131    dir
132}
133
134fn _benchmark_make_dir_tree(func: fn(PathBuf) -> Dir, n: i32) -> f32 {
135    let mut times: Vec<Duration> = vec![];
136    for _i in 0..n {
137        let start = Instant::now();
138        let root = Path::new(r".");
139        let _result = func(root.to_path_buf());
140        let end = Instant::now();
141        let time = end - start;
142        times.push(time)
143    }
144    let total_time = times
145        .iter()
146        .fold(Duration::default(), |acc, &x| acc + x)
147        .as_secs_f32();
148    let average_time: f32 = total_time / times.len() as f32;
149
150    println!(
151        "function ran {} times and took an average of {} seconds, total: {} seconds",
152        n, average_time, total_time
153    );
154    average_time
155}
156
157#[cfg(test)]
158mod bench {
159    use crate::scanning::{_benchmark_make_dir_tree, make_dir_tree, make_dir_tree_parallel};
160
161    #[test]
162    #[ignore]
163    fn benchmark_make_dir_tree() {
164        _benchmark_make_dir_tree(make_dir_tree, 10);
165    }
166
167    #[test]
168    #[ignore]
169    fn benchmark_make_dir_tree_parallel() {
170        _benchmark_make_dir_tree(make_dir_tree_parallel, 10);
171    }
172}
173
174#[cfg(test)]
175mod test {
176    use crate::scanning::{make_dir_tree, make_dir_tree_parallel};
177    use std::path::Path;
178
179    #[test]
180    fn test_make_dir_tree() {
181        let root = Path::new(r".");
182        let tree = make_dir_tree(root.to_path_buf());
183        for sub_dir in tree.contents.unwrap().iter() {
184            println!("{}", sub_dir.display_default())
185        }
186    }
187
188    #[test]
189    fn test_make_dir_tree_parallel() {
190        let root = Path::new(r".");
191        let tree = make_dir_tree_parallel(root.to_path_buf());
192        for sub_dir in tree.contents.unwrap().iter() {
193            println!("{}", sub_dir.display_default())
194        }
195    }
196}