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
33impl App {
34 pub fn from_env() -> Self {
36 App {
37 args: Args::parse(),
38 }
39 }
40
41 pub fn run(mut self) -> Result<(), RuntimeError> {
43 let column_width_distribution = self.args.column_width_distribution();
51
52 if self.args.json_input {
53 if !self.args.files.is_empty() {
54 return Err(RuntimeError::JsonInputArgConflict);
55 }
56
57 let Args {
58 bytes_format,
59 top_down,
60 align_right,
61 ..
62 } = self.args;
63 let direction = Direction::from_top_down(top_down);
64 let bar_alignment = BarAlignment::from_align_right(align_right);
65
66 let body = stdin()
67 .pipe(serde_json::from_reader::<_, JsonData>)
68 .map_err(RuntimeError::DeserializationFailure)?
69 .body;
70
71 trait VisualizeJsonTree: size::Size + Into<u64> + Send {
72 fn visualize_json_tree(
73 tree: JsonTree<Self>,
74 bytes_format: Self::DisplayFormat,
75 column_width_distribution: ColumnWidthDistribution,
76 direction: Direction,
77 bar_alignment: BarAlignment,
78 ) -> Result<String, RuntimeError> {
79 let JsonTree { tree, shared } = tree;
80
81 let data_tree = tree
82 .par_try_into_tree()
83 .map_err(|error| RuntimeError::InvalidInputReflection(error.to_string()))?;
84 let visualizer = Visualizer {
85 data_tree: &data_tree,
86 bytes_format,
87 column_width_distribution,
88 direction,
89 bar_alignment,
90 };
91
92 let JsonShared { details, summary } = shared;
93 let summary = summary.or_else(|| details.map(|details| details.summarize()));
94
95 let visualization = if let Some(summary) = summary {
96 let summary = summary.display(bytes_format);
97 format!("{visualizer}{summary}\n")
99 } else {
100 visualizer.to_string()
101 };
102
103 Ok(visualization)
104 }
105 }
106
107 impl<Size: size::Size + Into<u64> + Send> VisualizeJsonTree for Size {}
108
109 macro_rules! visualize {
110 ($tree:expr, $bytes_format:expr) => {
111 VisualizeJsonTree::visualize_json_tree(
112 $tree,
113 $bytes_format,
114 column_width_distribution,
115 direction,
116 bar_alignment,
117 )
118 };
119 }
120
121 let visualization = match body {
122 JsonDataBody::Bytes(tree) => visualize!(tree, bytes_format),
123 JsonDataBody::Blocks(tree) => visualize!(tree, ()),
124 }?;
125
126 print!("{visualization}"); return Ok(());
128 }
129
130 #[cfg(not(unix))]
131 if self.args.deduplicate_hardlinks {
132 return crate::runtime_error::UnsupportedFeature::DeduplicateHardlink
133 .pipe(RuntimeError::UnsupportedFeature)
134 .pipe(Err);
135 }
136
137 #[cfg(not(unix))]
138 if self.args.one_file_system {
139 return crate::runtime_error::UnsupportedFeature::OneFileSystem
140 .pipe(RuntimeError::UnsupportedFeature)
141 .pipe(Err);
142 }
143
144 let threads = match self.args.threads {
145 Threads::Auto => {
146 let disks = Disks::new_with_refreshed_list();
147 if any_path_is_in_hdd::<Disk, hdd::RealFs>(&self.args.files, &disks) {
148 eprintln!("warning: HDD detected, the thread limit will be set to 1");
149 eprintln!("hint: You can pass --threads=max disable this behavior");
150 Some(1)
151 } else {
152 None
153 }
154 }
155 Threads::Max => None,
156 Threads::Fixed(threads) => Some(threads.get()),
157 };
158
159 if let Some(threads) = threads {
160 rayon::ThreadPoolBuilder::new()
161 .num_threads(threads)
162 .build_global()
163 .unwrap_or_else(|_| eprintln!("warning: Failed to set thread limit to {threads}"));
164 }
165
166 if cfg!(unix) && self.args.deduplicate_hardlinks && self.args.files.len() > 1 {
167 use overlapping_arguments::{remove_overlapping_paths, RealApi};
171 remove_overlapping_paths::<RealApi>(&mut self.args.files);
172 }
173
174 let report_error = if self.args.silent_errors {
175 ErrorReport::SILENT
176 } else {
177 ErrorReport::TEXT
178 };
179
180 trait GetSizeUtils: GetSize<Size: size::Size> {
181 const INSTANCE: Self;
182 const QUANTITY: Quantity;
183 fn formatter(bytes_format: BytesFormat) -> <Self::Size as size::Size>::DisplayFormat;
184 }
185
186 impl GetSizeUtils for GetApparentSize {
187 const INSTANCE: Self = GetApparentSize;
188 const QUANTITY: Quantity = Quantity::ApparentSize;
189 #[inline]
190 fn formatter(bytes_format: BytesFormat) -> BytesFormat {
191 bytes_format
192 }
193 }
194
195 #[cfg(unix)]
196 impl GetSizeUtils for GetBlockSize {
197 const INSTANCE: Self = GetBlockSize;
198 const QUANTITY: Quantity = Quantity::BlockSize;
199 #[inline]
200 fn formatter(bytes_format: BytesFormat) -> BytesFormat {
201 bytes_format
202 }
203 }
204
205 #[cfg(unix)]
206 impl GetSizeUtils for GetBlockCount {
207 const INSTANCE: Self = GetBlockCount;
208 const QUANTITY: Quantity = Quantity::BlockCount;
209 #[inline]
210 fn formatter(_: BytesFormat) {}
211 }
212
213 trait CreateReporter<const REPORT_PROGRESS: bool>: GetSizeUtils {
214 type Reporter;
215 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter;
216 }
217
218 impl<SizeGetter> CreateReporter<false> for SizeGetter
219 where
220 Self: GetSizeUtils,
221 {
222 type Reporter = ErrorOnlyReporter<fn(ErrorReport)>;
223 #[inline]
224 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter {
225 ErrorOnlyReporter::new(report_error)
226 }
227 }
228
229 impl<SizeGetter> CreateReporter<true> for SizeGetter
230 where
231 Self: GetSizeUtils,
232 Self::Size: Into<u64> + Send + Sync,
233 ProgressReport<Self::Size>: Default + 'static,
234 u64: Into<Self::Size>,
235 {
236 type Reporter = ProgressAndErrorReporter<Self::Size, fn(ErrorReport)>;
237 #[inline]
238 fn create_reporter(report_error: fn(ErrorReport)) -> Self::Reporter {
239 ProgressAndErrorReporter::new(
240 ProgressReport::TEXT,
241 Duration::from_millis(100),
242 report_error,
243 )
244 }
245 }
246
247 trait CreateHardlinksHandler<const DEDUPLICATE_HARDLINKS: bool, const REPORT_PROGRESS: bool>:
248 CreateReporter<REPORT_PROGRESS>
249 {
250 type HardlinksHandler: hardlink::RecordHardlinks<Self::Size, Self::Reporter>
251 + sub::HardlinkSubroutines<Self::Size>;
252 fn create_hardlinks_handler() -> Self::HardlinksHandler;
253 }
254
255 impl<const REPORT_PROGRESS: bool, SizeGetter> CreateHardlinksHandler<false, REPORT_PROGRESS>
256 for SizeGetter
257 where
258 Self: CreateReporter<REPORT_PROGRESS>,
259 Self::Size: Send + Sync,
260 {
261 type HardlinksHandler = hardlink::HardlinkIgnorant;
262 #[inline]
263 fn create_hardlinks_handler() -> Self::HardlinksHandler {
264 hardlink::HardlinkIgnorant
265 }
266 }
267
268 #[cfg(unix)]
269 impl<const REPORT_PROGRESS: bool, SizeGetter> CreateHardlinksHandler<true, REPORT_PROGRESS>
270 for SizeGetter
271 where
272 Self: CreateReporter<REPORT_PROGRESS>,
273 Self::Size: Send + Sync + 'static,
274 Self::Reporter: crate::reporter::Reporter<Self::Size>,
275 {
276 type HardlinksHandler = hardlink::HardlinkAware<Self::Size>;
277 #[inline]
278 fn create_hardlinks_handler() -> Self::HardlinksHandler {
279 hardlink::HardlinkAware::new()
280 }
281 }
282
283 macro_rules! run {
284 ($(
285 $(#[$variant_attrs:meta])*
286 $size_getter:ident, $progress:literal, $hardlinks:ident;
287 )*) => { match self.args {$(
288 $(#[$variant_attrs])*
289 Args {
290 quantity: <$size_getter as GetSizeUtils>::QUANTITY,
291 progress: $progress,
292 #[cfg(unix)] deduplicate_hardlinks: $hardlinks,
293 #[cfg(not(unix))] deduplicate_hardlinks: _,
294 one_file_system,
295 files,
296 json_output,
297 bytes_format,
298 top_down,
299 align_right,
300 max_depth,
301 min_ratio,
302 no_sort,
303 omit_json_shared_details,
304 omit_json_shared_summary,
305 ..
306 } => Sub {
307 direction: Direction::from_top_down(top_down),
308 bar_alignment: BarAlignment::from_align_right(align_right),
309 size_getter: <$size_getter as GetSizeUtils>::INSTANCE,
310 hardlinks_handler: <$size_getter as CreateHardlinksHandler<{ cfg!(unix) && $hardlinks }, $progress>>::create_hardlinks_handler(),
311 device_boundary: DeviceBoundary::from_one_file_system(one_file_system),
312 reporter: <$size_getter as CreateReporter<$progress>>::create_reporter(report_error),
313 bytes_format: <$size_getter as GetSizeUtils>::formatter(bytes_format),
314 files,
315 json_output: JsonOutputParam::from_cli_flags(json_output, omit_json_shared_details, omit_json_shared_summary),
316 column_width_distribution,
317 max_depth,
318 min_ratio,
319 no_sort,
320 }
321 .run(),
322 )*} };
323 }
324
325 run! {
326 GetApparentSize, false, false;
327 GetApparentSize, true, false;
328 #[cfg(unix)] GetBlockSize, false, false;
329 #[cfg(unix)] GetBlockSize, true, false;
330 #[cfg(unix)] GetBlockCount, false, false;
331 #[cfg(unix)] GetBlockCount, true, false;
332 #[cfg(unix)] GetApparentSize, false, true;
333 #[cfg(unix)] GetApparentSize, true, true;
334 #[cfg(unix)] GetBlockSize, false, true;
335 #[cfg(unix)] GetBlockSize, true, true;
336 #[cfg(unix)] GetBlockCount, false, true;
337 #[cfg(unix)] GetBlockCount, true, true;
338 }
339 }
340}
341
342mod hdd;
343mod mount_point;
344mod overlapping_arguments;