use std::{ collections::HashSet, io::Write, path::Path, sync::{ atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering}, mpsc::{self, RecvTimeoutError, Sender}, Arc, RwLock, }, thread::JoinHandle, time::Duration, }; 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 { fn set(&self, val: T); fn get(&self) -> T; } #[derive(Default)] pub struct ThreadStringWrapper { inner: RwLock, } impl ThreadSyncTrait 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: bool, pub file_not_found: HashSet, pub unknown_error: HashSet, } /* -------------------------------------------------------------------------- */ fn format_preparing_str( prog_char: char, data: &PAtomicInfo, is_iso: bool, is_display_kb: bool, ) -> String { let path_in = data.current_path.get(); let size = human_readable_number(data.total_file_size.load(ORDERING), is_iso, is_display_kb); format!("Preparing: {path_in} {size} ... {prog_char}") } fn format_indexing_str( prog_char: char, data: &PAtomicInfo, is_iso: bool, display_kb: bool, ) -> 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), is_iso, display_kb); 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, } impl PIndicator { pub fn build_me() -> Self { Self { thread: None, data: Arc::new(PAtomicInfo { ..Default::default() }), } } pub fn spawn(&mut self, is_iso: bool, display_kb: bool) { 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 stdout = std::io::stdout(); 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 print!("\r{:width$}", " ", width = msg.len()); let prog_char = PROGRESS_CHARS[progress_char_i]; msg = match data.state.load(ORDERING) { Operation::INDEXING => { format_indexing_str(prog_char, &data, is_iso, display_kb) } Operation::PREPARING => { format_preparing_str(prog_char, &data, is_iso, display_kb) } _ => panic!("Unknown State"), }; write!(stdout, "\r{msg}").unwrap(); stdout.flush().unwrap(); progress_char_i += 1; progress_char_i %= PROGRESS_CHARS_LEN; } print!("\r{:width$}", " ", width = msg.len()); print!("\r"); stdout.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(); } } }