diff --git a/crates/arr-api/src/subtitles.rs b/crates/arr-api/src/subtitles.rs index 7c24d3e..b7503af 100644 --- a/crates/arr-api/src/subtitles.rs +++ b/crates/arr-api/src/subtitles.rs @@ -1272,9 +1272,13 @@ async fn refresh_jellyfin(state: &AppState) { ) )] pub async fn queue(State(state): State) -> Result, ApiError> { + // A language dropped from the wanted set (§15) is not a gap any more — + // its attempt row just has not been cleaned up yet. `missing_for` (#201) + // already applies this bound; the queue reads the same way. + let wanted: BTreeSet = wanted_languages(&state).await?.into_iter().collect(); Ok(Json(SubtitleQueue { - movies: movie_gaps(&state).await?, - series: series_gaps(&state).await?, + movies: movie_gaps(&state, &wanted).await?, + series: series_gaps(&state, &wanted).await?, })) } @@ -1295,7 +1299,10 @@ fn attempt_reason( } } -async fn movie_gaps(state: &AppState) -> Result, ApiError> { +async fn movie_gaps( + state: &AppState, + wanted: &BTreeSet, +) -> Result, ApiError> { let database = pool(state)?; let mut out: Vec = Vec::new(); @@ -1313,6 +1320,9 @@ async fn movie_gaps(state: &AppState) -> Result, ApiError .fetch_all(database) .await?; for row in attempts { + if !wanted.contains(&row.language) { + continue; + } let (reason, detail) = attempt_reason(row.state, row.last_failure); push_movie_gap( &mut out, @@ -1388,7 +1398,10 @@ fn push_movie_gap( }); } -async fn series_gaps(state: &AppState) -> Result, ApiError> { +async fn series_gaps( + state: &AppState, + wanted: &BTreeSet, +) -> Result, ApiError> { let database = pool(state)?; let mut out: Vec = Vec::new(); @@ -1410,6 +1423,9 @@ async fn series_gaps(state: &AppState) -> Result, ApiErr .fetch_all(database) .await?; for row in attempts { + if !wanted.contains(&row.language) { + continue; + } let (reason, detail) = attempt_reason(row.state, row.last_failure); push_episode_gap( &mut out, @@ -2710,6 +2726,29 @@ mod tests { assert!(body["series"].as_array().expect("series").is_empty()); } + /// #202: an attempt row for a language no longer in the wanted set is + /// not a gap any more — `missing_for` (#201) already draws this line. + #[tokio::test] + async fn subtitle_queue_drops_a_language_no_longer_wanted() { + let fixture = stub_application().await; + arr_db::subtitles::record_attempt( + &fixture.pool, + fixture.media_file_id, + "en", + arr_db::SubtitleState::Failed, + Some("429 from opensubtitles"), + ) + .await + .expect("record failed attempt"); + sqlx::query("UPDATE subtitle_settings SET wanted_languages = '[\"pt-PT\"]' WHERE id = 1") + .execute(&fixture.pool) + .await + .expect("narrow wanted set"); + + let body = queue(&fixture.base).await; + assert!(body["movies"].as_array().expect("movies").is_empty()); + } + /// #202: a movie's failed attempt surfaces with its language and detail. #[tokio::test] async fn subtitle_queue_lists_a_movie_gap() {