From b7fb7929f0ac5f12a68c7dd2e9c5d6ffa20febca Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 22 Sep 2026 14:41:37 -0400 Subject: [PATCH] feat: add cache IPC commands for analysis caching Co-authored-by: Cursor --- src-tauri/src/commands/cache.rs | 153 ++++++++++++++++++++++++++++++++ src-tauri/src/commands/mod.rs | 1 + src-tauri/src/lib.rs | 9 +- 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src-tauri/src/commands/cache.rs diff --git a/src-tauri/src/commands/cache.rs b/src-tauri/src/commands/cache.rs new file mode 100644 index 0000000..82838a9 --- /dev/null +++ b/src-tauri/src/commands/cache.rs @@ -0,0 +1,153 @@ +use crate::models::{ThumbnailSpritesheet, WaveformTiers}; +use crate::services::cache_manager::{self, CacheManifest, CacheStats}; +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CachedAnalysis { + pub waveform_tiers: Option, + pub keyframe_positions: Vec, + pub thumbnail_spritesheets: Vec, + pub caption_file_path: Option, +} + +#[tauri::command] +pub async fn load_cached_analysis(url: String) -> Option { + let url_clone = url.clone(); + let manifest = tokio::task::spawn_blocking(move || cache_manager::load_manifest(&url_clone)) + .await + .ok() + .flatten()?; + + let cache_dir = cache_manager::cache_dir_for_url(&url); + + // Resolve relative paths in thumbnail spritesheets to absolute paths + let spritesheets: Vec = manifest + .thumbnail_spritesheets + .into_iter() + .map(|mut sheet| { + let abs_path = cache_dir.join(&sheet.file_path); + sheet.file_path = abs_path.to_string_lossy().to_string(); + sheet + }) + .collect(); + + // Verify thumbnail files actually exist on disk + let thumbs_valid = if spritesheets.is_empty() { + false + } else { + let first_dir = std::path::Path::new(&spritesheets[0].file_path); + first_dir.is_dir() + }; + + // Resolve caption path to absolute + let caption_path = manifest + .caption_file_path + .map(|rel| cache_dir.join(&rel).to_string_lossy().to_string()) + .filter(|p| std::path::Path::new(p).is_file()); + + Some(CachedAnalysis { + waveform_tiers: manifest.waveform_tiers, + keyframe_positions: manifest.keyframe_positions, + thumbnail_spritesheets: if thumbs_valid { + spritesheets + } else { + vec![] + }, + caption_file_path: caption_path, + }) +} + +#[tauri::command] +pub async fn save_analysis_to_cache( + url: String, + title: String, + duration: f64, + fps: f64, + waveform_tiers: Option, + keyframe_positions: Vec, + thumbnail_spritesheets: Vec, + caption_file_path: Option, +) -> Result<(), String> { + let cache_dir = cache_manager::cache_dir_for_url(&url); + + // Convert absolute spritesheet paths to relative + let relative_sheets: Vec = thumbnail_spritesheets + .into_iter() + .map(|mut sheet| { + if let Ok(rel) = std::path::Path::new(&sheet.file_path).strip_prefix(&cache_dir) { + sheet.file_path = rel.to_string_lossy().to_string(); + } + sheet + }) + .collect(); + + // Convert absolute caption path to relative + let relative_caption = caption_file_path.and_then(|abs| { + std::path::Path::new(&abs) + .strip_prefix(&cache_dir) + .ok() + .map(|rel| rel.to_string_lossy().to_string()) + }); + + let now = format!( + "{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + ); + + let manifest = CacheManifest { + version: 1, + source_url: url.clone(), + title, + duration, + fps, + created_at: now.clone(), + last_accessed_at: now, + waveform_tiers, + keyframe_positions, + thumbnail_spritesheets: relative_sheets, + caption_file_path: relative_caption, + }; + + tokio::task::spawn_blocking(move || cache_manager::save_manifest(&url, &manifest)) + .await + .map_err(|e| format!("Task failed: {e}"))? +} + +#[tauri::command] +pub async fn clear_video_cache(url: String) -> Result<(), String> { + tokio::task::spawn_blocking(move || cache_manager::clear_cache_entry(&url)) + .await + .map_err(|e| format!("Task failed: {e}"))? +} + +#[tauri::command] +pub async fn clear_all_cache() -> Result<(), String> { + tokio::task::spawn_blocking(cache_manager::clear_all_cache) + .await + .map_err(|e| format!("Task failed: {e}"))? +} + +#[tauri::command] +pub async fn get_cache_stats() -> Result { + tokio::task::spawn_blocking(cache_manager::get_cache_stats) + .await + .map_err(|e| format!("Task failed: {e}"))? +} + +#[tauri::command] +pub fn get_thumbnail_cache_dir(url: String) -> String { + let dir = cache_manager::thumbnail_cache_dir(&url); + std::fs::create_dir_all(&dir).ok(); + dir.to_string_lossy().to_string() +} + +#[tauri::command] +pub fn get_caption_cache_dir(url: String) -> String { + let dir = cache_manager::caption_cache_dir(&url); + std::fs::create_dir_all(&dir).ok(); + dir.to_string_lossy().to_string() +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index bcc3879..2e0f470 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,3 +1,4 @@ +pub mod cache; pub mod dependencies; pub mod export; pub mod media_analysis; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 135813e..fd40204 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -5,7 +5,7 @@ mod services; use std::collections::HashSet; use std::path::PathBuf; -use commands::{dependencies, export, media_analysis, video}; +use commands::{cache, dependencies, export, media_analysis, video}; use tauri::Manager; struct MediaServerPort(u16); @@ -123,6 +123,13 @@ pub fn run() { media_analysis::check_embedded_subtitles, media_analysis::list_system_fonts, export::export_clips, + cache::load_cached_analysis, + cache::save_analysis_to_cache, + cache::clear_video_cache, + cache::clear_all_cache, + cache::get_cache_stats, + cache::get_thumbnail_cache_dir, + cache::get_caption_cache_dir, get_media_server_port, ]) .run(tauri::generate_context!())