#![allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
reason = "M175: human-readable display formatting — values bounded by realistic torrent sizes; precision loss intentional for short readable strings"
)]
pub mod file_entries;
pub mod peer_flags;
pub mod pseudo_trackers;
pub use file_entries::{FlatFileEntry, build_flat};
pub use peer_flags::peer_flags;
pub use pseudo_trackers::{
PSEUDO_TRACKER_DHT_URL, PSEUDO_TRACKER_LSD_URL, PSEUDO_TRACKER_PEX_URL, PSEUDO_TRACKER_TIER,
is_pseudo_tracker, synthesize_pseudo_trackers,
};
use irontide_core::FilePriority;
use irontide_session::TorrentState;
#[must_use]
pub fn parse_priority_label(s: &str) -> Option<FilePriority> {
match s {
"skip" => Some(FilePriority::Skip),
"low" => Some(FilePriority::Low),
"normal" => Some(FilePriority::Normal),
"high" => Some(FilePriority::High),
_ => None,
}
}
#[must_use]
pub fn format_size(bytes: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
if bytes >= GIB {
format!("{:.2} GiB", bytes as f64 / GIB as f64)
} else if bytes >= MIB {
format!("{:.1} MiB", bytes as f64 / MIB as f64)
} else if bytes >= KIB {
format!("{:.1} KiB", bytes as f64 / KIB as f64)
} else {
format!("{bytes} B")
}
}
#[must_use]
pub fn format_rate(bytes_per_sec: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
if bytes_per_sec >= MIB {
format!("{:.1} MB/s", bytes_per_sec as f64 / MIB as f64)
} else if bytes_per_sec >= KIB {
format!("{:.1} KB/s", bytes_per_sec as f64 / KIB as f64)
} else {
format!("{bytes_per_sec} B/s")
}
}
#[must_use]
pub fn format_state(state: &TorrentState, user_seed_mode: bool) -> &'static str {
if user_seed_mode && matches!(state, TorrentState::Downloading) {
return "seed only";
}
match state {
TorrentState::FetchingMetadata => "fetching metadata",
TorrentState::Checking => "checking",
TorrentState::Downloading => "downloading",
TorrentState::Complete => "complete",
TorrentState::Seeding => "seeding",
TorrentState::Paused => "paused",
TorrentState::Queued => "queued",
TorrentState::Stopped => "stopped",
TorrentState::Sharing => "sharing",
}
}
#[must_use]
pub fn format_state_with_super_seeding(
state: &TorrentState,
user_seed_mode: bool,
super_seeding: bool,
) -> &'static str {
if super_seeding && matches!(state, TorrentState::Seeding) {
return "super seeding";
}
format_state(state, user_seed_mode)
}
#[must_use]
pub fn format_ratio(uploaded: u64, downloaded: u64) -> String {
if downloaded == 0 && uploaded > 0 {
return "\u{221e}".to_string(); }
if downloaded == 0 {
return "0.00".to_string();
}
format!("{:.2}", uploaded as f64 / downloaded as f64)
}
#[must_use]
pub fn format_eta(remaining_bytes: u64, rate_bps: u64) -> String {
if rate_bps == 0 {
return "\u{2014}".to_string(); }
let secs = remaining_bytes / rate_bps;
if secs >= 86400 {
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
format!("{days}d {hours}h")
} else if secs >= 3600 {
let hours = secs / 3600;
let mins = (secs % 3600) / 60;
format!("{hours}h {mins}m")
} else if secs >= 60 {
let mins = secs / 60;
let s = secs % 60;
format!("{mins}m {s}s")
} else {
format!("{secs}s")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_size_boundaries() {
assert_eq!(format_size(0), "0 B");
assert_eq!(format_size(512), "512 B");
assert_eq!(format_size(1024), "1.0 KiB");
assert_eq!(format_size(512 * 1024), "512.0 KiB");
assert_eq!(format_size(1_048_576), "1.0 MiB");
assert_eq!(format_size(512 * 1_048_576), "512.0 MiB");
assert_eq!(format_size(1_073_741_824), "1.00 GiB");
assert_eq!(format_size(512 * 1_073_741_824), "512.00 GiB");
}
#[test]
fn test_format_rate_boundaries() {
assert_eq!(format_rate(0), "0 B/s");
assert_eq!(format_rate(512), "512 B/s");
assert_eq!(format_rate(1024), "1.0 KB/s");
assert_eq!(format_rate(512 * 1024), "512.0 KB/s");
assert_eq!(format_rate(1_048_576), "1.0 MB/s");
assert_eq!(format_rate(1_073_741_824), "1024.0 MB/s");
}
#[test]
fn test_format_state_all_variants() {
assert_eq!(
format_state(&TorrentState::FetchingMetadata, false),
"fetching metadata"
);
assert_eq!(format_state(&TorrentState::Checking, false), "checking");
assert_eq!(
format_state(&TorrentState::Downloading, false),
"downloading"
);
assert_eq!(format_state(&TorrentState::Complete, false), "complete");
assert_eq!(format_state(&TorrentState::Seeding, false), "seeding");
assert_eq!(format_state(&TorrentState::Paused, false), "paused");
assert_eq!(format_state(&TorrentState::Queued, false), "queued");
assert_eq!(format_state(&TorrentState::Stopped, false), "stopped");
assert_eq!(format_state(&TorrentState::Sharing, false), "sharing");
assert_eq!(format_state(&TorrentState::Downloading, true), "seed only");
assert_eq!(format_state(&TorrentState::Seeding, true), "seeding");
assert_eq!(format_state(&TorrentState::Paused, true), "paused");
assert_eq!(format_state(&TorrentState::Queued, true), "queued");
assert_eq!(format_state(&TorrentState::Complete, true), "complete");
assert_eq!(format_state(&TorrentState::Sharing, true), "sharing");
assert_eq!(format_state(&TorrentState::Stopped, true), "stopped");
}
#[test]
fn test_format_ratio() {
assert_eq!(format_ratio(0, 0), "0.00");
assert_eq!(format_ratio(500, 0), "\u{221e}");
assert_eq!(format_ratio(150, 100), "1.50");
assert_eq!(format_ratio(1000, 1000), "1.00");
assert_eq!(format_ratio(1_000_000, 1_000), "1000.00");
assert_eq!(format_ratio(50, 100), "0.50");
}
#[test]
fn test_parse_priority_label_known_slugs() {
assert_eq!(parse_priority_label("skip"), Some(FilePriority::Skip));
assert_eq!(parse_priority_label("low"), Some(FilePriority::Low));
assert_eq!(parse_priority_label("normal"), Some(FilePriority::Normal));
assert_eq!(parse_priority_label("high"), Some(FilePriority::High));
}
#[test]
fn test_parse_priority_label_unknown_returns_none() {
assert_eq!(parse_priority_label(""), None);
assert_eq!(parse_priority_label("SKIP"), None);
assert_eq!(parse_priority_label("medium"), None);
assert_eq!(parse_priority_label("4"), None);
}
#[test]
fn test_format_eta() {
assert_eq!(format_eta(0, 1), "0s");
assert_eq!(format_eta(1_000_000, 0), "\u{2014}");
assert_eq!(format_eta(30, 1), "30s");
assert_eq!(format_eta(45 * 60 + 12, 1), "45m 12s");
assert_eq!(format_eta(2 * 3600 + 15 * 60, 1), "2h 15m");
assert_eq!(format_eta(2 * 86400 + 15 * 3600, 1), "2d 15h");
assert_eq!(format_eta(100 * 1_048_576, 1_048_576), "1m 40s");
}
#[test]
fn format_state_with_super_seeding_shows_super_seeding() {
assert_eq!(
format_state_with_super_seeding(&TorrentState::Seeding, false, true),
"super seeding"
);
}
#[test]
fn format_state_with_super_seeding_normal_seeding() {
assert_eq!(
format_state_with_super_seeding(&TorrentState::Seeding, false, false),
"seeding"
);
}
#[test]
fn format_state_with_super_seeding_non_seeding_state() {
assert_eq!(
format_state_with_super_seeding(&TorrentState::Downloading, false, true),
"downloading"
);
}
}