mirror of
https://github.com/bootandy/dust.git
synced 2026-01-12 04:56:05 -08:00
Progress spinner and status line are written to stderr instead of stdout. No longer any need to detect if stdout is being redirected.
162 lines
5.0 KiB
Rust
162 lines
5.0 KiB
Rust
use std::{
|
|
collections::HashSet,
|
|
io::Write,
|
|
path::Path,
|
|
sync::{
|
|
Arc, RwLock,
|
|
atomic::{AtomicU8, AtomicUsize, Ordering},
|
|
mpsc::{self, RecvTimeoutError, Sender},
|
|
},
|
|
thread::JoinHandle,
|
|
time::Duration,
|
|
};
|
|
|
|
#[cfg(not(target_has_atomic = "64"))]
|
|
use portable_atomic::AtomicU64;
|
|
#[cfg(target_has_atomic = "64")]
|
|
use std::sync::atomic::AtomicU64;
|
|
|
|
use crate::display::human_readable_number;
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
pub const ORDERING: Ordering = Ordering::Relaxed;
|
|
|
|
const SPINNER_SLEEP_TIME: u64 = 100;
|
|
const PROGRESS_CHARS: [char; 4] = ['-', '\\', '|', '/'];
|
|
const PROGRESS_CHARS_LEN: usize = PROGRESS_CHARS.len();
|
|
|
|
pub trait ThreadSyncTrait<T> {
|
|
fn set(&self, val: T);
|
|
fn get(&self) -> T;
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ThreadStringWrapper {
|
|
inner: RwLock<String>,
|
|
}
|
|
|
|
impl ThreadSyncTrait<String> for ThreadStringWrapper {
|
|
fn set(&self, val: String) {
|
|
*self.inner.write().unwrap() = val;
|
|
}
|
|
|
|
fn get(&self) -> String {
|
|
(*self.inner.read().unwrap()).clone()
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
// creating an enum this way allows to have simpler syntax compared to a Mutex or a RwLock
|
|
#[allow(non_snake_case)]
|
|
pub mod Operation {
|
|
pub const INDEXING: u8 = 0;
|
|
pub const PREPARING: u8 = 1;
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct PAtomicInfo {
|
|
pub num_files: AtomicUsize,
|
|
pub total_file_size: AtomicU64,
|
|
pub state: AtomicU8,
|
|
pub current_path: ThreadStringWrapper,
|
|
}
|
|
|
|
impl PAtomicInfo {
|
|
pub fn clear_state(&self, dir: &Path) {
|
|
self.state.store(Operation::INDEXING, ORDERING);
|
|
let dir_name = dir.to_string_lossy().to_string();
|
|
self.current_path.set(dir_name);
|
|
self.total_file_size.store(0, ORDERING);
|
|
self.num_files.store(0, ORDERING);
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct RuntimeErrors {
|
|
pub no_permissions: HashSet<String>,
|
|
pub file_not_found: HashSet<String>,
|
|
pub unknown_error: HashSet<String>,
|
|
pub interrupted_error: i32,
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
fn format_preparing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
|
|
let path_in = data.current_path.get();
|
|
let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
|
|
format!("Preparing: {path_in} {size} ... {prog_char}")
|
|
}
|
|
|
|
fn format_indexing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
|
|
let path_in = data.current_path.get();
|
|
let file_count = data.num_files.load(ORDERING);
|
|
let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
|
|
let file_str = format!("{file_count} files, {size}");
|
|
format!("Indexing: {path_in} {file_str} ... {prog_char}")
|
|
}
|
|
|
|
pub struct PIndicator {
|
|
pub thread: Option<(Sender<()>, JoinHandle<()>)>,
|
|
pub data: Arc<PAtomicInfo>,
|
|
}
|
|
|
|
impl PIndicator {
|
|
pub fn build_me() -> Self {
|
|
Self {
|
|
thread: None,
|
|
data: Arc::new(PAtomicInfo {
|
|
..Default::default()
|
|
}),
|
|
}
|
|
}
|
|
|
|
pub fn spawn(&mut self, output_display: String) {
|
|
let data = self.data.clone();
|
|
let (stop_handler, receiver) = mpsc::channel::<()>();
|
|
|
|
let time_info_thread = std::thread::spawn(move || {
|
|
let mut progress_char_i: usize = 0;
|
|
let mut stderr = std::io::stderr();
|
|
let mut msg = "".to_string();
|
|
|
|
// While the timeout triggers we go round the loop
|
|
// If we disconnect or the sender sends its message we exit the while loop
|
|
while let Err(RecvTimeoutError::Timeout) =
|
|
receiver.recv_timeout(Duration::from_millis(SPINNER_SLEEP_TIME))
|
|
{
|
|
// Clear the text written by 'write!'& Return at the start of line
|
|
let clear = format!("\r{:width$}", " ", width = msg.len());
|
|
write!(stderr, "{clear}").unwrap();
|
|
let prog_char = PROGRESS_CHARS[progress_char_i];
|
|
|
|
msg = match data.state.load(ORDERING) {
|
|
Operation::INDEXING => format_indexing_str(prog_char, &data, &output_display),
|
|
Operation::PREPARING => format_preparing_str(prog_char, &data, &output_display),
|
|
_ => panic!("Unknown State"),
|
|
};
|
|
|
|
write!(stderr, "\r{msg}").unwrap();
|
|
stderr.flush().unwrap();
|
|
|
|
progress_char_i += 1;
|
|
progress_char_i %= PROGRESS_CHARS_LEN;
|
|
}
|
|
|
|
let clear = format!("\r{:width$}", " ", width = msg.len());
|
|
write!(stderr, "{clear}").unwrap();
|
|
write!(stderr, "\r").unwrap();
|
|
stderr.flush().unwrap();
|
|
});
|
|
self.thread = Some((stop_handler, time_info_thread))
|
|
}
|
|
|
|
pub fn stop(self) {
|
|
if let Some((stop_handler, thread)) = self.thread {
|
|
stop_handler.send(()).unwrap();
|
|
thread.join().unwrap();
|
|
}
|
|
}
|
|
}
|