refactor(daemon): extract tv queue roll-up

This commit is contained in:
Miguel Palhas
2026-08-23 16:53:52 +01:00
parent 8ec1506098
commit e3d41c24e1
6 changed files with 164 additions and 152 deletions
+86 -74
View File
@@ -147,78 +147,10 @@ impl AttentionAction {
});
}
let mut tv: HashMap<i64, (String, Option<i64>, TvEntry)> = HashMap::new();
let tv_no_pt_source = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
e.id AS "episode_id!: i64"
FROM episodes e
JOIN seasons se ON se.id = e.season_id
JOIN series s ON s.id = se.series_id
JOIN roots root ON root.id = s.root_id
WHERE root.audience = 'kids'
AND s.blocked = 0
AND e.wanted = 1 AND e.state = 'missing' AND e.search_attempts > 0
AND NOT EXISTS (
SELECT 1 FROM episode_releases er
JOIN releases r ON r.id = er.release_id
WHERE er.episode_id = e.id AND r.verdict IN ('eligible', 'waived')
)
"#
)
.fetch_all(database.pool())
.await?;
for row in tv_no_pt_source {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.no_pt_source
.push(row.episode_id);
}
let episode_hard_fails = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
g.target_id AS "episode_id!: i64"
FROM grabs g
JOIN episodes e ON g.target_kind = 'episode' AND e.id = g.target_id
JOIN seasons se ON se.id = e.season_id
JOIN series s ON s.id = se.series_id
WHERE g.state = 'failed'
GROUP BY s.id, s.title, s.year, e.id
HAVING count(DISTINCT g.release_id) >= 2
"#
)
.fetch_all(database.pool())
.await?;
for row in episode_hard_fails {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.hard_failed_episodes
.push(row.episode_id);
}
let season_pack_fails = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
g.target_id AS "season_id!: i64"
FROM grabs g
JOIN seasons se ON g.target_kind = 'season' AND se.id = g.target_id
JOIN series s ON s.id = se.series_id
WHERE g.state = 'failed'
GROUP BY s.id, s.title, s.year, se.id
"#
)
.fetch_all(database.pool())
.await?;
for row in season_pack_fails {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.failed_season_packs
.push(row.season_id);
}
for (&series_id, (title, year, entry)) in &tv {
for (series_id, title, year, entry) in queue_tv(database).await? {
queued.insert(
Subject::Series(series_id),
(title_with_year(title, *year), entry.reason()),
(title_with_year(&title, year), entry.reason()),
);
}
@@ -262,18 +194,98 @@ fn title_with_year(title: &str, year: Option<i64>) -> String {
}
/// The series' roll-up entry, created empty on first sight.
fn tv_entry<'a>(
tv: &'a mut HashMap<i64, (String, Option<i64>, TvEntry)>,
fn tv_entry(
tv: &mut HashMap<i64, (String, Option<i64>, TvEntry)>,
series_id: i64,
title: String,
year: Option<i64>,
) -> &'a mut TvEntry {
) -> &mut TvEntry {
&mut tv
.entry(series_id)
.or_insert_with(|| (title, year, TvEntry::default()))
.2
}
/// TV roll-up (§9.5): every queued series with what put it there — wanted
/// episodes whose every candidate was rejected for language, episodes two
/// different releases hard-failed post-probe, and seasons whose pack grab
/// hard-failed. One entry per series, so the notification can be one per
/// series however long the broken season is.
async fn queue_tv(database: &Db) -> Result<Vec<(i64, String, Option<i64>, TvEntry)>, sqlx::Error> {
let mut tv = HashMap::new();
let tv_no_pt_source = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
e.id AS "episode_id!: i64"
FROM episodes e
JOIN seasons se ON se.id = e.season_id
JOIN series s ON s.id = se.series_id
JOIN roots root ON root.id = s.root_id
WHERE root.audience = 'kids'
AND s.blocked = 0
AND e.wanted = 1 AND e.state = 'missing' AND e.search_attempts > 0
AND NOT EXISTS (
SELECT 1 FROM episode_releases er
JOIN releases r ON r.id = er.release_id
WHERE er.episode_id = e.id AND r.verdict IN ('eligible', 'waived')
)
"#
)
.fetch_all(database.pool())
.await?;
for row in tv_no_pt_source {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.no_pt_source
.push(row.episode_id);
}
let episode_hard_fails = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
g.target_id AS "episode_id!: i64"
FROM grabs g
JOIN episodes e ON g.target_kind = 'episode' AND e.id = g.target_id
JOIN seasons se ON se.id = e.season_id
JOIN series s ON s.id = se.series_id
WHERE g.state = 'failed'
GROUP BY s.id, s.title, s.year, e.id
HAVING count(DISTINCT g.release_id) >= 2
"#
)
.fetch_all(database.pool())
.await?;
for row in episode_hard_fails {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.hard_failed_episodes
.push(row.episode_id);
}
let season_pack_fails = sqlx::query!(
r#"
SELECT s.id AS "series_id!: i64", s.title AS "title!: String", s.year,
g.target_id AS "season_id!: i64"
FROM grabs g
JOIN seasons se ON g.target_kind = 'season' AND se.id = g.target_id
JOIN series s ON s.id = se.series_id
WHERE g.state = 'failed'
GROUP BY s.id, s.title, s.year, se.id
"#
)
.fetch_all(database.pool())
.await?;
for row in season_pack_fails {
tv_entry(&mut tv, row.series_id, row.title, row.year)
.failed_season_packs
.push(row.season_id);
}
Ok(tv
.into_iter()
.map(|(id, (t, y, e))| (id, t, y, e))
.collect())
}
impl Action for AttentionAction {
fn name(&self) -> &'static str {
"attention"
@@ -359,7 +371,7 @@ mod tests {
VALUES (?, ?, ?, 1, 'missing', 1)",
)
.bind(season_id)
.bind(number as i64 + 1)
.bind(i64::try_from(number).unwrap_or(0) + 1)
.bind(format!("Episode {number}"))
.execute(database.pool())
.await