feat: add per-library download detection auto-schedule

Adds a configurable schedule (manual/hourly/daily/weekly) for the
download detection job in the library settings modal. The indexer
scheduler triggers the job automatically, and the API job poller
processes it — consistent with the reading_status_push pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 13:57:59 +01:00
parent 19de3ceebb
commit e0d94758af
12 changed files with 212 additions and 35 deletions

View File

@@ -128,6 +128,75 @@ pub async fn check_and_schedule_reading_status_push(pool: &PgPool) -> Result<()>
Ok(())
}
pub async fn check_and_schedule_download_detection(pool: &PgPool) -> Result<()> {
// Only schedule if Prowlarr is configured
let prowlarr_configured: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM settings WHERE key = 'prowlarr' AND value->>'base_url' IS NOT NULL AND value->>'base_url' != '')"
)
.fetch_one(pool)
.await
.unwrap_or(false);
if !prowlarr_configured {
return Ok(());
}
let libraries = sqlx::query(
r#"
SELECT id, download_detection_mode
FROM libraries
WHERE download_detection_mode != 'manual'
AND (
next_download_detection_at IS NULL
OR next_download_detection_at <= NOW()
)
AND NOT EXISTS (
SELECT 1 FROM index_jobs
WHERE library_id = libraries.id
AND type = 'download_detection'
AND status IN ('pending', 'running')
)
"#
)
.fetch_all(pool)
.await?;
for row in libraries {
let library_id: Uuid = row.get("id");
let detection_mode: String = row.get("download_detection_mode");
info!("[SCHEDULER] Auto-running download detection for library {} (mode: {})", library_id, detection_mode);
let job_id = Uuid::new_v4();
sqlx::query(
"INSERT INTO index_jobs (id, library_id, type, status) VALUES ($1, $2, 'download_detection', 'pending')"
)
.bind(job_id)
.bind(library_id)
.execute(pool)
.await?;
let interval_minutes: i64 = match detection_mode.as_str() {
"hourly" => 60,
"daily" => 1440,
"weekly" => 10080,
_ => 1440,
};
sqlx::query(
"UPDATE libraries SET last_download_detection_at = NOW(), next_download_detection_at = NOW() + INTERVAL '1 minute' * $2 WHERE id = $1"
)
.bind(library_id)
.bind(interval_minutes)
.execute(pool)
.await?;
info!("[SCHEDULER] Created download_detection job {} for library {}", job_id, library_id);
}
Ok(())
}
pub async fn check_and_schedule_metadata_refreshes(pool: &PgPool) -> Result<()> {
let libraries = sqlx::query(
r#"

View File

@@ -35,6 +35,9 @@ pub async fn run_worker(state: AppState, interval_seconds: u64) {
if let Err(err) = scheduler::check_and_schedule_reading_status_push(&scheduler_state.pool).await {
error!("[SCHEDULER] Reading status push error: {}", err);
}
if let Err(err) = scheduler::check_and_schedule_download_detection(&scheduler_state.pool).await {
error!("[SCHEDULER] Download detection error: {}", err);
}
tokio::time::sleep(scheduler_wait).await;
}
});