Targeted-search backoff and release-date gating #90
Reference in New Issue
Block a user
Delete Branch "issue/26-search-backoff"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds TMDB-gated targeted search, the 1h/6h/1d/3d/7d backoff, and reset-on-metadata-change behavior.
Closes #26.
Reviewed
1d5501449d162d7838f58543a42ddcacb2788c80.@@ -143,3 +153,2 @@}for movie in gaps {let Some(movie) = self.refresh_metadata(database, movie).await? else {refresh_metadata(...).await?propagates a TMDB error out oftick: one failed TMDB request aborts the whole pass — remaining pending movies are skipped and the outcomes fromtrack_sent_grabsare dropped.grab_onefailures are deliberately per-movie ("One title's failure must not cost the rest of the tick"); TMDB failures deserve the same log-and-continue treatment.@@ -162,0 +188,4 @@};let tmdb_id =u32::try_from(movie.tmdb_id).map_err(|_| GrabError::InvalidTmdbId(movie.id))?;let metadata = tmdb.movie(tmdb_id).await?;TMDB is queried once per pending movie on every 30 s tick, before the
search_duegate. A wanted list of N unreleased or backed-off movies costs 2N TMDB requests per minute forever; the §6.2 cost table says targeted work is one call per wanted item per attempt. A per-movie refresh TTL (hours) would keep the metadata-change reset without the per-tick fetch.@@ -354,6 +441,7 @@ impl GrabAction {let Some(winner) = candidates.into_iter().find(|candidate| {!blacklist.blocks_candidate(&candidate.name, &candidate.download_url)}) else {record_search(database, movie.id).await?;record_searchnow runs only in the no-eligible-winner branch. When a winner exists but the grab fails after the search — a Transmission error (Errfromgrab_one), or the blacklisted-infohash drop path returningOk(None)— the attempt is not recorded, so the same full Torznab search (and in the blacklist case, an add + drop of the same torrent) repeats every 30 s with no backoff. Record the search before winner selection, or in those failure paths too.Reviewed
425a0466161c3c721c50de5a04ee73f183b91a7e. All three prior findings addressed (per-movie TMDB error handling, 6 h metadata refresh TTL,record_searchon failed-grab paths). No findings.Addressed all three findings, see inline replies.
@@ -143,3 +153,2 @@}for movie in gaps {let (movie_id, title) = (movie.id, movie.title.clone());Fixed:
refresh_metadataerrors now log andcontinueintick, same asgrab_onefailures — one title's metadata error no longer aborts the rest of the pass. Pushed in425a046.@@ -161,1 +188,4 @@async fn refresh_metadata(&self,database: &Db,Fixed: added a persisted
metadata_refreshed_atcolumn (migration 0009) with a 6h TTL, independent of the search backoff. When not due, the storeddigital_releasegates the search instead of hitting TMDB. Covered by a new test that uses a freshTmdbClientper tick to rule out its own response cache masking the effect. Pushed in425a046.@@ -440,5 +440,5 @@.await?;let Some(winner) = candidates.into_iter().find(|candidate| {!blacklist.blocks_candidate(&candidate.name, &candidate.download_url)}) else {record_search(database, movie.id).await?;Fixed:
record_searchnow also runs on the Transmission-error path and the blacklisted-infohash drop path (extracted intosend_winner), not only when no winner is found. Pushed in425a046.