librqbit 8.1.1

The main library used by rqbit torrent client. The binary is just a small wrapper on top of it.
Documentation
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<Duration>,
    pub download_speed: Speed,
    pub upload_speed: Speed,
    pub time_remaining: Option<DurationWithHumanReadable>,
}

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}")?;
        }
        write!(f, ", up speed: {}", self.upload_speed)?;
        Ok(())
    }
}

impl From<&TorrentStateLive> for LiveStats {
    fn from(live: &TorrentStateLive) -> Self {
        let snapshot = live.stats_snapshot();
        let down_estimator = live.down_speed_estimator();
        let up_estimator = live.up_speed_estimator();

        Self {
            average_piece_download_time: snapshot.average_piece_download_time(),
            snapshot,
            download_speed: down_estimator.mbps().into(),
            upload_speed: up_estimator.mbps().into(),
            time_remaining: down_estimator
                .time_remaining()
                .map(DurationWithHumanReadable),
        }
    }
}

#[derive(Clone, Copy, Serialize, Debug)]
pub enum TorrentStatsState {
    #[serde(rename = "initializing")]
    Initializing,
    #[serde(rename = "live")]
    Live,
    #[serde(rename = "paused")]
    Paused,
    #[serde(rename = "error")]
    Error,
}

impl std::fmt::Display for TorrentStatsState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TorrentStatsState::Initializing => f.write_str("initializing"),
            TorrentStatsState::Live => f.write_str("live"),
            TorrentStatsState::Paused => f.write_str("paused"),
            TorrentStatsState::Error => f.write_str("error"),
        }
    }
}

#[derive(Serialize, Debug)]
pub struct TorrentStats {
    pub state: TorrentStatsState,
    pub file_progress: Vec<u64>,
    pub error: Option<String>,
    pub progress_bytes: u64,
    pub uploaded_bytes: u64,
    pub total_bytes: u64,
    pub finished: bool,
    pub live: Option<LiveStats>,
}

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<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
    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<f64> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        #[derive(Serialize)]
        struct Tmp {
            mbps: f64,
            human_readable: String,
        }
        Tmp {
            mbps: self.mbps,
            human_readable: format!("{}", self),
        }
        .serialize(serializer)
    }
}