shadow_rs/utility/
status_bar.rs1use std::io::Write;
2use std::sync::atomic::AtomicBool;
3use std::sync::{Arc, RwLock};
4use std::time::Duration;
5
6const SAVE_CURSOR: &str = "\u{1B}[s";
7const RESTORE_CURSOR: &str = "\u{1B}[u";
8const NEXT_LINE: &str = "\u{1B}[1E";
9const PREV_LINE: &str = "\u{1B}[1F";
10const CLEAR: &str = "\u{1B}[K";
11const RESTORE_SCROLL_REGION: &str = "\u{1B}[r";
12const LAST_LINE: &str = "\u{1B}[9999H";
13
14pub trait StatusBarState: std::fmt::Display + std::marker::Send + std::marker::Sync {}
15impl<T> StatusBarState for T where T: std::fmt::Display + std::marker::Send + std::marker::Sync {}
16
17pub struct StatusBar<T: 'static + StatusBarState> {
18 state: Arc<Status<T>>,
19 stop_flag: Arc<AtomicBool>,
20 thread: Option<std::thread::JoinHandle<()>>,
21}
22
23impl<T: 'static + StatusBarState> StatusBar<T> {
24 pub fn new(state: T, redraw_interval: Duration) -> Self {
26 let state = Arc::new(Status::new(state));
27 let stop_flag = Arc::new(AtomicBool::new(false));
28
29 Self {
30 state: Arc::clone(&state),
31 stop_flag: Arc::clone(&stop_flag),
32 thread: Some(std::thread::spawn(move || {
33 Self::redraw_loop(state, stop_flag, redraw_interval);
34 })),
35 }
36 }
37
38 fn redraw_loop(state: Arc<Status<T>>, stop_flag: Arc<AtomicBool>, redraw_interval: Duration) {
39 while !stop_flag.load(std::sync::atomic::Ordering::Acquire) {
42 let rows = match tiocgwinsz() {
44 Ok(x) => x.ws_row,
45 Err(e) => {
46 log::error!("Status bar ioctl failed ({e}). Stopping the status bar.");
47 break;
48 }
49 };
50
51 if rows > 1 {
52 #[rustfmt::skip]
53 let to_print = [
54 SAVE_CURSOR, RESTORE_SCROLL_REGION, RESTORE_CURSOR,
58 SAVE_CURSOR, "\n", RESTORE_CURSOR,
60 NEXT_LINE, PREV_LINE,
63 SAVE_CURSOR,
65 &format!("\u{1B}[1;{}r", rows - 1),
67 LAST_LINE, &format!("{}", *state.inner.read().unwrap()), CLEAR,
69 RESTORE_CURSOR,
71 ]
72 .join("");
73
74 std::io::stderr().write_all(to_print.as_bytes()).unwrap();
78 let _ = std::io::stderr().flush();
79 }
80 std::thread::sleep(redraw_interval);
81 }
82
83 let to_print =
84 format!("{SAVE_CURSOR}{LAST_LINE}{CLEAR}{RESTORE_SCROLL_REGION}{RESTORE_CURSOR}");
85
86 std::io::stderr().write_all(to_print.as_bytes()).unwrap();
87 let _ = std::io::stderr().flush();
88 }
89
90 pub fn stop(self) {
92 }
94
95 pub fn status(&self) -> &Arc<Status<T>> {
96 &self.state
97 }
98}
99
100impl<T: 'static + StatusBarState> std::ops::Drop for StatusBar<T> {
101 fn drop(&mut self) {
102 self.stop_flag
103 .swap(true, std::sync::atomic::Ordering::Relaxed);
104 if let Some(handle) = self.thread.take() {
105 if let Err(e) = handle.join() {
106 log::warn!("Progress bar thread did not exit cleanly: {e:?}");
107 }
108 }
109 }
110}
111
112pub struct StatusPrinter<T: 'static + StatusBarState> {
113 state: Arc<Status<T>>,
114 stop_sender: Option<std::sync::mpsc::Sender<()>>,
115 thread: Option<std::thread::JoinHandle<()>>,
116}
117
118impl<T: 'static + StatusBarState> StatusPrinter<T> {
119 pub fn new(state: T) -> Self {
121 let state = Arc::new(Status::new(state));
122 let (stop_sender, stop_receiver) = std::sync::mpsc::channel();
123
124 Self {
125 state: Arc::clone(&state),
126 stop_sender: Some(stop_sender),
127 thread: Some(std::thread::spawn(move || {
128 Self::print_loop(state, stop_receiver);
129 })),
130 }
131 }
132
133 fn print_loop(state: Arc<Status<T>>, stop_receiver: std::sync::mpsc::Receiver<()>) {
134 let print_interval = Duration::from_secs(60);
135
136 loop {
137 match stop_receiver.recv_timeout(print_interval) {
138 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
140 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
141 Ok(()) => unreachable!(),
142 }
143
144 let to_write = format!("Progress: {}\n", *state.inner.read().unwrap());
148 std::io::stderr().write_all(to_write.as_bytes()).unwrap();
149 let _ = std::io::stderr().flush();
150 }
151 }
152
153 pub fn stop(self) {
155 }
157
158 pub fn status(&self) -> &Arc<Status<T>> {
159 &self.state
160 }
161}
162
163impl<T: 'static + StatusBarState> std::ops::Drop for StatusPrinter<T> {
164 fn drop(&mut self) {
165 self.stop_sender.take();
167 if let Some(handle) = self.thread.take() {
168 if let Err(e) = handle.join() {
169 log::warn!("Progress thread did not exit cleanly: {e:?}");
170 }
171 }
172 }
173}
174
175#[derive(Debug)]
177pub struct Status<T> {
178 inner: RwLock<T>,
181}
182
183impl<T> Status<T> {
184 fn new(inner: T) -> Self {
185 Self {
186 inner: RwLock::new(inner),
187 }
188 }
189
190 pub fn update(&self, f: impl FnOnce(&mut T)) {
193 f(&mut *self.inner.write().unwrap())
194 }
195}
196
197nix::ioctl_read_bad!(_tiocgwinsz, libc::TIOCGWINSZ, libc::winsize);
198
199fn tiocgwinsz() -> nix::Result<libc::winsize> {
200 let mut win_size: libc::winsize = unsafe { std::mem::zeroed() };
201 unsafe { _tiocgwinsz(0, &mut win_size)? };
202 Ok(win_size)
203}