When a grabbed release fails (transfer error / peer-cancel / never lands), the engine now retries instead of giving up — the music-style depth: - Grab stores the OTHER accepted results as a retry pool + the search context (schema v16: candidates / search_ctx / tried_queries / tried_files / attempts). - core/video/retry.py (pure, tested): plan_retry() → try the next-best candidate; when the pool is dry, next_query() generates an ALTERNATE query (movie: drop the year; TV: numbering variants) to re-search; budget MAX_ATTEMPTS=6. merge_candidates dedupes against already-tried releases. - Monitor: on failure, _fail_or_retry hops to the next candidate inline; if none, flips the row to a new 'searching' state and a background requery thread re-searches the alternate query, evaluates against the profile, and starts the best fresh hit — or marks failed once truly exhausted. 'searching' rows are owned by their thread. - Page: 'Searching' status (Trying another release…) + a 'Nx' attempt badge. 16 tests (retry engine + candidate-retry transition); ruff clean on touched files.
69 lines
2.9 KiB
Python
69 lines
2.9 KiB
Python
"""Auto-retry decision engine — next_query / plan_retry / merge_candidates. Pure,
|
|
isolated from music."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from core.video.retry import MAX_ATTEMPTS, merge_candidates, next_query, plan_retry
|
|
|
|
|
|
def test_next_query_movie_drops_year_then_exhausts():
|
|
ctx = {"scope": "movie", "title": "Dune", "year": 2021}
|
|
assert next_query(ctx, []) == "Dune 2021"
|
|
assert next_query(ctx, ["Dune 2021"]) == "Dune" # fall back to no-year
|
|
assert next_query(ctx, ["Dune 2021", "Dune"]) is None # exhausted
|
|
|
|
|
|
def test_next_query_tv_variants():
|
|
ep = {"scope": "episode", "title": "The Wire", "season": 2, "episode": 5}
|
|
assert next_query(ep, []) == "The Wire S02E05"
|
|
assert next_query(ep, ["The Wire S02E05"]) == "The Wire 2x05"
|
|
se = {"scope": "season", "title": "The Wire", "season": 2}
|
|
assert next_query(se, []) == "The Wire S02"
|
|
assert next_query(se, ["The Wire S02"]) == "The Wire Season 2"
|
|
|
|
|
|
def _row(**kw):
|
|
base = {"attempts": 0, "candidates": "[]", "tried_files": "[]",
|
|
"search_ctx": json.dumps({"scope": "movie", "title": "Dune", "year": 2021}),
|
|
"tried_queries": json.dumps(["Dune 2021"])}
|
|
base.update(kw)
|
|
return base
|
|
|
|
|
|
def test_plan_retry_next_candidate_first():
|
|
cands = [{"filename": "a.mkv", "username": "u"}, {"filename": "b.mkv", "username": "v"}]
|
|
plan = plan_retry(_row(candidates=json.dumps(cands)))
|
|
assert plan["action"] == "candidate" and plan["candidate"]["filename"] == "a.mkv"
|
|
assert [c["filename"] for c in plan["rest"]] == ["b.mkv"]
|
|
|
|
|
|
def test_plan_retry_skips_already_tried_candidate():
|
|
cands = [{"filename": "a.mkv"}, {"filename": "b.mkv"}]
|
|
plan = plan_retry(_row(candidates=json.dumps(cands), tried_files=json.dumps(["a.mkv"])))
|
|
assert plan["action"] == "candidate" and plan["candidate"]["filename"] == "b.mkv"
|
|
|
|
|
|
def test_plan_retry_requeries_when_candidates_exhausted():
|
|
plan = plan_retry(_row(candidates="[]")) # no candidates, year-query already tried
|
|
assert plan["action"] == "requery" and plan["query"] == "Dune" # the no-year variant
|
|
|
|
|
|
def test_plan_retry_fails_when_everything_exhausted():
|
|
plan = plan_retry(_row(candidates="[]", tried_queries=json.dumps(["Dune 2021", "Dune"])))
|
|
assert plan["action"] == "fail"
|
|
|
|
|
|
def test_plan_retry_respects_budget():
|
|
cands = [{"filename": "a.mkv"}]
|
|
plan = plan_retry(_row(candidates=json.dumps(cands), attempts=MAX_ATTEMPTS))
|
|
assert plan["action"] == "fail" and "budget" in plan["reason"]
|
|
|
|
|
|
def test_merge_candidates_dedupes_against_tried():
|
|
new = [{"filename": "a.mkv", "username": "u", "title": "Dune.2021.1080p"},
|
|
{"filename": "b.mkv", "username": "v"}, {"filename": "a.mkv", "username": "w"}]
|
|
out = merge_candidates(new, ["b.mkv"])
|
|
assert [c["filename"] for c in out] == ["a.mkv"] # b excluded (tried), dup a collapsed
|
|
assert out[0]["release_title"] == "Dune.2021.1080p"
|