use crate::theme::Settings; use std::path::Path; const SETTINGS_FILE: &str = "settings.json"; pub fn settings_dir() -> std::path::PathBuf { if let Ok(exe) = std::env::current_exe() { if let Some(dir) = exe.parent() { return dir.to_path_buf(); } } std::path::PathBuf::from(".") } pub fn save_settings(dir: &Path, settings: &Settings) -> Result<(), String> { let path = dir.join(SETTINGS_FILE); let json = serde_json::to_string_pretty(settings) .map_err(|e| format!("序列化设置失败: {}", e))?; std::fs::write(&path, json) .map_err(|e| format!("保存设置失败: {}", e))?; Ok(()) } pub fn load_settings(dir: &Path) -> Result { let path = dir.join(SETTINGS_FILE); let json = std::fs::read_to_string(&path) .map_err(|e| format!("读取设置失败: {}", e))?; let mut settings: Settings = serde_json::from_str(&json) .map_err(|e| format!("解析设置失败: {}", e))?; // Always refresh profiles from code-defined presets so that updated // preset values (font size, spacing, etc.) take effect even when // an older settings.json is present on disk. let preset_names: Vec = crate::style::StyleProfile::presets() .iter() .map(|p| p.name.clone()) .collect(); settings.profiles = crate::style::StyleProfile::presets(); if !preset_names.contains(&settings.active_profile) { settings.active_profile = "Kindle 默认".into(); } Ok(settings) } #[cfg(test)] mod tests { use super::*; use crate::theme::{Settings, Theme}; #[test] fn test_save_and_load_settings() { let dir = std::env::temp_dir().join("epub-read-test"); let _ = std::fs::create_dir_all(&dir); let mut s = Settings::default(); s.font_size = 24.0; s.theme = Theme::Dark; save_settings(&dir, &s).unwrap(); let loaded = load_settings(&dir).unwrap(); assert_eq!(loaded.font_size, 24.0); assert_eq!(loaded.theme, Theme::Dark); let _ = std::fs::remove_dir_all(&dir); } #[test] fn test_load_settings_nonexistent() { let dir = std::env::temp_dir().join("epub-read-test-nonexistent"); let _ = std::fs::remove_dir_all(&dir); let result = load_settings(&dir); assert!(result.is_err()); } }