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/// };
33/// let data_tree: DataTree<OsStringDisplay, Bytes> = builder.into();
34/// ```
35#[derive(Debug)]
36pub struct FsTreeBuilder<Size, SizeGetter, Report>
37where
38    Report: Reporter<Size> + Sync,
39    Size: size::Size + Send + Sync,
40    SizeGetter: GetSize<Size = Size> + Sync,
41{
42    /// Root of the directory tree.
43    pub root: PathBuf,
44    /// Returns size of an item.
45    pub size_getter: SizeGetter,
46    /// Reports progress to external system.
47    pub reporter: Report,
48}
49
50impl<Size, SizeGetter, Report> From<FsTreeBuilder<Size, SizeGetter, Report>>
51    for DataTree<OsStringDisplay, Size>
52where
53    Report: Reporter<Size> + Sync,
54    Size: size::Size + Send + Sync,
55    SizeGetter: GetSize<Size = Size> + Sync,
56{
57    /// Create a [`DataTree`] from an [`FsTreeBuilder`].
58    fn from(builder: FsTreeBuilder<Size, SizeGetter, Report>) -> Self {
59        let FsTreeBuilder {
60            root,
61            size_getter,
62            reporter,
63        } = builder;
64
65        TreeBuilder::<PathBuf, OsStringDisplay, Size, _, _> {
66            name: OsStringDisplay::os_string_from(&root),
67
68            path: root,
69
70            get_info: |path| {
71                let stats = match symlink_metadata(path) {
72                    Err(error) => {
73                        reporter.report(Event::EncounterError(ErrorReport {
74                            operation: SymlinkMetadata,
75                            path,
76                            error,
77                        }));
78                        return Info {
79                            size: Size::default(),
80                            children: Vec::new(),
81                        };
82                    }
83                    Ok(stats) => stats,
84                };
85
86                let children: Vec<_> = if stats.file_type().is_dir() {
87                    match read_dir(path) {
88                        Err(error) => {
89                            reporter.report(Event::EncounterError(ErrorReport {
90                                operation: ReadDirectory,
91                                path,
92                                error,
93                            }));
94                            return Info::default();
95                        }
96                        Ok(entries) => entries,
97                    }
98                    .filter_map(|entry| match entry {
99                        Err(error) => {
100                            reporter.report(Event::EncounterError(ErrorReport {
101                                operation: AccessEntry,
102                                path,
103                                error,
104                            }));
105                            None
106                        }
107                        Ok(entry) => entry.file_name().pipe(OsStringDisplay::from).pipe(Some),
108                    })
109                    .collect()
110                } else {
111                    Vec::new()
112                };
113
114                let size = size_getter.get_size(&stats);
115                reporter.report(Event::ReceiveData(size));
116
117                Info { size, children }
118            },
119
120            join_path: |prefix, name| prefix.join(&name.0),
121        }
122        .into()
123    }
124}