fix(daemon): retry season packs after backoff
A failed season-pack grab held the season off the pack lane forever: pack_hard_failed was a bare EXISTS over failed grabs, so one bad torrent disabled pack search for good, against §6.2's "it never gives up entirely, it goes quiet". The guard now rides the shared backoff curve (backoff_elapsed, 1h → 6h → 1d → 3d, capped 7d), counting failed pack grabs as attempts and anchoring on the latest one's grabbed_at. Both the targeted and RSS lanes agree. A manual season search waives the window outright — the season deck is §6.2's escape hatch. Closes #181. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
//! while a season is airing, grab per episode. A completed season with no
|
||||
//! episodes on disk prefers the pack — one torrent, better seeded, consistent
|
||||
//! encode. A pack that hard-failed must not cost the whole season, so the
|
||||
//! season falls back to per-episode instead of being blacklisted outright.
|
||||
//! season falls back to per-episode while the failure's §6.2 backoff window
|
||||
//! is open, and retries the pack once it elapses — quiet, never off.
|
||||
//!
|
||||
//! Re-grabbing a pack once an airing season completes is deliberately not
|
||||
//! done (§14): a season with any episode already on disk grabs per episode.
|
||||
@@ -31,8 +32,9 @@ pub struct SeasonGrabFacts<'a> {
|
||||
/// re-grabs a pack over episodes on disk, and a pack must not re-import
|
||||
/// what exists).
|
||||
pub any_episode_on_disk: bool,
|
||||
/// Whether a season-pack grab for this season already hard-failed.
|
||||
pub pack_hard_failed: bool,
|
||||
/// Whether a failed season-pack grab still holds the season off the
|
||||
/// pack lane — true only while the §6.2 backoff window is open.
|
||||
pub pack_backoff_active: bool,
|
||||
}
|
||||
|
||||
/// Picks the grab mode for one season.
|
||||
@@ -49,7 +51,7 @@ pub fn season_grab_mode(facts: &SeasonGrabFacts<'_>) -> SeasonGrabMode {
|
||||
.iter()
|
||||
.all(|date| date.is_some_and(|date| date <= facts.now));
|
||||
|
||||
if fully_released && !facts.any_episode_on_disk && !facts.pack_hard_failed {
|
||||
if fully_released && !facts.any_episode_on_disk && !facts.pack_backoff_active {
|
||||
SeasonGrabMode::SeasonPack
|
||||
} else {
|
||||
SeasonGrabMode::PerEpisode
|
||||
@@ -69,7 +71,7 @@ mod tests {
|
||||
air_dates,
|
||||
now: SystemTime::UNIX_EPOCH + 100 * DAY,
|
||||
any_episode_on_disk: false,
|
||||
pack_hard_failed: false,
|
||||
pack_backoff_active: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +107,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hard_failed_pack_falls_back_to_per_episode() {
|
||||
fn a_failed_pack_inside_its_backoff_window_falls_back_to_per_episode() {
|
||||
let aired = [Some(SystemTime::UNIX_EPOCH + 10 * DAY)];
|
||||
let mut facts = facts(&aired);
|
||||
facts.pack_hard_failed = true;
|
||||
facts.pack_backoff_active = true;
|
||||
assert_eq!(season_grab_mode(&facts), SeasonGrabMode::PerEpisode);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user