use std::io::Write;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock};
use std::time::Duration;
const SAVE_CURSOR: &str = "\u{1B}[s";
const RESTORE_CURSOR: &str = "\u{1B}[u";
const NEXT_LINE: &str = "\u{1B}[1E";
const PREV_LINE: &str = "\u{1B}[1F";
const CLEAR: &str = "\u{1B}[K";
const RESTORE_SCROLL_REGION: &str = "\u{1B}[r";
const LAST_LINE: &str = "\u{1B}[9999H";
pub trait StatusBarState: std::fmt::Display + std::marker::Send + std::marker::Sync {}
impl<T> StatusBarState for T where T: std::fmt::Display + std::marker::Send + std::marker::Sync {}
pub struct StatusBar<T: 'static + StatusBarState> {
state: Arc<Status<T>>,
stop_flag: Arc<AtomicBool>,
thread: Option<std::thread::JoinHandle<()>>,
}
impl<T: 'static + StatusBarState> StatusBar<T> {
pub fn new(state: T, redraw_interval: Duration) -> Self {
let state = Arc::new(Status::new(state));
let stop_flag = Arc::new(AtomicBool::new(false));
Self {
state: Arc::clone(&state),
stop_flag: Arc::clone(&stop_flag),
thread: Some(std::thread::spawn(move || {
Self::redraw_loop(state, stop_flag, redraw_interval);
})),
}
}
fn redraw_loop(state: Arc<Status<T>>, stop_flag: Arc<AtomicBool>, redraw_interval: Duration) {
while !stop_flag.load(std::sync::atomic::Ordering::Acquire) {
let rows = match tiocgwinsz() {
Ok(x) => x.ws_row,
Err(e) => {
log::error!("Status bar ioctl failed ({}). Stopping the status bar.", e);
break;
}
};
if rows > 1 {
#[rustfmt::skip]
let to_print = [
SAVE_CURSOR, RESTORE_SCROLL_REGION, RESTORE_CURSOR,
SAVE_CURSOR, "\n", RESTORE_CURSOR,
NEXT_LINE, PREV_LINE,
SAVE_CURSOR,
&format!("\u{1B}[1;{}r", rows - 1),
LAST_LINE, &format!("{}", *state.inner.read().unwrap()), CLEAR,
RESTORE_CURSOR,
]
.join("");
std::io::stderr().write_all(to_print.as_bytes()).unwrap();
let _ = std::io::stderr().flush();
}
std::thread::sleep(redraw_interval);
}
let to_print = format!(
"{save_cursor}{last_line}{clear}{restore_scroll_region}{restore_cursor}",
save_cursor = SAVE_CURSOR,
last_line = LAST_LINE,
clear = CLEAR,
restore_scroll_region = RESTORE_SCROLL_REGION,
restore_cursor = RESTORE_CURSOR,
);
std::io::stderr().write_all(to_print.as_bytes()).unwrap();
let _ = std::io::stderr().flush();
}
pub fn stop(self) {
}
pub fn status(&self) -> &Arc<Status<T>> {
&self.state
}
}
impl<T: 'static + StatusBarState> std::ops::Drop for StatusBar<T> {
fn drop(&mut self) {
self.stop_flag
.swap(true, std::sync::atomic::Ordering::Relaxed);
if let Some(handle) = self.thread.take() {
if let Err(e) = handle.join() {
log::warn!("Progress bar thread did not exit cleanly: {:?}", e);
}
}
}
}
pub struct StatusPrinter<T: 'static + StatusBarState> {
state: Arc<Status<T>>,
stop_sender: Option<std::sync::mpsc::Sender<()>>,
thread: Option<std::thread::JoinHandle<()>>,
}
impl<T: 'static + StatusBarState> StatusPrinter<T> {
pub fn new(state: T) -> Self {
let state = Arc::new(Status::new(state));
let (stop_sender, stop_receiver) = std::sync::mpsc::channel();
Self {
state: Arc::clone(&state),
stop_sender: Some(stop_sender),
thread: Some(std::thread::spawn(move || {
Self::print_loop(state, stop_receiver);
})),
}
}
fn print_loop(state: Arc<Status<T>>, stop_receiver: std::sync::mpsc::Receiver<()>) {
let print_interval = Duration::from_secs(60);
loop {
match stop_receiver.recv_timeout(print_interval) {
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
Ok(()) => unreachable!(),
}
let to_write = format!("Progress: {}\n", *state.inner.read().unwrap());
std::io::stderr().write_all(to_write.as_bytes()).unwrap();
let _ = std::io::stderr().flush();
}
}
pub fn stop(self) {
}
pub fn status(&self) -> &Arc<Status<T>> {
&self.state
}
}
impl<T: 'static + StatusBarState> std::ops::Drop for StatusPrinter<T> {
fn drop(&mut self) {
self.stop_sender.take();
if let Some(handle) = self.thread.take() {
if let Err(e) = handle.join() {
log::warn!("Progress thread did not exit cleanly: {:?}", e);
}
}
}
}
#[derive(Debug)]
pub struct Status<T> {
inner: RwLock<T>,
}
impl<T> Status<T> {
fn new(inner: T) -> Self {
Self {
inner: RwLock::new(inner),
}
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
f(&mut *self.inner.write().unwrap())
}
}
nix::ioctl_read_bad!(_tiocgwinsz, libc::TIOCGWINSZ, libc::winsize);
fn tiocgwinsz() -> nix::Result<libc::winsize> {
let mut win_size: libc::winsize = unsafe { std::mem::zeroed() };
unsafe { _tiocgwinsz(0, &mut win_size)? };
Ok(win_size)
}