use std::time::Duration; use serde::Serialize; use super::{live::stats::snapshot::StatsSnapshot, TorrentStateLive}; use size_format::SizeFormatterBinary as SF; #[derive(Serialize, Default, Debug)] pub struct LiveStats { pub snapshot: StatsSnapshot, pub average_piece_download_time: Option, pub download_speed: Speed, pub time_remaining: Option, } impl std::fmt::Display for LiveStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "down speed: {}", self.download_speed)?; if let Some(time_remaining) = &self.time_remaining { write!(f, " eta: {time_remaining}")?; } Ok(()) } } impl From<&TorrentStateLive> for LiveStats { fn from(live: &TorrentStateLive) -> Self { let snapshot = live.stats_snapshot(); let estimator = live.speed_estimator(); Self { average_piece_download_time: snapshot.average_piece_download_time(), snapshot, download_speed: estimator.download_mbps().into(), time_remaining: estimator.time_remaining().map(DurationWithHumanReadable), } } } #[derive(Serialize, Debug)] pub struct TorrentStats { pub state: &'static str, pub error: Option, pub progress_bytes: u64, pub total_bytes: u64, pub finished: bool, pub live: Option, } impl std::fmt::Display for TorrentStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: ", self.state)?; if let Some(error) = &self.error { return write!(f, "{error}"); } write!( f, "{} ({})", self.progress_percent_human_readable(), self.progress_bytes_human_readable() )?; if let Some(live) = &self.live { write!(f, " [{live}]")?; } Ok(()) } } impl TorrentStats { pub fn progress_percent_human_readable(&self) -> impl std::fmt::Display { struct Percents { progress: u64, total: u64, } impl std::fmt::Display for Percents { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.total == 0 { return write!(f, "N/A"); } let pct = self.progress as f64 / self.total as f64 * 100f64; write!(f, "{pct:.2}%") } } Percents { progress: self.progress_bytes, total: self.total_bytes, } } pub fn progress_bytes_human_readable(&self) -> impl std::fmt::Display { struct Progress { progress: u64, total: u64, } impl std::fmt::Display for Progress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} / {}", SF::new(self.progress), SF::new(self.total)) } } Progress { progress: self.progress_bytes, total: self.total_bytes, } } } fn format_seconds_to_time(seconds: u64, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let hours = seconds / 3600; let minutes = (seconds % 3600) / 60; let seconds = seconds % 60; if hours > 0 { write!(f, "{}h {}m", hours, minutes) } else if minutes > 0 { write!(f, "{}m {}s", minutes, seconds) } else { write!(f, "{}s", seconds) } } pub struct DurationWithHumanReadable(Duration); impl core::fmt::Display for DurationWithHumanReadable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result { format_seconds_to_time(self.0.as_secs(), f) } } impl core::fmt::Debug for DurationWithHumanReadable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) } } impl Serialize for DurationWithHumanReadable { fn serialize(&self, serializer: S) -> core::result::Result where S: serde::Serializer, { #[derive(Serialize)] struct Tmp { duration: Duration, human_readable: String, } Tmp { duration: self.0, human_readable: format!("{}", self), } .serialize(serializer) } } #[derive(Default)] pub struct Speed { pub mbps: f64, } impl Speed { fn new(mbps: f64) -> Self { Self { mbps } } } impl From for Speed { fn from(mbps: f64) -> Self { Self::new(mbps) } } impl core::fmt::Display for Speed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:.2} MiB/s", self.mbps) } } impl core::fmt::Debug for Speed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) } } impl Serialize for Speed { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { #[derive(Serialize)] struct Tmp { mbps: f64, human_readable: String, } Tmp { mbps: self.mbps, human_readable: format!("{}", self), } .serialize(serializer) } }