feat(daemon): refresh metadata on add
The metadata lane runs daily, so a series added a moment ago showed no seasons for up to 24 hours and a movie had no digital release date — the field §6.2 gates targeted search on. AppState now carries a MetadataCommand channel alongside the movie, episode and season ones. Both create handlers send on it after the row is committed, and a new daemon lane drains it. Its own task rather than an arm of manual::run: a refresh against TMDB can take a while and must not sit in front of an operator's manual search. The add never waits on TMDB and never fails because of it. A refresh that fails leaves metadata_refreshed_at NULL, which is what the daily sweep already treats as due, so the title is retried rather than lost. A command naming a title deleted in between finds no row and does nothing. METADATA_INTERVAL is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ mod import;
|
||||
mod indexers;
|
||||
mod jellyfin;
|
||||
mod manual;
|
||||
mod metadata;
|
||||
mod notify;
|
||||
mod reaper;
|
||||
pub mod reconcile;
|
||||
@@ -125,6 +126,10 @@ async fn run() -> Result<(), Error> {
|
||||
let notifier = Notifier::new(config.ntfy_url.clone())?;
|
||||
let (reconcile, manual_grab, manual_tv) =
|
||||
reconcile_loop(&database, &config, &transmission, tmdb.as_ref(), ¬ifier)?;
|
||||
// Issue #176: the on-demand half of the metadata lane needs its own
|
||||
// handle — the sweep's `SeriesRefreshAction` is owned by `ReconcileLoop`,
|
||||
// and the compat shim takes the other clone below.
|
||||
let metadata_tmdb = tmdb.clone();
|
||||
|
||||
// Jellyseerr's Radarr shim (DESIGN.md §9.4) reads the same database and
|
||||
// needs its own TMDB client for `movie/lookup`.
|
||||
@@ -162,12 +167,18 @@ async fn run() -> Result<(), Error> {
|
||||
// in the 64-slot buffer forever. Issue #132: the episode and season
|
||||
// channels are drained by the same lane.
|
||||
let mut manual_task = tokio::spawn(manual::run(
|
||||
state,
|
||||
database,
|
||||
state.clone(),
|
||||
database.clone(),
|
||||
manual_grab,
|
||||
manual_tv,
|
||||
shutdown_rx,
|
||||
shutdown_rx.clone(),
|
||||
));
|
||||
// Issue #176: adding a title queues a refresh here rather than waiting
|
||||
// for the daily sweep. Its own task, not an arm of `manual::run`: a
|
||||
// refresh can take a while against TMDB and must not sit in front of an
|
||||
// operator's manual search.
|
||||
let mut metadata_task =
|
||||
tokio::spawn(metadata::run(state, database, metadata_tmdb, shutdown_rx));
|
||||
let signal_tx = shutdown_tx.clone();
|
||||
let server = async move {
|
||||
axum::serve(listener, app)
|
||||
@@ -184,18 +195,28 @@ async fn run() -> Result<(), Error> {
|
||||
let _ = shutdown_tx.send(true);
|
||||
reconcile_task.await?;
|
||||
manual_task.await?;
|
||||
metadata_task.await?;
|
||||
result.map_err(Error::Serve)
|
||||
}
|
||||
result = &mut reconcile_task => {
|
||||
result?;
|
||||
let _ = shutdown_tx.send(true);
|
||||
manual_task.await?;
|
||||
metadata_task.await?;
|
||||
server.await.map_err(Error::Serve)
|
||||
}
|
||||
result = &mut manual_task => {
|
||||
result?;
|
||||
let _ = shutdown_tx.send(true);
|
||||
reconcile_task.await?;
|
||||
metadata_task.await?;
|
||||
server.await.map_err(Error::Serve)
|
||||
}
|
||||
result = &mut metadata_task => {
|
||||
result?;
|
||||
let _ = shutdown_tx.send(true);
|
||||
reconcile_task.await?;
|
||||
manual_task.await?;
|
||||
server.await.map_err(Error::Serve)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user