pub mod backend;
pub mod key;
pub mod sqlite;
use std::time::Duration;
pub use backend::{CacheBackend, CacheError};
use chrono::{NaiveDate, Utc};
pub use key::{CacheKey, MediaType};
pub use sqlite::SqliteCache;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct CacheTtlConfig {
pub details: Duration,
pub search: Duration,
pub discovery: Duration,
pub items: Duration,
pub old_content_threshold_days: u32,
pub recent_content_threshold_days: u32,
pub old_content_details_ttl: Duration,
pub recent_content_details_ttl: Duration,
pub active_content_details_ttl: Duration,
}
impl Default for CacheTtlConfig {
fn default() -> Self {
Self {
details: Duration::from_secs(24 * 3600), search: Duration::from_secs(3600), discovery: Duration::from_secs(15 * 60), items: Duration::from_secs(6 * 3600), old_content_threshold_days: 1095, recent_content_threshold_days: 180, old_content_details_ttl: Duration::from_secs(7 * 24 * 3600), recent_content_details_ttl: Duration::from_secs(6 * 3600), active_content_details_ttl: Duration::from_secs(4 * 3600), }
}
}
impl CacheTtlConfig {
fn parse_date(s: &str) -> Option<NaiveDate> {
NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()
}
fn days_since(date: NaiveDate) -> i64 {
(Utc::now().date_naive() - date).num_days()
}
pub fn movie_details_ttl(&self, release_date: Option<&str>, status: Option<&str>) -> Duration {
let release_days = release_date
.and_then(Self::parse_date)
.map(Self::days_since);
let is_released = status.map(|s| s == "Released").unwrap_or(false);
let is_old = release_days
.map(|d| d > self.old_content_threshold_days as i64)
.unwrap_or(false);
if is_released && is_old {
return self.old_content_details_ttl;
}
let is_in_production = matches!(
status,
Some("In Production") | Some("Post Production") | Some("Planned") | Some("Rumored")
);
let is_recent = release_days
.map(|d| d <= self.recent_content_threshold_days as i64)
.unwrap_or(false);
if is_in_production || is_recent {
return self.recent_content_details_ttl;
}
self.details
}
pub fn tv_show_details_ttl(
&self,
first_air_date: Option<&str>,
last_air_date: Option<&str>,
status: Option<&str>,
in_production: bool,
) -> Duration {
let last_air_days = last_air_date
.and_then(Self::parse_date)
.map(Self::days_since);
let recently_aired = last_air_days.map(|d| d <= 90).unwrap_or(false);
if in_production || recently_aired {
return self.active_content_details_ttl;
}
let first_air_days = first_air_date
.and_then(Self::parse_date)
.map(Self::days_since);
let is_ended = matches!(status, Some("Ended") | Some("Canceled"));
let is_old = first_air_days
.map(|d| d > self.old_content_threshold_days as i64)
.unwrap_or(false);
if is_ended && is_old {
return self.old_content_details_ttl;
}
let is_returning = matches!(
status,
Some("Returning Series") | Some("In Production") | Some("Planned")
);
let is_recent = first_air_days
.map(|d| d <= self.recent_content_threshold_days as i64)
.unwrap_or(false);
if is_returning || is_recent {
return self.recent_content_details_ttl;
}
self.details
}
}