1pub mod sub;
2
3pub use sub::Sub;
4
5use crate::{
6 args::{Args, Quantity, Threads},
7 bytes_format::BytesFormat,
8 device::DeviceBoundary,
9 get_size::{GetApparentSize, GetSize},
10 hardlink,
11 json_data::{JsonData, JsonDataBody, JsonShared, JsonTree},
12 reporter::{ErrorOnlyReporter, ErrorReport, ProgressAndErrorReporter, ProgressReport},
13 runtime_error::RuntimeError,
14 size,
15 visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
16};
17use clap::Parser;
18use hdd::any_path_is_in_hdd;
19use pipe_trait::Pipe;
20use std::{io::stdin, time::Duration};
21use sub::JsonOutputParam;
22use sysinfo::{Disk, Disks};
23
24#[cfg(unix)]
25use crate::get_size::{GetBlockCount, GetBlockSize};
26
27pub struct App {
29 args: Args,
31}
32
33#[derive(Clone, Copy)]
35struct ChartLayout {
36 column_width_distribution: ColumnWidthDistribution,
38 direction: Direction,
40 bar_alignment: BarAlignment,
42}
43
44#[derive(Clone, Copy)]
46struct JsonInputShaping {
47 max_depth: u64,
49 min_ratio: f32,
51 no_sort: bool,
53}
54
55impl App {
56 pub fn from_env() -> Self {
58 App {
59 args: Args::parse(),
60 }
61 }
62
63 pub fn run(mut self) -> Result<(), RuntimeError> {
65 let column_width_distribution = self.args.column_width_distribution();
73
74 if self.args.json_input {
75 if !self.args.files.is_empty() {
76 return Err(RuntimeError::JsonInputArgConflict);
77 }
78
79 let Args {
80 bytes_format,
81 top_down,
82 align_right,
83 max_depth,
84 min_ratio,
85 no_sort,
86 ..
87 } = self.args;
88 let layout = ChartLayout {
89 column_width_distribution,
90 direction: Direction::from_top_down(top_down),
91 bar_alignment: BarAlignment::from_align_right(align_right),
92 };
93 let shaping = JsonInputShaping {
94 max_depth: max_depth.get(),
95 min_ratio: min_ratio.into(),
96 no_sort,
97 };
98
99 let body = stdin()
100 .pipe(serde_json::from_reader::<_, JsonData>)
101 .map_err(RuntimeError::DeserializationFailure)?
102 .body;
103
104 trait VisualizeJsonTree: size::Size + Into<u64> + Send {
105 fn visualize_json_tree(
106 tree: JsonTree<Self>,
107 bytes_format: Self::DisplayFormat,
108 layout: ChartLayout,
109 shaping: JsonInputShaping,
110 ) -> Result<String, RuntimeError> {
111 let JsonTree { tree, shared } = tree;
112 let ChartLayout {
113 column_width_distribution,
114 direction,
115 bar_alignment,
116 } = layout;
117 let JsonInputShaping {
118 max_depth,
119 min_ratio,
120 no_sort,
121 } = shaping;
122
123 let mut data_tree = tree
124 .par_try_into_tree()
125 .map_err(|error| RuntimeError::InvalidInputReflection(error.to_string()))?
126 .into_par_retained(|_, depth| depth + 1 < max_depth);
127 data_tree.par_cull_insignificant_data(min_ratio);
128 if !no_sort {
129 data_tree
130 .par_sort_by(|left, right| left.size().cmp(&right.size()).reverse());
131 }
132
133 let visualizer = Visualizer {
134 data_tree: &data_tree,
135 bytes_format,
136 column_width_distribution,
137 direction,
138 bar_alignment,
139 };
140
141 let JsonShared { details, summary } = shared;
142 let summary = summary.or_else(|| details.map(|details| details.summarize()));
143
144 let visualization = if let Some(summary) = summary {
145 let summary = summary.display(bytes_format);
146 format!("{visualizer}{summary}\n")
148 } else {
149 visualizer.to_string()
150 };
151
152 Ok(visualization)
153 }
154 }
155
156 impl<Size: size::Size + Into<u64> + Send> VisualizeJsonTree for Size {}
157
158 macro_rules! visualize {
159 ($tree:expr, $bytes_format:expr) => {
160 VisualizeJsonTree::visualize_json_tree($tree, $bytes_format, layout, shaping)
161 };
162 }
163
164 let visualization = match body {
165 JsonDataBody::Bytes(tree) => visualize!(tree, bytes_format),
166 JsonDataBody::Blocks(tree) => visualize!(tree, ()),
167 }?;
168
169 print!("{visualization}"); return Ok(());
171 }
172
173 #[cfg(not(unix))]
174 if self.args.deduplicate_hardlinks {
175 return crate::runtime_error::UnsupportedFeature::DeduplicateHardlink
176 .pipe(RuntimeError::UnsupportedFeature)
177 .pipe(Err);
178 }
179
180 #[cfg(not(unix))]
181 if self.args.one_file_system {
182 return crate::runtime_error::UnsupportedFeature::OneFileSystem
183 .pipe(RuntimeError::UnsupportedFeature)
184 .pipe(Err);
185 }
186
187 let threads = match self.args.threads {
188 Threads::Auto => {
189 let disks = Disks::new_with_refreshed_list();
190 any_path_is_in_hdd::<Disk, hdd::RealFs>(&self.args.files, &disks).then(|| {
191 eprintln!("warning: HDD detected, the thread limit will be set to 1");
192 eprintln!("hint: You can pass --threads=max disable this behavior");
193 1
194 })
195 }
196 Threads::Max => None,
197 Threads::Fixed(threads) => Some(threads.get()),
198 };
199
200 if let Some(threads) = threads {
201 rayon::ThreadPoolBuilder::new()
202 .num_threads(threads)
203 .build_global()
204 .unwrap_or_else(|_| eprintln!("warning: Failed to set thread limit to {threads}"));
205 }
206
207 if cfg!(unix) && self.args.deduplicate_hardlinks && self.args.files.len() > 1 {
208 use overlapping_arguments::{RealApi, remove_overlapping_paths};
212 remove_overlapping_paths::<RealApi>(&mut self.args.files);
213 }
214
215 let report_error = if self.args.silent_errors {
216 ErrorReport::SILENT
217 } else {
218 ErrorReport::TEXT
219 };
220
221 trait GetSizeUtils: GetSize<Size: size::Size> {
222 const INSTANCE: Self;
223 const QUANTITY: Quantity;
224 fn formatter(bytes_format: BytesFormat) -> <Self::Size as size::Size>::DisplayFormat;
225 }
226
227 impl GetSizeUtils for GetApparentSize {
228 const INSTANCE: Self = GetApparentSize;
229 const QUANTITY: Quantity = Quantity::ApparentSize;
230 #[inline]
231 fn formatter(bytes_format: BytesFormat) -> BytesFormat {
232 bytes_format
233 }
234 }
235
236 #[cfg(unix)]
237 impl GetSizeUtils for GetBlockSize {
238 const INSTANCE: Self = GetBlockSize;
239 const QUANTITY: Quantity = Quantity::BlockSize;
240 #[inline]
241 fn formatter(bytes_format: BytesFormat) -> BytesFormat {
242 bytes_format
243 }
244 }
245
246 #[cfg(unix)]
247 impl GetSizeUtils for GetBlockCount {
248 const INSTANCE: Self = GetBlockCount;
249 const QUANTITY: Quantity = Quantity::BlockCount;
250 #[inline]
251 fn formatter(_: BytesFormat) {}
252 }
253
254 trait CreateReporter<const REPORT_PROGRESS: bool>: GetSizeUtils {
255 type Reporter;
256 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter;
257 }
258
259 impl<SizeGetter> CreateReporter<false> for SizeGetter
260 where
261 Self: GetSizeUtils,
262 {
263 type Reporter = ErrorOnlyReporter<fn(ErrorReport)>;
264 #[inline]
265 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter {
266 ErrorOnlyReporter::new(report_error)
267 }
268 }
269
270 impl<SizeGetter> CreateReporter<true> for SizeGetter
271 where
272 Self: GetSizeUtils,
273 Self::Size: Into<u64> + Send + Sync,
274 ProgressReport<Self::Size>: Default + 'static,
275 u64: Into<Self::Size>,
276 {
277 type Reporter = ProgressAndErrorReporter<Self::Size, fn(ErrorReport)>;
278 #[inline]
279 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter {
280 ProgressAndErrorReporter::new(
281 ProgressReport::TEXT,
282 Duration::from_millis(100),
283 report_error,
284 )
285 }
286 }
287
288 trait CreateHardlinksHandler<const DEDUPLICATE_HARDLINKS: bool, const REPORT_PROGRESS: bool>:
289 CreateReporter<REPORT_PROGRESS>
290 {
291 type HardlinksHandler: hardlink::RecordHardlinks<Self::Size, Self::Reporter>
292 + sub::HardlinkSubroutines<Self::Size>;
293 fn create_hardlinks_handler() -> Self::HardlinksHandler;
294 }
295
296 impl<const REPORT_PROGRESS: bool, SizeGetter> CreateHardlinksHandler<false, REPORT_PROGRESS>
297 for SizeGetter
298 where
299 Self: CreateReporter<REPORT_PROGRESS>,
300 Self::Size: Send + Sync,
301 {
302 type HardlinksHandler = hardlink::HardlinkIgnorant;
303 #[inline]
304 fn create_hardlinks_handler() -> Self::HardlinksHandler {
305 hardlink::HardlinkIgnorant
306 }
307 }
308
309 #[cfg(unix)]
310 impl<const REPORT_PROGRESS: bool, SizeGetter> CreateHardlinksHandler<true, REPORT_PROGRESS>
311 for SizeGetter
312 where
313 Self: CreateReporter<REPORT_PROGRESS>,
314 Self::Size: Send + Sync + 'static,
315 Self::Reporter: crate::reporter::Reporter<Self::Size>,
316 {
317 type HardlinksHandler = hardlink::HardlinkAware<Self::Size>;
318 #[inline]
319 fn create_hardlinks_handler() -> Self::HardlinksHandler {
320 hardlink::HardlinkAware::new()
321 }
322 }
323
324 macro_rules! run {
325 ($(
326 $(#[$variant_attrs:meta])*
327 $size_getter:ident, $progress:literal, $hardlinks:ident;
328 )*) => { match self.args {$(
329 $(#[$variant_attrs])*
330 Args {
331 quantity: <$size_getter as GetSizeUtils>::QUANTITY,
332 progress: $progress,
333 #[cfg(unix)] deduplicate_hardlinks: $hardlinks,
334 #[cfg(not(unix))] deduplicate_hardlinks: _,
335 one_file_system,
336 files,
337 json_output,
338 bytes_format,
339 top_down,
340 align_right,
341 max_depth,
342 min_ratio,
343 no_sort,
344 omit_json_shared_details,
345 omit_json_shared_summary,
346 ..
347 } => Sub {
348 direction: Direction::from_top_down(top_down),
349 bar_alignment: BarAlignment::from_align_right(align_right),
350 size_getter: <$size_getter as GetSizeUtils>::INSTANCE,
351 hardlinks_handler: <$size_getter as CreateHardlinksHandler<{ cfg!(unix) && $hardlinks }, $progress>>::create_hardlinks_handler(),
352 device_boundary: DeviceBoundary::from_one_file_system(one_file_system),
353 reporter: <$size_getter as CreateReporter<$progress>>::create_reporter(report_error),
354 bytes_format: <$size_getter as GetSizeUtils>::formatter(bytes_format),
355 files,
356 json_output: JsonOutputParam::from_cli_flags(json_output, omit_json_shared_details, omit_json_shared_summary),
357 column_width_distribution,
358 max_depth,
359 min_ratio,
360 no_sort,
361 }
362 .run(),
363 )*} };
364 }
365
366 run! {
367 GetApparentSize, false, false;
368 GetApparentSize, true, false;
369 #[cfg(unix)] GetBlockSize, false, false;
370 #[cfg(unix)] GetBlockSize, true, false;
371 #[cfg(unix)] GetBlockCount, false, false;
372 #[cfg(unix)] GetBlockCount, true, false;
373 #[cfg(unix)] GetApparentSize, false, true;
374 #[cfg(unix)] GetApparentSize, true, true;
375 #[cfg(unix)] GetBlockSize, false, true;
376 #[cfg(unix)] GetBlockSize, true, true;
377 #[cfg(unix)] GetBlockCount, false, true;
378 #[cfg(unix)] GetBlockCount, true, true;
379 }
380 }
381}
382
383mod hdd;
384mod mount_point;
385mod overlapping_arguments;