rustic_rs/config/
progress_options.rs

1//! Progress Bar Config
2
3use std::{borrow::Cow, fmt::Write, time::Duration};
4
5use indicatif::{HumanDuration, ProgressBar, ProgressState, ProgressStyle};
6
7use clap::Parser;
8use conflate::Merge;
9
10use serde::{Deserialize, Serialize};
11use serde_with::{DisplayFromStr, serde_as};
12
13use rustic_core::{Progress, ProgressBars};
14
15mod constants {
16    use std::time::Duration;
17
18    pub(super) const DEFAULT_INTERVAL: Duration = Duration::from_millis(100);
19}
20
21/// Progress Bar Config
22#[serde_as]
23#[derive(Default, Debug, Parser, Clone, Copy, Deserialize, Serialize, Merge)]
24#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
25pub struct ProgressOptions {
26    /// Don't show any progress bar
27    #[clap(long, global = true, env = "RUSTIC_NO_PROGRESS")]
28    #[merge(strategy=conflate::bool::overwrite_false)]
29    pub no_progress: bool,
30
31    /// Interval to update progress bars (default: 100ms)
32    #[clap(
33        long,
34        global = true,
35        env = "RUSTIC_PROGRESS_INTERVAL",
36        value_name = "DURATION",
37        conflicts_with = "no_progress"
38    )]
39    #[serde_as(as = "Option<DisplayFromStr>")]
40    #[merge(strategy=conflate::option::overwrite_none)]
41    pub progress_interval: Option<humantime::Duration>,
42}
43
44impl ProgressOptions {
45    /// Get the progress interval
46    fn progress_interval(&self) -> Duration {
47        self.progress_interval
48            .map_or(constants::DEFAULT_INTERVAL, |i| *i)
49    }
50
51    /// Create a hidden progress bar
52    pub fn no_progress() -> RusticProgress {
53        RusticProgress(ProgressBar::hidden(), ProgressType::Hidden)
54    }
55}
56
57#[allow(clippy::literal_string_with_formatting_args)]
58impl ProgressBars for ProgressOptions {
59    type P = RusticProgress;
60
61    fn progress_spinner(&self, prefix: impl Into<Cow<'static, str>>) -> RusticProgress {
62        if self.no_progress {
63            return Self::no_progress();
64        }
65        let p = ProgressBar::new(0).with_style(
66            ProgressStyle::default_bar()
67                .template("[{elapsed_precise}] {prefix:30} {spinner}")
68                .unwrap(),
69        );
70        p.set_prefix(prefix);
71        p.enable_steady_tick(self.progress_interval());
72        RusticProgress(p, ProgressType::Spinner)
73    }
74
75    fn progress_counter(&self, prefix: impl Into<Cow<'static, str>>) -> RusticProgress {
76        if self.no_progress {
77            return Self::no_progress();
78        }
79        let p = ProgressBar::new(0).with_style(
80            ProgressStyle::default_bar()
81                .template("[{elapsed_precise}] {prefix:30} {bar:40.cyan/blue} {pos:>10}")
82                .unwrap(),
83        );
84        p.set_prefix(prefix);
85        p.enable_steady_tick(self.progress_interval());
86        RusticProgress(p, ProgressType::Counter)
87    }
88
89    fn progress_hidden(&self) -> RusticProgress {
90        Self::no_progress()
91    }
92
93    fn progress_bytes(&self, prefix: impl Into<Cow<'static, str>>) -> RusticProgress {
94        if self.no_progress {
95            return Self::no_progress();
96        }
97        let p = ProgressBar::new(0).with_style(
98            ProgressStyle::default_bar()
99            .template("[{elapsed_precise}] {prefix:30} {bar:40.cyan/blue} {bytes:>10}            {bytes_per_sec:12}")
100            .unwrap()
101            );
102        p.set_prefix(prefix);
103        p.enable_steady_tick(self.progress_interval());
104        RusticProgress(p, ProgressType::Bytes)
105    }
106}
107
108#[derive(Debug, Clone)]
109enum ProgressType {
110    Hidden,
111    Spinner,
112    Counter,
113    Bytes,
114}
115
116/// A default progress bar
117#[derive(Debug, Clone)]
118pub struct RusticProgress(ProgressBar, ProgressType);
119
120#[allow(clippy::literal_string_with_formatting_args)]
121impl Progress for RusticProgress {
122    fn is_hidden(&self) -> bool {
123        self.0.is_hidden()
124    }
125
126    fn set_length(&self, len: u64) {
127        match self.1 {
128            ProgressType::Counter => {
129                self.0.set_style(
130                    ProgressStyle::default_bar()
131                        .template(
132                            "[{elapsed_precise}] {prefix:30} {bar:40.cyan/blue} {pos:>10}/{len:10}",
133                        )
134                        .unwrap(),
135                );
136            }
137            ProgressType::Bytes => {
138                self.0.set_style(
139                    ProgressStyle::default_bar()
140                        .with_key("my_eta", |s: &ProgressState, w: &mut dyn Write| {
141                            let _ = match (s.pos(), s.len()){
142                                // Extra checks to prevent panics from dividing by zero or subtract overflow
143                                (pos,Some(len)) if pos != 0 && len > pos => write!(w,"{:#}", HumanDuration(Duration::from_secs(s.elapsed().as_secs() * (len-pos)/pos))),
144                                (_, _) => write!(w,"-"),
145                            };
146                        })
147                        .template("[{elapsed_precise}] {prefix:30} {bar:40.cyan/blue} {bytes:>10}/{total_bytes:10} {bytes_per_sec:12} (ETA {my_eta})")
148                        .unwrap()
149                );
150            }
151            _ => {}
152        }
153        self.0.set_length(len);
154    }
155
156    fn set_title(&self, title: &'static str) {
157        self.0.set_prefix(title);
158    }
159
160    fn inc(&self, inc: u64) {
161        self.0.inc(inc);
162    }
163
164    fn finish(&self) {
165        self.0.finish_with_message("done");
166    }
167}