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                max_depth,
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 unit_and_tree = stdin()
67                .pipe(serde_json::from_reader::<_, JsonData>)
68                .map_err(RuntimeError::DeserializationFailure)?
69                .unit_and_tree;
70
71            macro_rules! visualize {
72                ($reflection:expr, $bytes_format: expr) => {{
73                    let data_tree = $reflection
74                        .par_try_into_tree()
75                        .map_err(|error| RuntimeError::InvalidInputReflection(error.to_string()))?;
76                    Visualizer {
77                        data_tree: &data_tree,
78                        bytes_format: $bytes_format,
79                        column_width_distribution,
80                        direction,
81                        bar_alignment,
82                        max_depth,
83                    }
84                    .to_string()
85                }};
86            }
87
88            let visualization = match unit_and_tree {
89                UnitAndTree::Bytes(reflection) => visualize!(reflection, bytes_format),
90                UnitAndTree::Blocks(reflection) => visualize!(reflection, ()),
91            };
92
93            print!("{visualization}"); // it already ends with "\n", println! isn't needed here.
94            return Ok(());
95        }
96
97        let threads = match self.args.threads {
98            Threads::Auto => {
99                let disks = Disks::new_with_refreshed_list();
100                if any_path_is_in_hdd::<hdd::RealApi>(&self.args.files, &disks) {
101                    eprintln!("warning: HDD detected, the thread limit will be set to 1");
102                    Some(1)
103                } else {
104                    None
105                }
106            }
107            Threads::Max => None,
108            Threads::Fixed(threads) => Some(threads),
109        };
110
111        if let Some(threads) = threads {
112            rayon::ThreadPoolBuilder::new()
113                .num_threads(threads)
114                .build_global()
115                .unwrap_or_else(|_| eprintln!("warning: Failed to set thread limit to {threads}"));
116        }
117
118        let report_error = if self.args.silent_errors {
119            ErrorReport::SILENT
120        } else {
121            ErrorReport::TEXT
122        };
123
124        #[allow(clippy::extra_unused_type_parameters)]
125        fn error_only_reporter<Size>(
126            report_error: fn(ErrorReport),
127        ) -> ErrorOnlyReporter<fn(ErrorReport)> {
128            ErrorOnlyReporter::new(report_error)
129        }
130
131        fn progress_and_error_reporter<Size>(
132            report_error: fn(ErrorReport),
133        ) -> ProgressAndErrorReporter<Size, fn(ErrorReport)>
134        where
135            Size: size::Size + Into<u64> + Send + Sync,
136            ProgressReport<Size>: Default + 'static,
137            u64: Into<Size>,
138        {
139            ProgressAndErrorReporter::new(
140                ProgressReport::TEXT,
141                Duration::from_millis(100),
142                report_error,
143            )
144        }
145
146        macro_rules! run {
147            ($(
148                $(#[$variant_attrs:meta])*
149                {
150                    $size:ty => $format:expr;
151                    $quantity:ident => $size_getter:ident;
152                    $progress:literal => $create_reporter:ident;
153                }
154            )*) => { match self.args {$(
155                $(#[$variant_attrs])*
156                Args {
157                    quantity: Quantity::$quantity,
158                    progress: $progress,
159                    files,
160                    json_output,
161                    bytes_format,
162                    top_down,
163                    align_right,
164                    max_depth,
165                    min_ratio,
166                    no_sort,
167                    ..
168                } => Sub {
169                    direction: Direction::from_top_down(top_down),
170                    bar_alignment: BarAlignment::from_align_right(align_right),
171                    size_getter: $size_getter,
172                    reporter: $create_reporter::<$size>(report_error),
173                    bytes_format: $format(bytes_format),
174                    files,
175                    json_output,
176                    column_width_distribution,
177                    max_depth,
178                    min_ratio,
179                    no_sort,
180                }
181                .run(),
182            )*} };
183        }
184
185        run! {
186            {
187                Bytes => |x| x;
188                ApparentSize => GetApparentSize;
189                false => error_only_reporter;
190            }
191
192            {
193                Bytes => |x| x;
194                ApparentSize => GetApparentSize;
195                true => progress_and_error_reporter;
196            }
197
198            #[cfg(unix)]
199            {
200                Bytes => |x| x;
201                BlockSize => GetBlockSize;
202                false => error_only_reporter;
203            }
204
205            #[cfg(unix)]
206            {
207                Bytes => |x| x;
208                BlockSize => GetBlockSize;
209                true => progress_and_error_reporter;
210            }
211
212            #[cfg(unix)]
213            {
214                Blocks => |_| ();
215                BlockCount => GetBlockCount;
216                false => error_only_reporter;
217            }
218
219            #[cfg(unix)]
220            {
221                Blocks => |_| ();
222                BlockCount => GetBlockCount;
223                true => progress_and_error_reporter;
224            }
225        }
226    }
227}
228
229mod hdd;
230mod mount_point;