use std::fs; use std::path::{Path, PathBuf}; use sha2::{Digest, Sha256}; const PLUGIN_ID: &str = "com.jesshunter.layers"; const KICAD_MAJOR_DIR: &str = "KiCad/10.0/plugins"; pub fn plugin_data_dir() -> PathBuf { plugin_root() } pub fn plugin_dir() -> PathBuf { plugin_root() } pub fn data_dir() -> PathBuf { plugin_root() } pub fn state_dir() -> PathBuf { data_dir().join("state") } pub fn cache_root() -> PathBuf { data_dir().join("cache") } pub fn cache_dir(project_hash: &str) -> PathBuf { cache_root().join(project_hash) } pub fn preview_dir(project_hash: &str) -> PathBuf { cache_dir(project_hash).join("previews") } pub fn log_dir() -> PathBuf { data_dir().join("logs") } pub fn runtime_dir() -> PathBuf { data_dir().join("runtime") } pub fn settings_path() -> PathBuf { data_dir().join("settings.json") } pub fn state_path(project_hash: &str) -> PathBuf { state_dir().join(format!("{project_hash}.json")) } pub fn log_path() -> PathBuf { log_dir().join("layers.log") } pub fn project_hash(kicad_pro_path: &Path) -> String { let abs = match kicad_pro_path.canonicalize() { Ok(p) => p, Err(_) => kicad_pro_path.to_path_buf(), }; hash_bytes_hex16(abs.to_string_lossy().as_bytes()) } pub fn create_dirs_if_missing() -> std::io::Result<()> { for d in [ data_dir(), state_dir(), cache_root(), log_dir(), runtime_dir(), ] { fs::create_dir_all(d)?; } Ok(()) } #[cfg(target_os = "linux")] fn plugin_root() -> PathBuf { dirs::data_local_dir() .expect("XDG data-local dir unavailable; cannot resolve plugin data directory") .join(KICAD_MAJOR_DIR) .join(PLUGIN_ID) } #[cfg(any(target_os = "macos", target_os = "windows"))] fn plugin_root() -> PathBuf { dirs::document_dir() .expect("user Documents dir unavailable; cannot resolve plugin data directory") .join(KICAD_MAJOR_DIR) .join(PLUGIN_ID) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] fn plugin_root() -> PathBuf { dirs::data_local_dir() .expect("data-local dir unavailable; cannot resolve plugin data directory") .join(KICAD_MAJOR_DIR) .join(PLUGIN_ID) } fn hash_bytes_hex16(bytes: &[u8]) -> String { let digest = Sha256::digest(bytes); let mut s = String::with_capacity(16); for byte in &digest[..8] { use std::fmt::Write; let _ = write!(&mut s, "{byte:02x}"); } s } #[cfg(test)] mod tests { use super::*; #[test] fn project_hash_is_16_hex_chars() { let h = project_hash(Path::new("/tmp/example.kicad_pro")); assert_eq!(h.len(), 16); assert!(h.chars().all(|c| c.is_ascii_hexdigit())); } #[test] fn project_hash_is_deterministic() { let a = project_hash(Path::new("/tmp/a.kicad_pro")); let b = project_hash(Path::new("/tmp/a.kicad_pro")); assert_eq!(a, b); } #[test] fn subdirs_under_data_dir() { assert!(state_dir().starts_with(data_dir())); assert!(log_dir().starts_with(data_dir())); assert!(runtime_dir().starts_with(data_dir())); assert!(cache_root().starts_with(data_dir())); } #[test] fn log_path_is_layers_log() { assert_eq!(log_path().file_name().unwrap(), "layers.log"); } }