Files
soulseek-rs-modded/soulseek-rs-lib/src/types.rs
T
michel ae28f4236a feat(tui): surface the failure reason on failed downloads
Carry a human-readable reason on DownloadStatus::Failed so it flows
through the existing status channel to the TUI. The download Info pane
now shows an Error line for a failed download (connect failure, peer
disconnect, truncated transfer, peer-reported upload failure, or the
underlying transfer error). Failures without a known reason render as
before.
2026-07-19 19:36:15 +02:00

198 lines
5.0 KiB
Rust

use std::{collections::HashMap, sync::mpsc::Sender};
use crate::{error::Result, message::Message, utils::zlib::deflate};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct File {
pub username: String,
pub name: String,
pub size: u64,
pub attribs: HashMap<u32, u32>,
}
pub struct UploadFailed {
pub filename: String,
}
impl UploadFailed {
pub fn new_from_message(message: &mut Message) -> Self {
let filename = message.read_string();
Self { filename }
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SearchResult {
pub token: u32,
pub files: Vec<File>,
pub slots: u8,
pub speed: u32,
pub username: String,
}
#[derive(Debug, Clone)]
pub struct Search {
pub token: u32,
pub results: Vec<SearchResult>,
}
impl SearchResult {
pub fn new_from_message(message: &mut Message) -> Result<Self> {
let pointer = message.get_pointer();
let size = message.get_size();
let data: Vec<u8> = message.get_slice(pointer, size);
let deflated = deflate(&data)?;
let mut message = Message::new_with_data(deflated);
let username = message.read_string();
let token = message.read_int32();
let n_files = message.read_int32();
let mut files: Vec<File> = Vec::new();
for _ in 0..n_files {
message.read_int8();
let name = message.read_string();
let size = message.read_int64();
message.read_string();
let n_attribs = message.read_int32();
let mut attribs: HashMap<u32, u32> = HashMap::new();
for _ in 0..n_attribs {
attribs.insert(message.read_int32(), message.read_int32());
}
files.push(File {
username: username.clone(),
name,
size,
attribs,
});
}
let slots = message.read_int8();
let speed = message.read_int32();
Ok(Self {
token,
files,
slots,
speed,
username,
})
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Transfer {
pub direction: u32,
pub token: u32,
pub filename: String,
pub size: u64,
}
#[derive(Debug, Clone, Default)]
pub struct DownloadMetadata {
pub bitrate: Option<u32>,
pub length_seconds: Option<u32>,
pub peer_upload_speed: Option<u32>,
pub peer_free_slots: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct Download {
pub username: String,
pub filename: String,
pub token: u32,
pub size: u64,
pub download_directory: String,
pub status: DownloadStatus,
pub sender: Sender<DownloadStatus>,
pub queue_position: Option<u32>,
pub metadata: DownloadMetadata,
}
impl Download {
#[must_use]
pub const fn is_finished(&self) -> bool {
matches!(
self.status,
DownloadStatus::Completed
| DownloadStatus::Failed(_)
| DownloadStatus::TimedOut
)
}
#[must_use]
pub const fn bytes_downloaded(&self) -> u64 {
match &self.status {
DownloadStatus::InProgress {
bytes_downloaded, ..
}
| DownloadStatus::Paused {
bytes_downloaded, ..
} => *bytes_downloaded,
DownloadStatus::Completed => self.size,
_ => 0,
}
}
#[must_use]
pub const fn speed_bytes_per_sec(&self) -> f64 {
match &self.status {
DownloadStatus::InProgress {
speed_bytes_per_sec,
..
} => *speed_bytes_per_sec,
_ => 0.0,
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum DownloadStatus {
Queued,
InProgress {
bytes_downloaded: u64,
total_bytes: u64,
speed_bytes_per_sec: f64,
},
Paused {
bytes_downloaded: u64,
total_bytes: u64,
},
Completed,
/// Failed, optionally with a human-readable reason.
Failed(Option<String>),
TimedOut,
}
impl Transfer {
pub fn new_from_message(message: &mut Message) -> Self {
let direction = message.read_int32();
let token = message.read_int32();
let filename = message.read_string();
let size = message.read_int64();
Self {
direction,
token,
filename,
size,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// A truncated TransferRequest from an untrusted peer must parse to defaults
// rather than panic (the read_* primitives are bounds-checked).
#[test]
fn transfer_new_from_truncated_message_does_not_panic() {
let mut message = Message::new_with_data(vec![1, 0, 0]);
let transfer = Transfer::new_from_message(&mut message);
assert_eq!(transfer.direction, 0);
assert_eq!(transfer.token, 0);
assert_eq!(transfer.filename, "");
assert_eq!(transfer.size, 0);
}
}