diff --git a/soulseek-rs-lib/src/client.rs b/soulseek-rs-lib/src/client.rs index 705694b..b079db2 100644 --- a/soulseek-rs-lib/src/client.rs +++ b/soulseek-rs-lib/src/client.rs @@ -370,6 +370,16 @@ impl Client { .unwrap_or_default() } + /// Non-blocking variant that returns None if the lock is unavailable + pub fn try_get_search_results( + &self, + search_key: &str, + ) -> Option> { + self.context.try_read().ok().and_then(|ctx| { + ctx.searches.get(search_key).map(|s| s.results.clone()) + }) + } + pub fn get_all_searches(&self) -> HashMap { self.context.read().unwrap().searches.clone() } diff --git a/soulseek-rs/src/ui/main_tui.rs b/soulseek-rs/src/ui/main_tui.rs index 1fa2e22..2807395 100644 --- a/soulseek-rs/src/ui/main_tui.rs +++ b/soulseek-rs/src/ui/main_tui.rs @@ -710,44 +710,63 @@ impl MainTui { let timeout = self.search_timeout; let selected_search_index = self.state.selected_search_index; - for (idx, search) in self.state.searches.iter_mut().enumerate() { - // Poll for new results - let search_query = search.query.clone(); - let search_results = self.client.get_search_results(&search_query); + // Collect all queries first + let queries: Vec<(usize, String)> = self + .state + .searches + .iter() + .enumerate() + .map(|(idx, s)| (idx, s.query.clone())) + .collect(); - if !search_results.is_empty() { - search.results.clear(); - for result in search_results { - for file in result.files { - search.results.push(FileDisplayData { - filename: file.name.clone(), - size: file.size, - username: result.username.clone(), - speed: result.speed, - slots: result.slots, - bitrate: file.attribs.get(&0).copied(), - }); + // Fetch all results in one go (single lock acquisition per query) + // Use try_get_search_results to avoid blocking the UI thread + let all_results: Vec<(usize, Vec<_>)> = queries + .into_iter() + .filter_map(|(idx, query)| { + self.client + .try_get_search_results(&query) + .map(|results| (idx, results)) + }) + .collect(); + + // Now update state without holding any client locks + for (idx, search_results) in all_results { + if let Some(search) = self.state.searches.get_mut(idx) { + if !search_results.is_empty() { + search.results.clear(); + for result in search_results { + for file in result.files { + search.results.push(FileDisplayData { + filename: file.name.clone(), + size: file.size, + username: result.username.clone(), + speed: result.speed, + slots: result.slots, + bitrate: file.attribs.get(&0).copied(), + }); + } + } + + // Update selected search if this is the active one + if let Some(selected_idx) = selected_search_index { + if selected_idx == idx { + self.state.results_items = search.results.clone(); + self.state.results_filtered_items = + search.results.clone(); + self.state.results_filtered_indices = + (0..search.results.len()).collect(); + } } } - // Update selected search if this is the active one - if let Some(selected_idx) = selected_search_index { - if selected_idx == idx { - self.state.results_items = search.results.clone(); - self.state.results_filtered_items = - search.results.clone(); - self.state.results_filtered_indices = - (0..search.results.len()).collect(); - } + // Mark as completed after timeout + if search.status == SearchStatus::Active + && search.start_time.elapsed() > timeout + { + search.status = SearchStatus::Completed; } } - - // Mark as completed after timeout (but continue updating) - if search.status == SearchStatus::Active - && search.start_time.elapsed() > timeout - { - search.status = SearchStatus::Completed; - } } } @@ -777,26 +796,24 @@ impl MainTui { let sender = self.state.downloads_sender_channel.clone().unwrap(); thread::spawn(move || { - thread::spawn(move || { - for (filename, username, size) in selected_files.into_iter() { - match client.download( - filename.clone(), - username.clone(), - size, - download_dir.clone(), - ) { - Ok((download, rx)) => { - let _ = sender.send((download, rx)); - } - Err(e) => { - eprintln!( - "Failed to start download for {}: {}", - filename, e - ); - } + for (filename, username, size) in selected_files.into_iter() { + match client.download( + filename.clone(), + username.clone(), + size, + download_dir.clone(), + ) { + Ok((download, rx)) => { + let _ = sender.send((download, rx)); + } + Err(e) => { + eprintln!( + "Failed to start download for {}: {}", + filename, e + ); } } - }); + } }); // Clear selection