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