Add TUI download pause and queued delete
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user