diff --git a/Cargo.toml b/Cargo.toml index 557c0d7..b3d1664 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-player" -version = "0.1.35" +version = "0.1.36" edition = "2021" [dependencies] diff --git a/src/main.rs b/src/main.rs index 6543f40..0d287a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,10 @@ use ratatui::{backend::CrosstermBackend, Terminal}; use state::{AppState, PlayerState}; use std::io::{self, BufRead, BufReader, Write}; use std::os::unix::net::UnixStream; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; use tracing_subscriber; // UI update intervals and thresholds @@ -677,7 +681,8 @@ fn handle_key_event(terminal: &mut Terminal, st KeyCode::Char('y') | KeyCode::Char('Y') => { state.show_refresh_confirm = false; state.is_refreshing = true; - terminal.draw(|f| { let _ = ui::render(f, state, player); })?; // Show "Refreshing library..." immediately + state.refresh_file_count = 0; + terminal.draw(|f| { let _ = ui::render(f, state, player); })?; let cache_dir = cache::get_cache_dir()?; @@ -685,8 +690,26 @@ fn handle_key_event(terminal: &mut Terminal, st let _ = std::fs::remove_file(cache_dir.join("file_tree.json")); let _ = std::fs::remove_file(cache_dir.join("metadata.json")); - // Perform fresh scan - let new_cache = scanner::scan_paths(&state.config.scan_paths.paths)?; + // Create atomic counter for file count + let counter = Arc::new(AtomicUsize::new(0)); + let counter_clone = Arc::clone(&counter); + + // Spawn background thread to perform scan + let paths = state.config.scan_paths.paths.clone(); + let scan_thread = thread::spawn(move || { + scanner::scan_paths(&paths, &counter_clone) + }); + + // Poll counter and update UI while scanning + while !scan_thread.is_finished() { + state.refresh_file_count = counter.load(Ordering::Relaxed); + terminal.draw(|f| { let _ = ui::render(f, state, player); })?; + thread::sleep(Duration::from_millis(100)); + } + + // Get the result + let new_cache = scan_thread.join().map_err(|_| anyhow::anyhow!("Scan thread panicked"))??; + new_cache.save(&cache_dir)?; // Replace old cache completely @@ -701,6 +724,7 @@ fn handle_key_event(terminal: &mut Terminal, st } state.is_refreshing = false; + state.refresh_file_count = 0; } KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => { state.show_refresh_confirm = false; diff --git a/src/scanner/mod.rs b/src/scanner/mod.rs index 5769461..6647ffa 100644 --- a/src/scanner/mod.rs +++ b/src/scanner/mod.rs @@ -1,6 +1,7 @@ use crate::cache::{Cache, FileMetadata, FileTreeNode}; use anyhow::Result; use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; use walkdir::WalkDir; const AUDIO_EXTENSIONS: &[&str] = &["mp3", "flac", "wav", "ogg", "m4a", "aac", "opus", "wma"]; @@ -33,7 +34,7 @@ pub fn is_video_file(path: &Path) -> bool { } } -pub fn scan_directory(root_path: &Path) -> Result { +fn scan_directory_internal(root_path: &Path, counter: &AtomicUsize) -> Result { let name = root_path .file_name() .unwrap_or_default() @@ -62,13 +63,16 @@ pub fn scan_directory(root_path: &Path) -> Result { if entry.file_type().is_dir() { // Recursively scan subdirectories - if let Ok(child_node) = scan_directory(path) { + if let Ok(child_node) = scan_directory_internal(path, counter) { // Only add directory if it contains media files or non-empty subdirectories if !child_node.children.is_empty() { node.children.push(child_node); } } } else if is_media_file(path) { + // Increment counter for each media file found + counter.fetch_add(1, Ordering::Relaxed); + // Add media file let file_name = path .file_name() @@ -100,13 +104,13 @@ pub fn scan_directory(root_path: &Path) -> Result { Ok(node) } -pub fn scan_paths(paths: &[PathBuf]) -> Result { +pub fn scan_paths(paths: &[PathBuf], counter: &AtomicUsize) -> Result { let mut cache = Cache::new(); for path in paths { if path.exists() { tracing::info!("Scanning path: {:?}", path); - let tree_node = scan_directory(path)?; + let tree_node = scan_directory_internal(path, counter)?; // Collect all metadata from the tree collect_metadata(&tree_node, &mut cache); diff --git a/src/state/mod.rs b/src/state/mod.rs index 9983353..bfab6d4 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -89,6 +89,7 @@ pub struct AppState { pub context_menu: Option, pub play_mode: PlayMode, pub last_error: Option, + pub refresh_file_count: usize, } #[derive(Debug, Clone)] @@ -143,6 +144,7 @@ impl AppState { context_menu: None, play_mode: PlayMode::Normal, last_error: None, + refresh_file_count: 0, } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 074a470..7b6c00d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -79,7 +79,7 @@ pub fn render(frame: &mut Frame, state: &mut AppState, player: &mut Player) -> ( // Show refreshing popup if scanning if state.is_refreshing { - render_info_popup(frame, "Refreshing library..."); + render_refresh_popup(frame, state.refresh_file_count); } // Show confirmation popup if needed @@ -755,6 +755,56 @@ fn render_info_popup(frame: &mut Frame, message: &str) { frame.render_widget(message_widget, inner); } +fn render_refresh_popup(frame: &mut Frame, file_count: usize) { + // Create centered popup area - bigger for two lines + let area = frame.area(); + let popup_width = 50; + let popup_height = 5; + + let popup_area = Rect { + x: (area.width.saturating_sub(popup_width)) / 2, + y: (area.height.saturating_sub(popup_height)) / 2, + width: popup_width.min(area.width), + height: popup_height.min(area.height), + }; + + // Use Clear widget to completely erase the background + frame.render_widget(Clear, popup_area); + + // Render the popup block with solid background + let block = Block::default() + .borders(Borders::ALL) + .style(Style::default() + .bg(Theme::background()) + .fg(Theme::bright_foreground())); + + let inner = block.inner(popup_area); + frame.render_widget(block, popup_area); + + // Build two-line message + let lines = if file_count > 0 { + vec![ + Line::from("Refreshing library..."), + Line::from(""), + Line::from(format!("{} files found", file_count)) + .style(Style::default().fg(Theme::highlight())), + ] + } else { + vec![ + Line::from("Refreshing library..."), + ] + }; + + // Render message centered + let message_widget = Paragraph::new(lines) + .alignment(Alignment::Center) + .style(Style::default() + .fg(Theme::bright_foreground()) + .bg(Theme::background())); + + frame.render_widget(message_widget, inner); +} + fn render_confirm_popup(frame: &mut Frame, title: &str, message: &str) { // Create centered popup area let area = frame.area();