use std::{ env, fs, path::{Path, PathBuf}, }; use base64::{engine::general_purpose::STANDARD, Engine as _}; use serde_json::Value; pub fn local_app_data_dir() -> Option { env::var_os("LOCALAPPDATA").map(PathBuf::from).or_else(|| { env::var_os("USERPROFILE") .map(PathBuf::from) .map(|path| path.join("AppData").join("Local")) }) } pub fn pick_latest_subdirectory(root: &Path) -> Option { let entries = fs::read_dir(root).ok()?; let mut candidates = entries .flatten() .filter_map(|entry| { let file_type = entry.file_type().ok()?; if !file_type.is_dir() { return None; } let metadata = entry.metadata().ok()?; let modified = metadata.modified().ok(); Some(( modified, entry.file_name().to_string_lossy().to_string(), entry.path(), )) }) .collect::>(); candidates.sort_by(|left, right| right.0.cmp(&left.0).then_with(|| right.1.cmp(&left.1))); candidates.into_iter().next().map(|(_, _, path)| path) } pub fn load_image_as_data_url(path: &Path) -> Option { let bytes = fs::read(path).ok()?; let extension = path .extension() .and_then(|value| value.to_str()) .map(|value| value.to_ascii_lowercase())?; let mime_type = match extension.as_str() { "png" => "image/png", "jpg" | "jpeg" => "image/jpeg", "webp" => "image/webp", "gif" => "image/gif", "svg" => "image/svg+xml", _ => return None, }; Some(format!( "data:{mime_type};base64,{}", STANDARD.encode(bytes) )) } pub fn read_json_file(path: &Path) -> Option { let content = fs::read_to_string(path).ok()?; serde_json::from_str(&content).ok() } pub fn first_non_empty<'a>(values: impl IntoIterator>) -> Option<&'a str> { values .into_iter() .flatten() .find(|value| !value.trim().is_empty()) }