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()
}
/// 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> {
self.context.read().unwrap().searches.clone()
}
+67 -50
View File
@@ -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