parallel_disk_usage/
app.rs

1pub mod sub;
2
3pub use sub::Sub;
4
5use crate::{
6    args::{Args, Quantity, Threads},
7    get_size::GetApparentSize,
8    json_data::{JsonData, UnitAndTree},
9    reporter::{ErrorOnlyReporter, ErrorReport, ProgressAndErrorReporter, ProgressReport},
10    runtime_error::RuntimeError,
11    size::{self, Bytes},
12    visualizer::{BarAlignment, Direction, Visualizer},
13};
14use clap::Parser;
15use hdd::any_path_is_in_hdd;
16use pipe_trait::Pipe;
17use std::{io::stdin, time::Duration};
18use sysinfo::Disks;
19
20#[cfg(unix)]
21use crate::{
22    get_size::{GetBlockCount, GetBlockSize},
23    size::Blocks,
24};
25
26/// The main application.
27pub struct App {
28    /// The CLI arguments.
29    args: Args,
30}
31
32impl App {
33    /// Initialize the application from the environment.
34    pub fn from_env() -> Self {
35        App {
36            args: Args::parse(),
37        }
38    }
39
40    /// Run the application.
41    pub fn run(self) -> Result<(), RuntimeError> {
42        // DYNAMIC DISPATCH POLICY:
43        //
44        // Errors rarely occur, therefore, using dynamic dispatch to report errors have an acceptable
45        // impact on performance.
46        //
47        // The other operations which are invoked frequently should not utilize dynamic dispatch.
48
49        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 unit_and_tree = stdin()
66                .pipe(serde_json::from_reader::<_, JsonData>)
67                .map_err(RuntimeError::DeserializationFailure)?
68                .unit_and_tree;
69
70            macro_rules! visualize {
71                ($reflection:expr, $bytes_format: expr) => {{
72                    let data_tree = $reflection
73                        .par_try_into_tree()
74                        .map_err(|error| RuntimeError::InvalidInputReflection(error.to_string()))?;
75                    Visualizer {
76                        data_tree: &data_tree,
77                        bytes_format: $bytes_format,
78                        column_width_distribution,
79                        direction,
80                        bar_alignment,
81                    }
82                    .to_string()
83                }};
84            }
85
86            let visualization = match unit_and_tree {
87                UnitAndTree::Bytes(reflection) => visualize!(reflection, bytes_format),
88                UnitAndTree::Blocks(reflection) => visualize!(reflection, ()),
89            };
90
91            print!("{visualization}"); // it already ends with "\n", println! isn't needed here.
92            return Ok(());
93        }
94
95        let threads = match self.args.threads {
96            Threads::Auto => {
97                let disks = Disks::new_with_refreshed_list();
98                if any_path_is_in_hdd::<hdd::RealApi>(&self.args.files, &disks) {
99                    eprintln!("warning: HDD detected, the thread limit will be set to 1");
100                    Some(1)
101                } else {
102                    None
103                }
104            }
105            Threads::Max => None,
106            Threads::Fixed(threads) => Some(threads),
107        };
108
109        if let Some(threads) = threads {
110            rayon::ThreadPoolBuilder::new()
111                .num_threads(threads)
112                .build_global()
113                .unwrap_or_else(|_| eprintln!("warning: Failed to set thread limit to {threads}"));
114        }
115
116        let report_error = if self.args.silent_errors {
117            ErrorReport::SILENT
118        } else {
119            ErrorReport::TEXT
120        };
121
122        #[allow(clippy::extra_unused_type_parameters)]
123        fn error_only_reporter<Size>(
124            report_error: fn(ErrorReport),
125        ) -> ErrorOnlyReporter<fn(ErrorReport)> {
126            ErrorOnlyReporter::new(report_error)
127        }
128
129        fn progress_and_error_reporter<Size>(
130            report_error: fn(ErrorReport),
131        ) -> ProgressAndErrorReporter<Size, fn(ErrorReport)>
132        where
133            Size: size::Size + Into<u64> + Send + Sync,
134            ProgressReport<Size>: Default + 'static,
135            u64: Into<Size>,
136        {
137            ProgressAndErrorReporter::new(
138                ProgressReport::TEXT,
139                Duration::from_millis(100),
140                report_error,
141            )
142        }
143
144        macro_rules! run {
145            ($(
146                $(#[$variant_attrs:meta])*
147                {
148                    $size:ty => $format:expr;
149                    $quantity:ident => $size_getter:ident;
150                    $progress:literal => $create_reporter:ident;
151                }
152            )*) => { match self.args {$(
153                $(#[$variant_attrs])*
154                Args {
155                    quantity: Quantity::$quantity,
156                    progress: $progress,
157                    files,
158                    json_output,
159                    bytes_format,
160                    top_down,
161                    align_right,
162                    max_depth,
163                    min_ratio,
164                    no_sort,
165                    ..
166                } => Sub {
167                    direction: Direction::from_top_down(top_down),
168                    bar_alignment: BarAlignment::from_align_right(align_right),
169                    size_getter: $size_getter,
170                    reporter: $create_reporter::<$size>(report_error),
171                    bytes_format: $format(bytes_format),
172                    files,
173                    json_output,
174                    column_width_distribution,
175                    max_depth,
176                    min_ratio,
177                    no_sort,
178                }
179                .run(),
180            )*} };
181        }
182
183        run! {
184            {
185                Bytes => |x| x;
186                ApparentSize => GetApparentSize;
187                false => error_only_reporter;
188            }
189
190            {
191                Bytes => |x| x;
192                ApparentSize => GetApparentSize;
193                true => progress_and_error_reporter;
194            }
195
196            #[cfg(unix)]
197            {
198                Bytes => |x| x;
199                BlockSize => GetBlockSize;
200                false => error_only_reporter;
201            }
202
203            #[cfg(unix)]
204            {
205                Bytes => |x| x;
206                BlockSize => GetBlockSize;
207                true => progress_and_error_reporter;
208            }
209
210            #[cfg(unix)]
211            {
212                Blocks => |_| ();
213                BlockCount => GetBlockCount;
214                false => error_only_reporter;
215            }
216
217            #[cfg(unix)]
218            {
219                Blocks => |_| ();
220                BlockCount => GetBlockCount;
221                true => progress_and_error_reporter;
222            }
223        }
224    }
225}
226
227mod hdd;
228mod mount_point;