update: pipeline, search, clip, embedding fixes
This commit is contained in:
262
src/core/tmdb/cache.rs
Normal file
262
src/core/tmdb/cache.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user