fix: hanging TUI on queueing downloads
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user