rustic_rs/config/
progress_options.rs1use 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#[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 #[clap(long, global = true, env = "RUSTIC_NO_PROGRESS")]
28 #[merge(strategy=conflate::bool::overwrite_false)]
29 pub no_progress: bool,
30
31 #[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 fn progress_interval(&self) -> Duration {
47 self.progress_interval
48 .map_or(constants::DEFAULT_INTERVAL, |i| *i)
49 }
50
51 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#[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 (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}