yggdrasil/tasks/
orphan_asset_purge.rs1use std::time::Duration;
13
14use tokio::time::interval;
15
16use crate::api::assets::delete::purge_orphan_assets_impl;
17use crate::api::assets::types::PurgeOrphansResponse;
18use crate::api::error::{ApiError, AppError};
19use crate::db::pool::get_conn;
20use crate::models::settings::AssetPurgeSettings;
21
22pub async fn run_purge() {
28 let mut ticker = interval(Duration::from_secs(86400));
29 loop {
30 match purge_orphans().await {
31 Ok(Some(res)) if res.deleted_count > 0 || res.failures > 0 => tracing::info!(
32 "Orphan asset purge: removed {} assets, freed {} bytes, {} file failures",
33 res.deleted_count,
34 res.freed_bytes,
35 res.failures
36 ),
37 Ok(_) => {}
38 Err(e) => tracing::error!("Orphan asset purge error: {}", e),
39 }
40 ticker.tick().await;
41 }
42}
43
44async fn purge_orphans() -> Result<Option<PurgeOrphansResponse>, ApiError> {
51 let client = get_conn().await.map_err(AppError::db_conn)?;
52 let setting = |key: &'static str| {
53 let client = &client;
54 async move {
55 client
56 .query_opt("SELECT value FROM settings WHERE key = $1", &[&key])
57 .await
58 .map(|row| row.map(|r| r.get::<_, String>("value")))
59 .map_err(AppError::query)
60 }
61 };
62
63 let enabled = setting("asset_orphan_purge_enabled")
65 .await?
66 .and_then(|v| v.parse().ok())
67 .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_PURGE_ENABLED);
68 if !enabled {
69 return Ok(None);
70 }
71 let days = setting("asset_orphan_retention_days")
72 .await?
73 .and_then(|v| v.parse().ok())
74 .unwrap_or(crate::models::settings::DEFAULT_ASSET_ORPHAN_RETENTION_DAYS);
75 drop(client);
77
78 purge_orphan_assets_impl(AssetPurgeSettings::clamp_retention(days))
79 .await
80 .map(Some)
81}