irontide-format 1.0.2

Shared human-readable formatting helpers for irontide crates
Documentation
//! qBittorrent-parity pseudo-trackers for the DHT/PeX/LSD subsystems.
//!
//! qBt's `WebUI` v2 emits three synthetic tracker rows in front of any
//! announce-list trackers — one each for DHT, `PeX`, and LSD — letting
//! clients display per-subsystem connectivity in a uniform table. This
//! module produces those rows as `TrackerInfo` so both the GUI's Trackers
//! tab and the `qbt_v2::trackers` wire endpoint can share one synthesis
//! path.
//!
//! `TrackerInfo::tier` is `usize`, but qBt's wire format uses `-1` for
//! pseudo-trackers. We encode the sentinel as [`PSEUDO_TRACKER_TIER`]
//! (`usize::MAX`); consumers translate back when emitting wire rows.

use irontide_session::{TrackerInfo, TrackerStatus};

/// URL string emitted by qBittorrent for the DHT pseudo-tracker.
pub const PSEUDO_TRACKER_DHT_URL: &str = "** [DHT] **";

/// URL string emitted by qBittorrent for the `PeX` pseudo-tracker.
pub const PSEUDO_TRACKER_PEX_URL: &str = "** [PeX] **";

/// URL string emitted by qBittorrent for the LSD pseudo-tracker.
pub const PSEUDO_TRACKER_LSD_URL: &str = "** [LSD] **";

/// Sentinel `tier` value identifying a pseudo-tracker entry.
///
/// `TrackerInfo::tier` is `usize`, so the qBt wire's `-1` cannot round-trip
/// directly. Format consumers translate this sentinel back to `-1` for the
/// qBt v2 wire and use it to detect pseudo rows for special-cased rendering.
pub const PSEUDO_TRACKER_TIER: usize = usize::MAX;

/// Whether this tracker entry is a synthetic pseudo-tracker (DHT/PeX/LSD).
#[must_use]
pub fn is_pseudo_tracker(t: &TrackerInfo) -> bool {
    t.tier == PSEUDO_TRACKER_TIER
}

/// Build the full tracker list with the three qBt-parity pseudo-trackers
/// prepended in DHT → `PeX` → LSD order.
///
/// Counts populate the `seeders` field as a peer-count surrogate — the
/// qBt v2 wrapper maps that to qBt's `num_peers` for pseudo rows. The
/// pseudo entries always carry `TrackerStatus::Working`; subsystem
/// disabled-state is the wrapper's concern.
#[must_use]
pub fn synthesize_pseudo_trackers(
    real: &[TrackerInfo],
    dht_node_count: usize,
    pex_peer_count: usize,
    lsd_peer_count: usize,
) -> Vec<TrackerInfo> {
    let mut out = Vec::with_capacity(real.len() + 3);
    out.push(pseudo(PSEUDO_TRACKER_DHT_URL, dht_node_count));
    out.push(pseudo(PSEUDO_TRACKER_PEX_URL, pex_peer_count));
    out.push(pseudo(PSEUDO_TRACKER_LSD_URL, lsd_peer_count));
    out.extend(real.iter().cloned());
    out
}

#[allow(
    clippy::cast_possible_truncation,
    reason = "u32 saturation handled explicitly via min()"
)]
fn pseudo(url: &str, count: usize) -> TrackerInfo {
    let count_u32 = count.min(u32::MAX as usize) as u32;
    TrackerInfo {
        url: url.to_string(),
        tier: PSEUDO_TRACKER_TIER,
        status: TrackerStatus::Working,
        seeders: Some(count_u32),
        leechers: None,
        downloaded: None,
        next_announce_secs: 0,
        consecutive_failures: 0,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn real_tracker(url: &str, tier: usize) -> TrackerInfo {
        TrackerInfo {
            url: url.to_string(),
            tier,
            status: TrackerStatus::NotContacted,
            seeders: None,
            leechers: None,
            downloaded: None,
            next_announce_secs: 0,
            consecutive_failures: 0,
        }
    }

    #[test]
    fn three_pseudo_prepended_in_dht_pex_lsd_order() {
        let result = synthesize_pseudo_trackers(&[], 0, 0, 0);
        assert_eq!(result.len(), 3);
        assert_eq!(result[0].url, PSEUDO_TRACKER_DHT_URL);
        assert_eq!(result[1].url, PSEUDO_TRACKER_PEX_URL);
        assert_eq!(result[2].url, PSEUDO_TRACKER_LSD_URL);
    }

    #[test]
    fn pseudo_entries_all_use_sentinel_tier() {
        let result = synthesize_pseudo_trackers(&[], 0, 0, 0);
        for entry in result.iter().take(3) {
            assert_eq!(entry.tier, PSEUDO_TRACKER_TIER);
            assert!(is_pseudo_tracker(entry));
        }
    }

    #[test]
    fn counts_propagate_into_seeders_field() {
        let result = synthesize_pseudo_trackers(&[], 42, 7, 3);
        assert_eq!(result[0].seeders, Some(42));
        assert_eq!(result[1].seeders, Some(7));
        assert_eq!(result[2].seeders, Some(3));
        for entry in &result {
            assert_eq!(entry.leechers, None);
            assert_eq!(entry.downloaded, None);
        }
    }

    #[test]
    fn dht_count_zero_still_renders_pseudo_row() {
        let result = synthesize_pseudo_trackers(&[], 0, 5, 5);
        assert_eq!(result[0].seeders, Some(0));
        assert!(is_pseudo_tracker(&result[0]));
    }

    #[test]
    fn real_trackers_appended_after_pseudo() {
        let real = vec![
            real_tracker("https://tracker.example/announce", 0),
            real_tracker("https://backup.example/announce", 1),
        ];
        let result = synthesize_pseudo_trackers(&real, 1, 1, 1);
        assert_eq!(result.len(), 5);
        assert_eq!(result[3].url, "https://tracker.example/announce");
        assert_eq!(result[3].tier, 0);
        assert!(!is_pseudo_tracker(&result[3]));
        assert_eq!(result[4].tier, 1);
        assert!(!is_pseudo_tracker(&result[4]));
    }

    #[test]
    fn url_constants_match_qbt_wire_format() {
        assert_eq!(PSEUDO_TRACKER_DHT_URL, "** [DHT] **");
        assert_eq!(PSEUDO_TRACKER_PEX_URL, "** [PeX] **");
        assert_eq!(PSEUDO_TRACKER_LSD_URL, "** [LSD] **");
    }
}