rustic_rs/commands/tui/
progress.rs1use std::io::Stdout;
2use std::sync::{Arc, RwLock};
3use std::time::{Duration, SystemTime};
4
5use bytesize::ByteSize;
6use ratatui::{Terminal, backend::CrosstermBackend};
7use rustic_core::{Progress, ProgressBars};
8
9use super::widgets::{Draw, popup_gauge, popup_text};
10
11#[derive(Clone)]
12pub struct TuiProgressBars {
13 pub terminal: Arc<RwLock<Terminal<CrosstermBackend<Stdout>>>>,
14}
15
16impl TuiProgressBars {
17 fn as_progress(&self, progress_type: TuiProgressType, prefix: String) -> TuiProgress {
18 TuiProgress {
19 terminal: self.terminal.clone(),
20 data: Arc::new(RwLock::new(CounterData::new(prefix))),
21 progress_type,
22 }
23 }
24}
25
26impl ProgressBars for TuiProgressBars {
27 type P = TuiProgress;
28 fn progress_hidden(&self) -> Self::P {
29 self.as_progress(TuiProgressType::Hidden, String::new())
30 }
31 fn progress_spinner(&self, prefix: impl Into<std::borrow::Cow<'static, str>>) -> Self::P {
32 let progress = self.as_progress(TuiProgressType::Spinner, String::from(prefix.into()));
33 progress.popup();
34 progress
35 }
36 fn progress_counter(&self, prefix: impl Into<std::borrow::Cow<'static, str>>) -> Self::P {
37 let progress = self.as_progress(TuiProgressType::Counter, String::from(prefix.into()));
38 progress.popup();
39 progress
40 }
41 fn progress_bytes(&self, prefix: impl Into<std::borrow::Cow<'static, str>>) -> Self::P {
42 let progress = self.as_progress(TuiProgressType::Bytes, String::from(prefix.into()));
43 progress.popup();
44 progress
45 }
46}
47
48struct CounterData {
49 prefix: String,
50 begin: SystemTime,
51 length: Option<u64>,
52 count: u64,
53}
54
55impl CounterData {
56 fn new(prefix: String) -> Self {
57 Self {
58 prefix,
59 begin: SystemTime::now(),
60 length: None,
61 count: 0,
62 }
63 }
64}
65
66#[derive(Clone)]
67enum TuiProgressType {
68 Hidden,
69 Spinner,
70 Counter,
71 Bytes,
72}
73
74#[derive(Clone)]
75pub struct TuiProgress {
76 terminal: Arc<RwLock<Terminal<CrosstermBackend<Stdout>>>>,
77 data: Arc<RwLock<CounterData>>,
78 progress_type: TuiProgressType,
79}
80
81fn fmt_duration(d: Duration) -> String {
82 let seconds = d.as_secs();
83 let (minutes, seconds) = (seconds / 60, seconds % 60);
84 let (hours, minutes) = (minutes / 60, minutes % 60);
85 format!("[{hours:02}:{minutes:02}:{seconds:02}]")
86}
87
88impl TuiProgress {
89 fn popup(&self) {
90 let data = self.data.read().unwrap();
91 let elapsed = data.begin.elapsed().unwrap();
92 let length = data.length;
93 let count = data.count;
94 let ratio = match length {
95 None | Some(0) => 0.0,
96 Some(l) => count as f64 / l as f64,
97 };
98 let eta = match ratio {
99 r if r < 0.01 => " ETA: -".to_string(),
100 r if r > 0.999_999 => String::new(),
101 r => {
102 format!(
103 " ETA: {}",
104 fmt_duration(Duration::from_secs(1) + elapsed.div_f64(r / (1.0 - r)))
105 )
106 }
107 };
108 let prefix = &data.prefix;
109 let message = match self.progress_type {
110 TuiProgressType::Spinner => {
111 format!("{} {prefix}", fmt_duration(elapsed))
112 }
113 TuiProgressType::Counter => {
114 format!(
115 "{} {prefix} {}{}{eta}",
116 fmt_duration(elapsed),
117 count,
118 length.map_or(String::new(), |l| format!("/{l}"))
119 )
120 }
121 TuiProgressType::Bytes => {
122 format!(
123 "{} {prefix} {}{}{eta}",
124 fmt_duration(elapsed),
125 ByteSize(count).display(),
126 length.map_or(String::new(), |l| format!("/{}", ByteSize(l).display()))
127 )
128 }
129 TuiProgressType::Hidden => String::new(),
130 };
131 drop(data);
132
133 let mut terminal = self.terminal.write().unwrap();
134 _ = terminal
135 .draw(|f| {
136 let area = f.area();
137 match self.progress_type {
138 TuiProgressType::Hidden => {}
139 TuiProgressType::Spinner => {
140 let mut popup = popup_text("progress", message.into());
141 popup.draw(area, f);
142 }
143 TuiProgressType::Counter | TuiProgressType::Bytes => {
144 let mut popup = popup_gauge("progress", message.into(), ratio);
145 popup.draw(area, f);
146 }
147 }
148 })
149 .unwrap();
150 }
151}
152
153impl Progress for TuiProgress {
154 fn is_hidden(&self) -> bool {
155 matches!(self.progress_type, TuiProgressType::Hidden)
156 }
157 fn set_length(&self, len: u64) {
158 self.data.write().unwrap().length = Some(len);
159 self.popup();
160 }
161 fn set_title(&self, title: &'static str) {
162 self.data.write().unwrap().prefix = String::from(title);
163 self.popup();
164 }
165
166 fn inc(&self, inc: u64) {
167 self.data.write().unwrap().count += inc;
168 self.popup();
169 }
170 fn finish(&self) {}
171}