Skip to main content

irontide_core/
web_seed_stats.rs

1//! Per-URL stats for web-seed sources (BEP 17 + BEP 19).
2//!
3//! Lives in `irontide-core` because [`crate::FastResumeData`] persists a
4//! `HashMap<String, WebSeedStats>` so stats survive app restart (M178
5//! Tension-1). `irontide-session` re-exports this type to keep its public
6//! surface stable.
7
8use serde::{Deserialize, Serialize};
9
10/// Coarse state of a single web-seed source.
11///
12/// `Idle` — no successful chunk yet observed (cold-start before any progress).
13/// `Active` — at least one successful chunk landed; currently downloading.
14/// `Errored` — the most recent attempt failed; [`WebSeedStats::last_error`]
15/// has the message. Transitions back to `Active` on recovery, but
16/// `last_error` PERSISTS so users can see "currently working but flaked
17/// recently" in the GUI (M178 Issue 2.2 / D-eng-8).
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub enum WebSeedState {
20    /// No successful chunk yet observed.
21    #[default]
22    Idle,
23    /// At least one successful chunk landed; currently downloading.
24    Active,
25    /// Most recent attempt failed; `last_error` is populated.
26    Errored,
27}
28
29/// Per-URL stats for a single web-seed source, surfaced to the qBt v2
30/// `/api/v2/torrents/webseeds` endpoint and the GUI's HTTP Sources tab.
31///
32/// `consecutive_failures` resets to zero on a successful chunk; within a
33/// failure run it monotonically increments. `last_error` PERSISTS through
34/// the Errored→Active transition so the GUI can show "currently active,
35/// last failed with X" — intentional per Issue 2.2 / D-eng-8.
36///
37/// `next_retry_unix_secs` is forward-compatible with M186's BEP 17/19
38/// retry-with-backoff logic (D-eng-9 / Issue 2.3); always `None` in M178.
39#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
40pub struct WebSeedStats {
41    /// Web-seed URL (BEP 19 url-list or BEP 17 httpseeds entry).
42    pub url: String,
43    /// Coarse current state. See [`WebSeedState`].
44    #[serde(default)]
45    pub state: WebSeedState,
46    /// Cumulative bytes downloaded from this URL since first observation.
47    #[serde(default)]
48    pub downloaded_bytes: u64,
49    /// Rate at the most recent emission, computed over the throttle window.
50    #[serde(default)]
51    pub last_rate_bps: u64,
52    /// Most recently observed error message; persists through recovery
53    /// (Issue 2.2 / D-eng-8).
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub last_error: Option<String>,
56    /// Consecutive failures since the last success. Resets to zero on
57    /// a successful chunk; monotonically increments within a failure run.
58    #[serde(default)]
59    pub consecutive_failures: u32,
60    /// Unix timestamp of the most recent emission (success or failure).
61    #[serde(default)]
62    pub last_attempt_unix_secs: u64,
63    /// M186 forward-compat (D-eng-9): when set, the unix timestamp at
64    /// which BEP 17/19 retry-with-backoff will next attempt this URL.
65    /// Always `None` in M178; populated once the retry logic ships.
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub next_retry_unix_secs: Option<u64>,
68}