feat(tui): clear the whole queue from the transfers pane
Shift-C in the Downloads/Uploads pane now cancels every live download, forgets every row — queued, finished or failed — in the session too, and cancels uploads still streaming, so a shared queue empties for every window at once. The existing c keeps clearing only finished transfers.
This commit is contained in:
@@ -89,6 +89,31 @@ impl MainTui {
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty the whole transfer list: every live download is cancelled and
|
||||
/// every row — live or finished — is forgotten by the session too, so a
|
||||
/// shared queue empties for every window rather than only this one.
|
||||
/// Uploads still streaming are cancelled; finished ones are the
|
||||
/// session's history and refresh back from it regardless.
|
||||
pub(super) fn clear_all_transfers(&mut self) {
|
||||
for entry in &self.state.downloads {
|
||||
let download = &entry.download;
|
||||
if !download.is_finished() {
|
||||
self.client
|
||||
.cancel_download(&download.username, &download.filename);
|
||||
}
|
||||
self.client
|
||||
.remove_download(&download.username, &download.filename);
|
||||
}
|
||||
for upload in &self.state.uploads {
|
||||
if upload.status == UploadStatus::InProgress {
|
||||
self.client
|
||||
.cancel_upload(&upload.username, &upload.filename);
|
||||
}
|
||||
}
|
||||
self.state.downloads.clear();
|
||||
self.state.downloads_table_state.select(None);
|
||||
}
|
||||
|
||||
pub(super) fn toggle_selected_download_pause(&self) {
|
||||
let Some(index) = self.state.downloads_table_state.selected() else {
|
||||
return;
|
||||
|
||||
@@ -569,6 +569,9 @@ impl MainTui {
|
||||
KeyCode::Char('c') => {
|
||||
self.clear_finished_downloads();
|
||||
}
|
||||
KeyCode::Char('C') => {
|
||||
self.clear_all_transfers();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +295,9 @@ mod tests {
|
||||
download_dir_set: std::sync::Mutex<Option<String>>,
|
||||
/// What `set_shared_directories` was last asked for, if anything.
|
||||
shares_set: std::sync::Mutex<Option<Vec<String>>>,
|
||||
download_cancelled: std::sync::Mutex<Option<(String, String)>>,
|
||||
upload_cancelled: std::sync::Mutex<Option<(String, String)>>,
|
||||
download_cancelled: std::sync::Mutex<Vec<(String, String)>>,
|
||||
upload_cancelled: std::sync::Mutex<Vec<(String, String)>>,
|
||||
download_removed: std::sync::Mutex<Vec<(String, String)>>,
|
||||
}
|
||||
|
||||
fn queued(username: &str, filename: &str) -> soulseek_rs::types::Download {
|
||||
@@ -449,8 +450,12 @@ mod tests {
|
||||
fn remove_queued_download(&self, _u: &str, _f: &str) -> bool {
|
||||
false
|
||||
}
|
||||
fn remove_download(&self, _u: &str, _f: &str) -> bool {
|
||||
false
|
||||
fn remove_download(&self, u: &str, f: &str) -> bool {
|
||||
self.download_removed
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.push((u.to_string(), f.to_string()));
|
||||
true
|
||||
}
|
||||
fn uploads(&self) -> Vec<soulseek_rs::UploadInfo> {
|
||||
Vec::new()
|
||||
@@ -459,13 +464,17 @@ mod tests {
|
||||
Vec::new()
|
||||
}
|
||||
fn cancel_upload(&self, u: &str, f: &str) -> bool {
|
||||
*self.upload_cancelled.lock().expect("not poisoned") =
|
||||
Some((u.to_string(), f.to_string()));
|
||||
self.upload_cancelled
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.push((u.to_string(), f.to_string()));
|
||||
true
|
||||
}
|
||||
fn cancel_download(&self, u: &str, f: &str) -> bool {
|
||||
*self.download_cancelled.lock().expect("not poisoned") =
|
||||
Some((u.to_string(), f.to_string()));
|
||||
self.download_cancelled
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.push((u.to_string(), f.to_string()));
|
||||
true
|
||||
}
|
||||
fn set_upload_slots(&self, _slots: usize) {}
|
||||
@@ -614,11 +623,70 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
*session.download_cancelled.lock().expect("not poisoned"),
|
||||
Some(("bob".to_string(), "song.mp3".to_string()))
|
||||
vec![("bob".to_string(), "song.mp3".to_string())]
|
||||
);
|
||||
assert!(
|
||||
session
|
||||
.upload_cancelled
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.is_empty(),
|
||||
"the download row cancels no upload"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_c_clears_the_whole_transfer_list() {
|
||||
let session = Arc::new(TalkativeSession::default());
|
||||
let mut tui = attach(session.clone());
|
||||
tui.state.downloads.push(crate::models::DownloadEntry {
|
||||
download: queued("bob", "waiting.mp3"),
|
||||
receiver: None,
|
||||
});
|
||||
tui.state.downloads.push(crate::models::DownloadEntry {
|
||||
download: at_status(
|
||||
"carol",
|
||||
"live.mp3",
|
||||
soulseek_rs::DownloadStatus::InProgress {
|
||||
bytes_downloaded: 1,
|
||||
total_bytes: 4096,
|
||||
speed_bytes_per_sec: 1.0,
|
||||
},
|
||||
),
|
||||
receiver: None,
|
||||
});
|
||||
tui.state.downloads.push(crate::models::DownloadEntry {
|
||||
download: completed("dave", "done.mp3"),
|
||||
receiver: None,
|
||||
});
|
||||
tui.state.uploads.push(uploading("alice", "served.mp3"));
|
||||
tui.state.downloads_table_state.select(Some(0));
|
||||
tui.state.focused_pane = FocusedPane::Downloads;
|
||||
|
||||
tui.handle_key_event(key('C'));
|
||||
|
||||
assert!(tui.state.downloads.is_empty(), "every row goes");
|
||||
assert_eq!(
|
||||
*session.download_cancelled.lock().expect("not poisoned"),
|
||||
vec![
|
||||
("bob".to_string(), "waiting.mp3".to_string()),
|
||||
("carol".to_string(), "live.mp3".to_string()),
|
||||
],
|
||||
"live downloads are cancelled; the finished one is not"
|
||||
);
|
||||
assert_eq!(
|
||||
*session.download_removed.lock().expect("not poisoned"),
|
||||
vec![
|
||||
("bob".to_string(), "waiting.mp3".to_string()),
|
||||
("carol".to_string(), "live.mp3".to_string()),
|
||||
("dave".to_string(), "done.mp3".to_string()),
|
||||
],
|
||||
"the session forgets every download, not just this window's rows"
|
||||
);
|
||||
assert_eq!(
|
||||
*session.upload_cancelled.lock().expect("not poisoned"),
|
||||
None
|
||||
vec![("alice".to_string(), "served.mp3".to_string())],
|
||||
"a streaming upload is cancelled too"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -654,9 +722,13 @@ mod tests {
|
||||
|
||||
tui.handle_key_event(key('x'));
|
||||
|
||||
assert_eq!(
|
||||
*session.download_cancelled.lock().expect("not poisoned"),
|
||||
None
|
||||
assert!(
|
||||
session
|
||||
.download_cancelled
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.is_empty(),
|
||||
"a finished download is not cancelled"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -676,11 +748,15 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
*session.upload_cancelled.lock().expect("not poisoned"),
|
||||
Some(("alice".to_string(), "served.mp3".to_string()))
|
||||
vec![("alice".to_string(), "served.mp3".to_string())]
|
||||
);
|
||||
assert_eq!(
|
||||
*session.download_cancelled.lock().expect("not poisoned"),
|
||||
None
|
||||
assert!(
|
||||
session
|
||||
.download_cancelled
|
||||
.lock()
|
||||
.expect("not poisoned")
|
||||
.is_empty(),
|
||||
"the upload row cancels no download"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2042,6 +2118,18 @@ mod tests {
|
||||
assert!(screen.contains("run the search again"), "{screen}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_downloads_pane_lists_its_clear_everything_key() {
|
||||
let mut tui = furnished_tui();
|
||||
tui.state.focused_pane = FocusedPane::Downloads;
|
||||
let screen = screen_sized(&mut tui, 300, 40);
|
||||
assert!(screen.contains("[c → clear finished]"), "{screen}");
|
||||
assert!(screen.contains("[C → clear all]"), "{screen}");
|
||||
press(&mut tui, KeyCode::Char('?'));
|
||||
let screen = screen_of(&mut tui);
|
||||
assert!(screen.contains("cancelling the live ones"), "{screen}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_w_brings_every_pane_back_and_leaves_zoom() {
|
||||
let mut tui = furnished_tui();
|
||||
|
||||
@@ -89,6 +89,7 @@ const HELP_RIGHT: &[(&str, &[(&str, &str)])] = &[
|
||||
("r", "retry a failed one"),
|
||||
("d", "delete a queued or finished one"),
|
||||
("c", "clear every finished one"),
|
||||
("C", "clear every one, cancelling the live ones"),
|
||||
("b", "browse that user"),
|
||||
],
|
||||
),
|
||||
@@ -702,6 +703,7 @@ impl MainTui {
|
||||
("r", "retry failed"),
|
||||
("d", "delete queued/done"),
|
||||
("c", "clear finished"),
|
||||
("C", "clear all"),
|
||||
("b", "browse user"),
|
||||
("h/l", "scroll name"),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user