fix: hanging TUI on queueing downloads

This commit is contained in:
Michel de Graaf
2025-11-19 09:13:03 +01:00
parent 9f174c4c5e
commit f5b65053e2
2 changed files with 77 additions and 50 deletions
+10
View File
@@ -370,6 +370,16 @@ impl Client {
.unwrap_or_default() .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<Vec<SearchResult>> {
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<String, Search> { pub fn get_all_searches(&self) -> HashMap<String, Search> {
self.context.read().unwrap().searches.clone() self.context.read().unwrap().searches.clone()
} }
+67 -50
View File
@@ -710,44 +710,63 @@ impl MainTui {
let timeout = self.search_timeout; let timeout = self.search_timeout;
let selected_search_index = self.state.selected_search_index; let selected_search_index = self.state.selected_search_index;
for (idx, search) in self.state.searches.iter_mut().enumerate() { // Collect all queries first
// Poll for new results let queries: Vec<(usize, String)> = self
let search_query = search.query.clone(); .state
let search_results = self.client.get_search_results(&search_query); .searches
.iter()
.enumerate()
.map(|(idx, s)| (idx, s.query.clone()))
.collect();
if !search_results.is_empty() { // Fetch all results in one go (single lock acquisition per query)
search.results.clear(); // Use try_get_search_results to avoid blocking the UI thread
for result in search_results { let all_results: Vec<(usize, Vec<_>)> = queries
for file in result.files { .into_iter()
search.results.push(FileDisplayData { .filter_map(|(idx, query)| {
filename: file.name.clone(), self.client
size: file.size, .try_get_search_results(&query)
username: result.username.clone(), .map(|results| (idx, results))
speed: result.speed, })
slots: result.slots, .collect();
bitrate: file.attribs.get(&0).copied(),
}); // 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 // Mark as completed after timeout
if let Some(selected_idx) = selected_search_index { if search.status == SearchStatus::Active
if selected_idx == idx { && search.start_time.elapsed() > timeout
self.state.results_items = search.results.clone(); {
self.state.results_filtered_items = search.status = SearchStatus::Completed;
search.results.clone();
self.state.results_filtered_indices =
(0..search.results.len()).collect();
}
} }
} }
// 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(); let sender = self.state.downloads_sender_channel.clone().unwrap();
thread::spawn(move || { thread::spawn(move || {
thread::spawn(move || { for (filename, username, size) in selected_files.into_iter() {
for (filename, username, size) in selected_files.into_iter() { match client.download(
match client.download( filename.clone(),
filename.clone(), username.clone(),
username.clone(), size,
size, download_dir.clone(),
download_dir.clone(), ) {
) { Ok((download, rx)) => {
Ok((download, rx)) => { let _ = sender.send((download, rx));
let _ = sender.send((download, rx)); }
} Err(e) => {
Err(e) => { eprintln!(
eprintln!( "Failed to start download for {}: {}",
"Failed to start download for {}: {}", filename, e
filename, e );
);
}
} }
} }
}); }
}); });
// Clear selection // Clear selection