use crate::AppState; use base64::{engine::general_purpose::STANDARD, Engine}; use std::path::{Path, PathBuf}; use tauri::State; fn safe_kind(kind: &str) -> String { kind.chars() .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_') .collect::() .to_lowercase() } fn sanitize_name(name: &str) -> String { name.chars() .filter(|c| c.is_ascii_alphanumeric() || *c == '.' || *c == '-' || *c == '_') .collect() } fn extension_of(path: &str) -> String { Path::new(path) .extension() .and_then(|e| e.to_str()) .map(|e| e.to_lowercase()) .unwrap_or_else(|| "png".to_string()) } fn mime_for(ext: &str) -> &'static str { match ext { "jpg" | "jpeg" => "image/jpeg", "svg" => "image/svg+xml", "webp" => "image/webp", "gif" => "image/gif", _ => "image/png", } } fn assets_dir(state: &State) -> Result { let dir = state.data_dir.join("assets"); std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; Ok(dir) } fn write_asset(state: &State, kind: &str, ext: &str, bytes: &[u8]) -> Result { let dir = assets_dir(state)?; let file = format!( "{}-{}.{}", safe_kind(kind), uuid::Uuid::new_v4().simple(), ext ); let path = dir.join(file); std::fs::write(&path, bytes).map_err(|e| e.to_string())?; Ok(path.to_string_lossy().to_string()) } /// Copy a user-picked file into the app's asset store and return the stored path. #[tauri::command] pub fn import_asset( state: State, source_path: String, kind: String, ) -> Result { let bytes = std::fs::read(&source_path).map_err(|e| e.to_string())?; let ext = extension_of(&source_path); write_asset(&state, &kind, &ext, &bytes) } /// Used when the frontend already holds the bytes (base64) instead of a path. #[tauri::command] pub fn save_asset_bytes( state: State, kind: String, file_name: String, data_base64: String, ) -> Result { let bytes = STANDARD .decode(data_base64.as_bytes()) .map_err(|e| e.to_string())?; let ext = extension_of(&sanitize_name(&file_name)); write_asset(&state, &kind, &ext, &bytes) } /// Read a stored asset back as a data URI so it can be embedded in the PDF. #[tauri::command] pub fn read_asset_data_uri(path: String) -> Result { let bytes = std::fs::read(&path).map_err(|e| e.to_string())?; let ext = extension_of(&path); Ok(format!( "data:{};base64,{}", mime_for(&ext), STANDARD.encode(bytes) )) } #[tauri::command] pub fn remove_asset(state: State, path: String) -> Result<(), String> { let dir = assets_dir(&state)?; let target = PathBuf::from(&path); // Only allow deleting files inside the managed assets directory. if target.starts_with(&dir) && target.exists() { std::fs::remove_file(&target).map_err(|e| e.to_string())?; } Ok(()) } /// Write a base64 payload (e.g. a generated PDF) to a user-chosen path. #[tauri::command] pub fn save_binary_file(path: String, data_base64: String) -> Result<(), String> { let bytes = STANDARD .decode(data_base64.as_bytes()) .map_err(|e| e.to_string())?; if let Some(parent) = Path::new(&path).parent() { std::fs::create_dir_all(parent).ok(); } std::fs::write(&path, bytes).map_err(|e| e.to_string()) }