parallel_disk_usage/
fs_tree_builder.rs

1use super::{
2    data_tree::DataTree,
3    get_size::GetSize,
4    os_string_display::OsStringDisplay,
5    reporter::{error_report::Operation::*, ErrorReport, Event, Reporter},
6    size,
7    tree_builder::{Info, TreeBuilder},
8};
9use pipe_trait::Pipe;
10use std::{
11    fs::{read_dir, symlink_metadata},
12    path::PathBuf,
13};
14
15/// Build a [`DataTree`] from a directory tree using [`From`] or [`Into`].
16///
17/// **Example:**
18///
19/// ```no_run
20/// # use parallel_disk_usage::fs_tree_builder::FsTreeBuilder;
21/// use parallel_disk_usage::{
22///     data_tree::DataTree,
23///     get_size::GetApparentSize,
24///     os_string_display::OsStringDisplay,
25///     reporter::{ErrorOnlyReporter, ErrorReport},
26///     size::Bytes,
27/// };
28/// let builder = FsTreeBuilder {
29///     root: std::env::current_dir().unwrap(),
30///     size_getter: GetApparentSize,
31///     reporter: ErrorOnlyReporter::new(ErrorReport::SILENT),
32///     max_depth: 10,
33/// };
34/// let data_tree: DataTree<OsStringDisplay, Bytes> = builder.into();
35/// ```
36#[derive(Debug)]
37pub struct FsTreeBuilder<Size, SizeGetter, Report>
38where
39    Report: Reporter<Size> + Sync,
40    Size: size::Size + Send + Sync,
41    SizeGetter: GetSize<Size = Size> + Sync,
42{
43    /// Root of the directory tree.
44    pub root: PathBuf,
45    /// Returns size of an item.
46    pub size_getter: SizeGetter,
47    /// Reports progress to external system.
48    pub reporter: Report,
49    /// Deepest level of descendent display in the graph. The sizes beyond the max depth still count toward total.
50    pub max_depth: u64,
51}
52
53impl<Size, SizeGetter, Report> From<FsTreeBuilder<Size, SizeGetter, Report>>
54    for DataTree<OsStringDisplay, Size>
55where
56    Report: Reporter<Size> + Sync,
57    Size: size::Size + Send + Sync,
58    SizeGetter: GetSize<Size = Size> + Sync,
59{
60    /// Create a [`DataTree`] from an [`FsTreeBuilder`].
61    fn from(builder: FsTreeBuilder<Size, SizeGetter, Report>) -> Self {
62        let FsTreeBuilder {
63            root,
64            size_getter,
65            reporter,
66            max_depth,
67        } = builder;
68
69        TreeBuilder::<PathBuf, OsStringDisplay, Size, _, _> {
70            name: OsStringDisplay::os_string_from(&root),
71
72            path: root,
73
74            get_info: |path| {
75                let (is_dir, size) = match symlink_metadata(path) {
76                    Err(error) => {
77                        reporter.report(Event::EncounterError(ErrorReport {
78                            operation: SymlinkMetadata,
79                            path,
80                            error,
81                        }));
82                        return Info {
83                            size: Size::default(),
84                            children: Vec::new(),
85                        };
86                    }
87                    Ok(stats) => {
88                        // `stats` should be dropped ASAP to avoid piling up kernel memory usage
89                        let is_dir = stats.is_dir();
90                        let size = size_getter.get_size(&stats);
91                        reporter.report(Event::ReceiveData(size));
92                        (is_dir, size)
93                    }
94                };
95
96                let children: Vec<_> = if is_dir {
97                    match read_dir(path) {
98                        Err(error) => {
99                            reporter.report(Event::EncounterError(ErrorReport {
100                                operation: ReadDirectory,
101                                path,
102                                error,
103                            }));
104                            return Info::default();
105                        }
106                        Ok(entries) => entries,
107                    }
108                    .filter_map(|entry| match entry {
109                        Err(error) => {
110                            reporter.report(Event::EncounterError(ErrorReport {
111                                operation: AccessEntry,
112                                path,
113                                error,
114                            }));
115                            None
116                        }
117                        Ok(entry) => entry.file_name().pipe(OsStringDisplay::from).pipe(Some),
118                    })
119                    .collect()
120                } else {
121                    Vec::new()
122                };
123
124                Info { size, children }
125            },
126
127            join_path: |prefix, name| prefix.join(&name.0),
128
129            max_depth,
130        }
131        .into()
132    }
133}