Spaces:
Paused
Paused
Download api/src/cleanup.rs from stremio-db/a: direct link, hf CLI and curl.
- Browser
- Download file 4.78 kB
-
https://huggingface.co/spaces/stremio-db/a/resolve/main/api/src/cleanup.rs
- Command line
-
hf download hf://spaces/stremio-db/a/api/src/cleanup.rs
-
curl -L -o cleanup.rs https://huggingface.co/spaces/stremio-db/a/resolve/main/api/src/cleanup.rs
4.78 kB
| use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, ActiveValue::Set}; | |
| use crate::entity::{available_ratings, cleanup_state, image_meta}; | |
| use crate::AppState; | |
| const CLEANUP_INTERVAL_SECS: i64 = 7 * 24 * 3600; | |
| fn now_secs() -> i64 { | |
| std::time::SystemTime::now() | |
| .duration_since(std::time::UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_secs() as i64 | |
| } | |
| /// Checks whether a cleanup run is overdue (last run more than 7 days ago, | |
| /// or never run) and if so, runs it. Called periodically from a background | |
| /// task. Reading a persisted timestamp (rather than an in-memory one) means | |
| /// a run that was missed while the container was asleep/restarted still | |
| /// happens promptly on the next check after waking, instead of being | |
| /// silently skipped. | |
| pub async fn run_if_due(state: &AppState) { | |
| let now = now_secs(); | |
| let last_run = match cleanup_state::Entity::find_by_id(true).one(&state.db).await { | |
| Ok(Some(row)) => row.last_run, | |
| Ok(None) => None, | |
| Err(e) => { | |
| tracing::warn!(error = %e, "failed to read cleanup_state, skipping this check"); | |
| return; | |
| } | |
| }; | |
| let due = match last_run { | |
| Some(t) => now - t >= CLEANUP_INTERVAL_SECS, | |
| None => true, | |
| }; | |
| if !due { | |
| return; | |
| } | |
| run_cleanup(state, false).await; | |
| let model = cleanup_state::ActiveModel { | |
| id: Set(true), | |
| last_run: Set(Some(now)), | |
| }; | |
| if let Err(e) = cleanup_state::Entity::insert(model) | |
| .on_conflict( | |
| sea_orm::sea_query::OnConflict::column(cleanup_state::Column::Id) | |
| .update_columns([cleanup_state::Column::LastRun]) | |
| .to_owned(), | |
| ) | |
| .exec(&state.db) | |
| .await | |
| { | |
| tracing::warn!(error = %e, "failed to update cleanup_state.last_run"); | |
| } | |
| } | |
| /// Deletes image_meta and available_ratings rows older than 7 days, and | |
| /// purges the corresponding Cloudflare Cache-Tags. Lazy: does not | |
| /// re-render anything, the next real request for a purged title is simply | |
| /// treated as a cache miss. In dry_run mode, only logs what would be | |
| /// deleted. | |
| pub async fn run_cleanup(state: &AppState, dry_run: bool) { | |
| let cutoff = now_secs() - CLEANUP_INTERVAL_SECS; | |
| let expired_images = match image_meta::Entity::find() | |
| .filter(image_meta::Column::UpdatedAt.lt(cutoff)) | |
| .all(&state.db) | |
| .await | |
| { | |
| Ok(rows) => rows, | |
| Err(e) => { | |
| tracing::warn!(error = %e, "cleanup: failed to query expired image_meta rows"); | |
| return; | |
| } | |
| }; | |
| if expired_images.is_empty() { | |
| tracing::info!("cleanup: no expired image_meta rows found"); | |
| return; | |
| } | |
| tracing::info!(count = expired_images.len(), dry_run, "cleanup: expired image_meta rows found"); | |
| if dry_run { | |
| for row in &expired_images { | |
| tracing::info!(cache_key = %row.cache_key, "cleanup (dry run): would delete"); | |
| } | |
| return; | |
| } | |
| // Cloudflare tags are keyed "id:{id_type}:{id_value}" derived from the | |
| // cache_key prefix (before the settings suffix), matching the format | |
| // purge_cloudflare_tags() already expects. | |
| let prefixes: Vec<String> = expired_images | |
| .iter() | |
| .filter_map(|row| { | |
| let parts: Vec<&str> = row.cache_key.splitn(2, '/').collect(); | |
| if parts.len() == 2 { | |
| let id_part = parts[1].split(|c| c == '@' || c == '.').next().unwrap_or(parts[1]); | |
| Some(format!("{}/{}", parts[0], id_part)) | |
| } else { | |
| None | |
| } | |
| }) | |
| .collect(); | |
| match crate::services::stremio_account::purge_cloudflare_tags(&state.http, &prefixes).await { | |
| Ok(n) => tracing::info!(purged = n, "cleanup: Cloudflare tags purged"), | |
| Err(e) => tracing::warn!(error = %e, "cleanup: Cloudflare purge failed, continuing with Postgres delete anyway"), | |
| } | |
| let cache_keys: Vec<String> = expired_images.iter().map(|r| r.cache_key.clone()).collect(); | |
| match image_meta::Entity::delete_many() | |
| .filter(image_meta::Column::CacheKey.is_in(cache_keys)) | |
| .exec(&state.db) | |
| .await | |
| { | |
| Ok(res) => tracing::info!(deleted = res.rows_affected, "cleanup: image_meta rows deleted"), | |
| Err(e) => tracing::warn!(error = %e, "cleanup: failed to delete expired image_meta rows"), | |
| } | |
| match available_ratings::Entity::delete_many() | |
| .filter(available_ratings::Column::UpdatedAt.lt(cutoff)) | |
| .exec(&state.db) | |
| .await | |
| { | |
| Ok(res) => tracing::info!(deleted = res.rows_affected, "cleanup: available_ratings rows deleted"), | |
| Err(e) => tracing::warn!(error = %e, "cleanup: failed to delete expired available_ratings rows"), | |
| } | |
| } |