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) {
|
pub(super) fn toggle_selected_download_pause(&self) {
|
||||||
let Some(index) = self.state.downloads_table_state.selected() else {
|
let Some(index) = self.state.downloads_table_state.selected() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -569,6 +569,9 @@ impl MainTui {
|
|||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c') => {
|
||||||
self.clear_finished_downloads();
|
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>>,
|
download_dir_set: std::sync::Mutex<Option<String>>,
|
||||||
/// What `set_shared_directories` was last asked for, if anything.
|
/// What `set_shared_directories` was last asked for, if anything.
|
||||||
shares_set: std::sync::Mutex<Option<Vec<String>>>,
|
shares_set: std::sync::Mutex<Option<Vec<String>>>,
|
||||||
download_cancelled: std::sync::Mutex<Option<(String, String)>>,
|
download_cancelled: std::sync::Mutex<Vec<(String, String)>>,
|
||||||
upload_cancelled: std::sync::Mutex<Option<(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 {
|
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 {
|
fn remove_queued_download(&self, _u: &str, _f: &str) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
fn remove_download(&self, _u: &str, _f: &str) -> bool {
|
fn remove_download(&self, u: &str, f: &str) -> bool {
|
||||||
false
|
self.download_removed
|
||||||
|
.lock()
|
||||||
|
.expect("not poisoned")
|
||||||
|
.push((u.to_string(), f.to_string()));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
fn uploads(&self) -> Vec<soulseek_rs::UploadInfo> {
|
fn uploads(&self) -> Vec<soulseek_rs::UploadInfo> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
@@ -459,13 +464,17 @@ mod tests {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
fn cancel_upload(&self, u: &str, f: &str) -> bool {
|
fn cancel_upload(&self, u: &str, f: &str) -> bool {
|
||||||
*self.upload_cancelled.lock().expect("not poisoned") =
|
self.upload_cancelled
|
||||||
Some((u.to_string(), f.to_string()));
|
.lock()
|
||||||
|
.expect("not poisoned")
|
||||||
|
.push((u.to_string(), f.to_string()));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn cancel_download(&self, u: &str, f: &str) -> bool {
|
fn cancel_download(&self, u: &str, f: &str) -> bool {
|
||||||
*self.download_cancelled.lock().expect("not poisoned") =
|
self.download_cancelled
|
||||||
Some((u.to_string(), f.to_string()));
|
.lock()
|
||||||
|
.expect("not poisoned")
|
||||||
|
.push((u.to_string(), f.to_string()));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn set_upload_slots(&self, _slots: usize) {}
|
fn set_upload_slots(&self, _slots: usize) {}
|
||||||
@@ -614,11 +623,70 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*session.download_cancelled.lock().expect("not poisoned"),
|
*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!(
|
assert_eq!(
|
||||||
*session.upload_cancelled.lock().expect("not poisoned"),
|
*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'));
|
tui.handle_key_event(key('x'));
|
||||||
|
|
||||||
assert_eq!(
|
assert!(
|
||||||
*session.download_cancelled.lock().expect("not poisoned"),
|
session
|
||||||
None
|
.download_cancelled
|
||||||
|
.lock()
|
||||||
|
.expect("not poisoned")
|
||||||
|
.is_empty(),
|
||||||
|
"a finished download is not cancelled"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,11 +748,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*session.upload_cancelled.lock().expect("not poisoned"),
|
*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!(
|
assert!(
|
||||||
*session.download_cancelled.lock().expect("not poisoned"),
|
session
|
||||||
None
|
.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}");
|
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]
|
#[test]
|
||||||
fn shift_w_brings_every_pane_back_and_leaves_zoom() {
|
fn shift_w_brings_every_pane_back_and_leaves_zoom() {
|
||||||
let mut tui = furnished_tui();
|
let mut tui = furnished_tui();
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ const HELP_RIGHT: &[(&str, &[(&str, &str)])] = &[
|
|||||||
("r", "retry a failed one"),
|
("r", "retry a failed one"),
|
||||||
("d", "delete a queued or finished one"),
|
("d", "delete a queued or finished one"),
|
||||||
("c", "clear every finished one"),
|
("c", "clear every finished one"),
|
||||||
|
("C", "clear every one, cancelling the live ones"),
|
||||||
("b", "browse that user"),
|
("b", "browse that user"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -702,6 +703,7 @@ impl MainTui {
|
|||||||
("r", "retry failed"),
|
("r", "retry failed"),
|
||||||
("d", "delete queued/done"),
|
("d", "delete queued/done"),
|
||||||
("c", "clear finished"),
|
("c", "clear finished"),
|
||||||
|
("C", "clear all"),
|
||||||
("b", "browse user"),
|
("b", "browse user"),
|
||||||
("h/l", "scroll name"),
|
("h/l", "scroll name"),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user