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}