265 lines
8.6 KiB
Rust
265 lines
8.6 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::core::config::OUTPUT_DIR;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TmdbCacheIdentity {
|
|
pub identity_uuid: String,
|
|
pub name: String,
|
|
pub tmdb_id: u64,
|
|
pub character: String,
|
|
pub order: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TmdbCache {
|
|
pub file_uuid: String,
|
|
pub fetched_at: String,
|
|
pub source: String,
|
|
pub movie: TmdbMovie,
|
|
pub cast_count: usize,
|
|
pub identities_created: usize,
|
|
#[serde(default)]
|
|
pub identities: Vec<TmdbCacheIdentity>,
|
|
#[serde(default)]
|
|
pub cast: Vec<TmdbCastMember>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TmdbMovie {
|
|
pub tmdb_id: u64,
|
|
pub title: String,
|
|
pub release_date: Option<String>,
|
|
pub overview: Option<String>,
|
|
pub poster_path: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TmdbCastMember {
|
|
pub name: String,
|
|
pub character: String,
|
|
pub profile_path: Option<String>,
|
|
pub order: u32,
|
|
pub id: u64,
|
|
// Person detail fields from /person/{id}
|
|
pub biography: Option<String>,
|
|
pub birthday: Option<String>,
|
|
pub place_of_birth: Option<String>,
|
|
#[serde(default)]
|
|
pub also_known_as: Vec<String>,
|
|
pub imdb_id: Option<String>,
|
|
pub known_for_department: Option<String>,
|
|
pub popularity: Option<f64>,
|
|
pub deathday: Option<String>,
|
|
pub gender: Option<i32>,
|
|
pub homepage: Option<String>,
|
|
}
|
|
|
|
pub fn tmdb_cache_path(file_uuid: &str) -> PathBuf {
|
|
PathBuf::from(&*OUTPUT_DIR).join(format!("{}.tmdb.json", file_uuid))
|
|
}
|
|
|
|
pub fn read_tmdb_cache(file_uuid: &str) -> Result<TmdbCache> {
|
|
let path = tmdb_cache_path(file_uuid);
|
|
if !path.exists() {
|
|
anyhow::bail!(
|
|
"TMDb cache not found: {} (expected: {})",
|
|
file_uuid,
|
|
path.display()
|
|
);
|
|
}
|
|
let content = std::fs::read_to_string(&path)
|
|
.with_context(|| format!("Failed to read TMDb cache: {}", path.display()))?;
|
|
serde_json::from_str(&content)
|
|
.map_err(|e| anyhow::anyhow!("Invalid TMDb cache JSON {}: {}", path.display(), e))
|
|
}
|
|
|
|
pub fn write_tmdb_cache(cache: &TmdbCache) -> Result<()> {
|
|
let path = tmdb_cache_path(&cache.file_uuid);
|
|
let json = serde_json::to_string_pretty(cache)
|
|
.with_context(|| format!("Failed to serialize TMDb cache: {}", cache.file_uuid))?;
|
|
std::fs::write(&path, &json)
|
|
.with_context(|| format!("Failed to write TMDb cache: {}", path.display()))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn delete_tmdb_cache(file_uuid: &str) -> Result<()> {
|
|
let path = tmdb_cache_path(file_uuid);
|
|
if path.exists() {
|
|
std::fs::remove_file(&path)
|
|
.with_context(|| format!("Failed to delete TMDb cache: {}", path.display()))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn count_cache_files() -> usize {
|
|
let dir = PathBuf::from(&*OUTPUT_DIR);
|
|
match std::fs::read_dir(&dir) {
|
|
Ok(entries) => entries
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| e.file_name().to_string_lossy().ends_with(".tmdb.json"))
|
|
.count(),
|
|
Err(_) => 0,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn count_cache_files_at(base: &std::path::Path) -> usize {
|
|
match std::fs::read_dir(base) {
|
|
Ok(entries) => entries
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| e.file_name().to_string_lossy().ends_with(".tmdb.json"))
|
|
.count(),
|
|
Err(_) => 0,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn write_tmdb_cache_at(base: &std::path::Path, cache: &TmdbCache) -> Result<()> {
|
|
std::fs::create_dir_all(base)?;
|
|
let path = base.join(format!("{}.tmdb.json", cache.file_uuid));
|
|
let json = serde_json::to_string_pretty(cache)?;
|
|
std::fs::write(&path, &json)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn read_tmdb_cache_at(base: &std::path::Path, file_uuid: &str) -> Result<TmdbCache> {
|
|
let path = base.join(format!("{}.tmdb.json", file_uuid));
|
|
if !path.exists() {
|
|
anyhow::bail!("Cache not found");
|
|
}
|
|
let content = std::fs::read_to_string(&path)?;
|
|
serde_json::from_str(&content).map_err(Into::into)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn sample_cache(file_uuid: &str) -> TmdbCache {
|
|
TmdbCache {
|
|
file_uuid: file_uuid.to_string(),
|
|
fetched_at: "2026-05-16T12:00:00+00:00".to_string(),
|
|
source: "agent".to_string(),
|
|
movie: TmdbMovie {
|
|
tmdb_id: 4808,
|
|
title: "Charade".to_string(),
|
|
release_date: Some("1963-12-05".to_string()),
|
|
overview: Some("A romantic thriller...".to_string()),
|
|
poster_path: Some("/abc.jpg".to_string()),
|
|
},
|
|
cast: vec![
|
|
TmdbCastMember {
|
|
name: "Cary Grant".to_string(),
|
|
character: "Peter Joshua".to_string(),
|
|
profile_path: Some("/cary.jpg".to_string()),
|
|
order: 0,
|
|
id: 112,
|
|
biography: Some("Archibald Alec Leach...".to_string()),
|
|
birthday: Some("1904-01-18".to_string()),
|
|
place_of_birth: Some("Bristol, England, UK".to_string()),
|
|
also_known_as: vec!["Archie Leach".to_string()],
|
|
imdb_id: Some("nm0000026".to_string()),
|
|
known_for_department: Some("Acting".to_string()),
|
|
popularity: Some(28.3),
|
|
deathday: Some("1986-11-29".to_string()),
|
|
gender: Some(2),
|
|
homepage: None,
|
|
},
|
|
TmdbCastMember {
|
|
name: "Audrey Hepburn".to_string(),
|
|
character: "Regina Lampert".to_string(),
|
|
profile_path: Some("/audrey.jpg".to_string()),
|
|
order: 1,
|
|
id: 113,
|
|
biography: Some("Audrey Kathleen Hepburn...".to_string()),
|
|
birthday: Some("1929-05-04".to_string()),
|
|
place_of_birth: Some("Ixelles, Belgium".to_string()),
|
|
also_known_as: vec!["Edda van Heemstra".to_string()],
|
|
imdb_id: Some("nm0000030".to_string()),
|
|
known_for_department: Some("Acting".to_string()),
|
|
popularity: Some(35.7),
|
|
deathday: Some("1993-01-20".to_string()),
|
|
gender: Some(1),
|
|
homepage: None,
|
|
},
|
|
],
|
|
cast_count: 20,
|
|
identities_created: 0,
|
|
identities: vec![],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_cache_path_format() {
|
|
let p = tmdb_cache_path("abcdef");
|
|
assert!(p.to_string_lossy().ends_with("abcdef.tmdb.json"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_serde_roundtrip() {
|
|
let cache = sample_cache("aaaaaaaa");
|
|
let json = serde_json::to_string_pretty(&cache).unwrap();
|
|
let parsed: TmdbCache = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(parsed.file_uuid, "aaaaaaaa");
|
|
assert_eq!(parsed.movie.title, "Charade");
|
|
assert_eq!(parsed.cast.len(), 2);
|
|
assert_eq!(parsed.cast[0].name, "Cary Grant");
|
|
assert_eq!(parsed.movie.tmdb_id, 4808);
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_then_read_cache_at() {
|
|
let tmp = std::env::temp_dir().join("momentry_test_cache");
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
let base = &tmp;
|
|
|
|
let cache = sample_cache("bbbbbbbb");
|
|
write_tmdb_cache_at(base, &cache).unwrap();
|
|
|
|
let read = read_tmdb_cache_at(base, "bbbbbbbb").unwrap();
|
|
assert_eq!(read.movie.title, "Charade");
|
|
assert_eq!(read.cast[1].id, 113);
|
|
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_missing_cache_at_errors() {
|
|
let tmp = std::env::temp_dir().join("momentry_test_missing");
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
let base = &tmp;
|
|
|
|
let result = read_tmdb_cache_at(base, "nonexistent");
|
|
assert!(result.is_err());
|
|
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
}
|
|
|
|
#[test]
|
|
fn test_count_cache_files_at() {
|
|
let tmp = std::env::temp_dir().join("momentry_test_count");
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
let base = &tmp;
|
|
|
|
assert_eq!(count_cache_files_at(base), 0);
|
|
|
|
let c1 = sample_cache("aaa");
|
|
write_tmdb_cache_at(base, &c1).unwrap();
|
|
assert_eq!(count_cache_files_at(base), 1);
|
|
|
|
let c2 = sample_cache("bbb");
|
|
write_tmdb_cache_at(base, &c2).unwrap();
|
|
assert_eq!(count_cache_files_at(base), 2);
|
|
|
|
std::fs::write(base.join("other.json"), "{}").unwrap();
|
|
assert_eq!(count_cache_files_at(base), 2);
|
|
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
}
|
|
}
|