1use crate::{
2 args::{Depth, Fraction},
3 data_tree::DataTree,
4 device::DeviceBoundary,
5 fs_tree_builder::FsTreeBuilder,
6 get_size::GetSize,
7 hardlink::{DeduplicateSharedSize, HardlinkIgnorant, RecordHardlinks},
8 json_data::{BinaryVersion, JsonData, JsonDataBody, JsonShared, JsonTree, SchemaVersion},
9 os_string_display::OsStringDisplay,
10 reporter::ParallelReporter,
11 runtime_error::RuntimeError,
12 size,
13 status_board::GLOBAL_STATUS_BOARD,
14 visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
15};
16use pipe_trait::Pipe;
17use serde::Serialize;
18use std::{io::stdout, iter::once, path::PathBuf};
19
20pub struct Sub<Size, SizeGetter, HardlinksHandler, Report>
22where
23 Report: ParallelReporter<Size> + Sync,
24 Size: size::Size + Into<u64> + Serialize + Send + Sync,
25 SizeGetter: GetSize<Size = Size> + Copy + Sync,
26 HardlinksHandler: RecordHardlinks<Size, Report> + HardlinkSubroutines<Size> + Sync,
27 JsonTree<Size>: Into<JsonDataBody>,
28{
29 pub files: Vec<PathBuf>,
31 pub json_output: Option<JsonOutputParam>,
33 pub bytes_format: Size::DisplayFormat,
35 pub direction: Direction,
37 pub bar_alignment: BarAlignment,
39 pub column_width_distribution: ColumnWidthDistribution,
41 pub max_depth: Depth,
43 pub size_getter: SizeGetter,
45 pub hardlinks_handler: HardlinksHandler,
47 pub device_boundary: DeviceBoundary,
49 pub reporter: Report,
51 pub min_ratio: Fraction,
53 pub no_sort: bool,
55}
56
57impl<Size, SizeGetter, HardlinksHandler, Report> Sub<Size, SizeGetter, HardlinksHandler, Report>
58where
59 Size: size::Size + Into<u64> + Serialize + Send + Sync,
60 Report: ParallelReporter<Size> + Sync,
61 SizeGetter: GetSize<Size = Size> + Copy + Sync,
62 HardlinksHandler: RecordHardlinks<Size, Report> + HardlinkSubroutines<Size> + Sync,
63 JsonTree<Size>: Into<JsonDataBody>,
64{
65 pub fn run(self) -> Result<(), RuntimeError> {
67 let Sub {
68 files,
69 json_output,
70 bytes_format,
71 direction,
72 bar_alignment,
73 column_width_distribution,
74 max_depth,
75 size_getter,
76 hardlinks_handler,
77 device_boundary,
78 reporter,
79 min_ratio,
80 no_sort,
81 } = self;
82
83 let max_depth = max_depth.get();
84
85 let mut iter = files
86 .into_iter()
87 .map(|root| -> DataTree<OsStringDisplay, Size> {
88 FsTreeBuilder {
89 reporter: &reporter,
90 root,
91 size_getter,
92 hardlinks_recorder: &hardlinks_handler,
93 device_boundary,
94 max_depth,
95 }
96 .into()
97 });
98
99 let data_tree = if let Some(data_tree) = iter.next() {
100 data_tree
101 } else {
102 return Sub {
103 files: vec![".".into()],
104 hardlinks_handler,
105 reporter,
106 ..self
107 }
108 .run();
109 };
110
111 let only_one_arg = iter.len() == 0; let data_tree = if only_one_arg {
113 data_tree
114 } else {
115 let children: Vec<_> = once(data_tree).chain(iter).collect();
116
117 let fake_root_name = OsStringDisplay::os_string_from("");
120
121 DataTree::dir(fake_root_name, Size::default(), children)
122 .into_par_retained(|_, depth| depth + 1 < max_depth)
123 };
124
125 if reporter.destroy().is_err() {
126 eprintln!("[warning] Failed to destroy the thread that reports progress");
127 }
128
129 let min_ratio: f32 = min_ratio.into();
130 let (data_tree, deduplication_record) = {
131 let mut data_tree = data_tree;
132 data_tree.par_cull_insignificant_data(min_ratio);
133 if !no_sort {
134 data_tree.par_sort_by(|left, right| left.size().cmp(&right.size()).reverse());
135 }
136 let deduplication_record = hardlinks_handler.deduplicate(&mut data_tree);
137 if !only_one_arg {
138 assert_eq!(data_tree.name().as_os_str().to_str(), Some(""));
139 *data_tree.name_mut() = OsStringDisplay::os_string_from("(total)");
140 }
141 (data_tree, deduplication_record)
142 };
143
144 GLOBAL_STATUS_BOARD.clear_line(0);
145
146 if let Some(json_output) = json_output {
147 let JsonOutputParam {
148 shared_details,
149 shared_summary,
150 } = json_output;
151 let tree = data_tree
152 .into_reflection() .par_convert_names_to_utf8() .expect("convert all names from raw string to UTF-8");
155
156 let deduplication_result = if !shared_details && !shared_summary {
157 Ok(JsonShared::default())
158 } else {
159 || -> Result<_, RuntimeError> {
161 let mut shared = deduplication_record
162 .map_err(HardlinksHandler::convert_error)?
163 .pipe(HardlinksHandler::json_report)?
164 .unwrap_or_default();
165 if !shared_details {
166 shared.details = None;
167 }
168 if !shared_summary {
169 shared.summary = None;
170 }
171 Ok(shared)
172 }()
173 };
174
175 let (shared, deduplication_result) = match deduplication_result {
177 Ok(shared) => (shared, Ok(())),
178 Err(error) => (JsonShared::default(), Err(error)),
179 };
180
181 let json_tree = JsonTree { tree, shared };
182 let json_data = JsonData {
183 schema_version: SchemaVersion,
184 binary_version: Some(BinaryVersion::current()),
185 body: json_tree.into(),
186 };
187
188 return serde_json::to_writer(stdout(), &json_data)
189 .map_err(RuntimeError::SerializationFailure)
190 .or(deduplication_result);
191 }
192
193 let visualizer = Visualizer {
194 data_tree: &data_tree,
195 bytes_format,
196 direction,
197 bar_alignment,
198 column_width_distribution,
199 };
200
201 print!("{visualizer}"); let deduplication_record = deduplication_record.map_err(HardlinksHandler::convert_error)?;
204 HardlinksHandler::print_report(deduplication_record, bytes_format)?;
205
206 Ok(())
207 }
208}
209
210#[derive(Debug, Clone, Copy)]
213pub struct JsonOutputParam {
214 pub shared_details: bool,
216 pub shared_summary: bool,
218}
219
220impl JsonOutputParam {
221 pub(super) fn from_cli_flags(
223 output_json: bool,
224 omit_shared_details: bool,
225 omit_shared_summary: bool,
226 ) -> Option<Self> {
227 output_json.then_some(JsonOutputParam {
228 shared_details: !omit_shared_details,
229 shared_summary: !omit_shared_summary,
230 })
231 }
232}
233
234pub trait HardlinkSubroutines<Size: size::Size>: DeduplicateSharedSize<Size> {
236 fn convert_error(error: Self::Error) -> RuntimeError;
238 fn print_report(
240 report: Self::Report,
241 bytes_format: Size::DisplayFormat,
242 ) -> Result<(), RuntimeError>;
243 fn json_report(report: Self::Report) -> Result<Option<JsonShared<Size>>, RuntimeError>;
245}
246
247impl<Size> HardlinkSubroutines<Size> for HardlinkIgnorant
248where
249 DataTree<OsStringDisplay, Size>: Send,
250 Size: size::Size + Sync,
251{
252 #[inline]
253 fn convert_error(error: Self::Error) -> RuntimeError {
254 match error {}
255 }
256
257 #[inline]
258 fn print_report((): Self::Report, _: Size::DisplayFormat) -> Result<(), RuntimeError> {
259 Ok(())
260 }
261
262 #[inline]
263 fn json_report((): Self::Report) -> Result<Option<JsonShared<Size>>, RuntimeError> {
264 Ok(None)
265 }
266}
267
268#[cfg(unix)]
269mod unix_ext;