use super::spinner::{Spinner, SpinnerStyle};
use crate::style::Style;
use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
pub struct Status {
message: Arc<Mutex<String>>,
spinner_style: SpinnerStyle,
running: Arc<AtomicBool>,
thread_handle: Option<thread::JoinHandle<()>>,
}
impl Status {
pub fn new(message: &str) -> Self {
Status {
message: Arc::new(Mutex::new(message.to_string())),
spinner_style: SpinnerStyle::Dots,
running: Arc::new(AtomicBool::new(false)),
thread_handle: None,
}
}
pub fn spinner_style(mut self, style: SpinnerStyle) -> Self {
self.spinner_style = style;
self
}
pub fn style(self, _style: Style) -> Self {
self
}
pub fn start(&mut self) {
if self.running.load(Ordering::SeqCst) {
return;
}
self.running.store(true, Ordering::SeqCst);
let running = self.running.clone();
let message = self.message.clone();
let spinner_style = self.spinner_style;
self.thread_handle = Some(thread::spawn(move || {
let spinner = Spinner::new("").style(spinner_style);
while running.load(Ordering::SeqCst) {
let frame = spinner.current_frame();
let msg = message.lock().unwrap().clone();
print!("\r\x1B[K{} {}", frame, msg);
let _ = io::stdout().flush();
thread::sleep(Duration::from_millis(spinner_style.interval_ms()));
}
print!("\r\x1B[K");
let _ = io::stdout().flush();
}));
}
pub fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(handle) = self.thread_handle.take() {
let _ = handle.join();
}
}
pub fn update(&mut self, message: &str) {
*self.message.lock().unwrap() = message.to_string();
}
}
impl Drop for Status {
fn drop(&mut self) {
self.stop();
}
}
pub struct StatusGuard {
status: Status,
}
impl StatusGuard {
pub fn new(message: &str) -> Self {
let mut status = Status::new(message);
status.start();
StatusGuard { status }
}
#[allow(dead_code)]
pub fn with_style(message: &str, spinner_style: SpinnerStyle) -> Self {
let mut status = Status::new(message).spinner_style(spinner_style);
status.start();
StatusGuard { status }
}
#[allow(dead_code)]
pub fn update(&mut self, message: &str) {
self.status.update(message);
}
}
impl Drop for StatusGuard {
fn drop(&mut self) {
self.status.stop();
}
}
pub fn with_status<T, F: FnOnce() -> T>(message: &str, f: F) -> T {
let _guard = StatusGuard::new(message);
f()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_new() {
let status = Status::new("Testing...");
assert!(!status.running.load(Ordering::SeqCst));
}
#[test]
fn test_status_guard_drop() {
{
let _guard = StatusGuard::new("Test");
thread::sleep(Duration::from_millis(50));
}
}
}