Add TUI download pause and queued delete

This commit is contained in:
michel
2026-04-26 11:50:37 +02:00
parent 807dea5f17
commit de58c8cbe2
7 changed files with 354 additions and 9 deletions
+187
View File
@@ -115,6 +115,15 @@ impl ClientContext {
) -> Option<&mut Download> {
self.downloads.iter_mut().find(|d| d.token == token)
}
pub fn get_download_by_file_mut(
&mut self,
username: &str,
filename: &str,
) -> Option<&mut Download> {
self.downloads
.iter_mut()
.find(|d| d.username == username && d.filename == filename)
}
pub fn get_download_tokens(&self) -> Vec<u32> {
self.downloads.iter().map(|d| d.token).collect()
}
@@ -131,6 +140,23 @@ impl ClientContext {
download.status = status;
}
}
pub fn remove_queued_download_by_file(
&mut self,
username: &str,
filename: &str,
) -> bool {
let Some(index) = self.downloads.iter().position(|download| {
download.username == username
&& download.filename == filename
&& matches!(download.status, DownloadStatus::Queued)
}) else {
return false;
};
self.downloads.remove(index);
true
}
}
#[test]
fn test_client_context_downloads() {
@@ -161,6 +187,104 @@ fn test_client_context_downloads() {
assert!(context.get_download_by_token(1234).is_none());
}
#[test]
fn test_client_pause_and_resume_download() {
let client = Client::new("test-user", "test-password");
let (download_sender, download_receiver) = mpsc::channel();
let download = Download {
username: "peer".to_string(),
filename: "song.mp3".to_string(),
token: 123,
size: 100,
download_directory: "test".to_string(),
status: DownloadStatus::InProgress {
bytes_downloaded: 25,
total_bytes: 100,
speed_bytes_per_sec: 10.0,
},
sender: download_sender,
};
client.context.write().unwrap().add_download(download);
assert!(client.pause_download("peer", "song.mp3"));
assert!(matches!(
client
.context
.read()
.unwrap()
.get_download_by_token(123)
.unwrap()
.status,
DownloadStatus::Paused {
bytes_downloaded: 25,
total_bytes: 100
}
));
assert!(matches!(
download_receiver.try_recv().unwrap(),
DownloadStatus::Paused {
bytes_downloaded: 25,
total_bytes: 100
}
));
assert!(client.resume_download("peer", "song.mp3"));
assert!(matches!(
client
.context
.read()
.unwrap()
.get_download_by_token(123)
.unwrap()
.status,
DownloadStatus::InProgress {
bytes_downloaded: 25,
total_bytes: 100,
speed_bytes_per_sec: 0.0
}
));
}
#[test]
fn test_client_removes_only_queued_downloads() {
let client = Client::new("test-user", "test-password");
let queued_download = Download {
username: "peer".to_string(),
filename: "queued.mp3".to_string(),
token: 123,
size: 100,
download_directory: "test".to_string(),
status: DownloadStatus::Queued,
sender: mpsc::channel().0,
};
let active_download = Download {
username: "peer".to_string(),
filename: "active.mp3".to_string(),
token: 456,
size: 100,
download_directory: "test".to_string(),
status: DownloadStatus::InProgress {
bytes_downloaded: 25,
total_bytes: 100,
speed_bytes_per_sec: 10.0,
},
sender: mpsc::channel().0,
};
{
let mut context = client.context.write().unwrap();
context.add_download(queued_download);
context.add_download(active_download);
}
assert!(client.remove_queued_download("peer", "queued.mp3"));
assert!(!client.remove_queued_download("peer", "active.mp3"));
let context = client.context.read().unwrap();
assert!(context.get_download_by_token(123).is_none());
assert!(context.get_download_by_token(456).is_some());
}
impl ClientContext {
#[must_use]
pub fn new() -> Self {
@@ -388,6 +512,69 @@ impl Client {
self.context.read().unwrap().get_downloads().clone()
}
pub fn pause_download(&self, username: &str, filename: &str) -> bool {
let mut context = self.context.write().unwrap();
let Some(download) =
context.get_download_by_file_mut(username, filename)
else {
return false;
};
let paused_status = match &download.status {
DownloadStatus::InProgress {
bytes_downloaded,
total_bytes,
..
} => DownloadStatus::Paused {
bytes_downloaded: *bytes_downloaded,
total_bytes: *total_bytes,
},
DownloadStatus::Paused { .. } => return true,
_ => return false,
};
download.status = paused_status.clone();
let _ = download.sender.send(paused_status);
true
}
pub fn resume_download(&self, username: &str, filename: &str) -> bool {
let mut context = self.context.write().unwrap();
let Some(download) =
context.get_download_by_file_mut(username, filename)
else {
return false;
};
let resumed_status = match &download.status {
DownloadStatus::Paused {
bytes_downloaded,
total_bytes,
} => DownloadStatus::InProgress {
bytes_downloaded: *bytes_downloaded,
total_bytes: *total_bytes,
speed_bytes_per_sec: 0.0,
},
DownloadStatus::InProgress { .. } => return true,
_ => return false,
};
download.status = resumed_status.clone();
let _ = download.sender.send(resumed_status);
true
}
pub fn remove_queued_download(
&self,
username: &str,
filename: &str,
) -> bool {
self.context
.write()
.unwrap()
.remove_queued_download_by_file(username, filename)
}
pub fn download(
&self,
filename: String,
+61 -5
View File
@@ -5,6 +5,7 @@ use std::net::TcpStream;
use std::net::ToSocketAddrs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::{Duration, Instant};
use crate::client::ClientContext;
@@ -268,9 +269,24 @@ impl DownloadPeer {
stream
.write_all(&START_DOWNLOAD)
.map_err(DownloadError::StreamWriteError)?;
if let Some(ref dl) = download {
Self::send_download_status(
client_context,
dl,
DownloadStatus::InProgress {
bytes_downloaded: 0,
total_bytes: dl.size,
speed_bytes_per_sec: 0.0,
},
);
}
}
loop {
if let Some(ref dl) = download {
self.wait_while_paused(client_context, dl)?;
}
match stream.read(&mut read_buffer) {
Ok(0) => {
trace!(
@@ -295,6 +311,17 @@ impl DownloadPeer {
);
download = Some(new_download);
processor.received = true;
if let Some(ref dl) = download {
Self::send_download_status(
client_context,
dl,
DownloadStatus::InProgress {
bytes_downloaded: 0,
total_bytes: dl.size,
speed_bytes_per_sec: 0.0,
},
);
}
continue;
}
@@ -318,11 +345,7 @@ impl DownloadPeer {
total_bytes: dl.size,
speed_bytes_per_sec: speed,
};
let _ = dl.sender.send(status.clone());
client_context
.write()
.unwrap()
.update_download_with_status(dl.token, status);
Self::send_download_status(client_context, dl, status);
last_update_time = Instant::now();
}
@@ -353,6 +376,39 @@ impl DownloadPeer {
Ok((processor.buffer, download))
}
fn send_download_status(
client_context: &Arc<RwLock<ClientContext>>,
download: &Download,
status: DownloadStatus,
) {
let _ = download.sender.send(status.clone());
client_context
.write()
.unwrap()
.update_download_with_status(download.token, status);
}
fn wait_while_paused(
&self,
client_context: &Arc<RwLock<ClientContext>>,
download: &Download,
) -> Result<(), DownloadError> {
loop {
let status = client_context
.read()
.map_err(|_| DownloadError::LockPoisoned)?
.get_download_by_token(download.token)
.map(|download| download.status.clone())
.ok_or(DownloadError::TokenNotFound(download.token))?;
if !matches!(status, DownloadStatus::Paused { .. }) {
return Ok(());
}
thread::sleep(Duration::from_millis(200));
}
}
fn resolve_download_path(
&self,
download: &Download,
+7
View File
@@ -112,6 +112,9 @@ impl Download {
match &self.status {
DownloadStatus::InProgress {
bytes_downloaded, ..
}
| DownloadStatus::Paused {
bytes_downloaded, ..
} => *bytes_downloaded,
DownloadStatus::Completed => self.size,
_ => 0,
@@ -138,6 +141,10 @@ pub enum DownloadStatus {
total_bytes: u64,
speed_bytes_per_sec: f64,
},
Paused {
bytes_downloaded: u64,
total_bytes: u64,
},
Completed,
Failed,
TimedOut,