use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::config::AppConfig; use crate::error::{PixstripError, Result}; use crate::preset::Preset; fn default_config_dir() -> PathBuf { dirs::config_dir() .unwrap_or_else(|| PathBuf::from("~/.config")) .join("pixstrip") } fn sanitize_filename(name: &str) -> String { name.chars() .map(|c| match c { '/' | '\\' | '<' | '>' | ':' | '"' | '|' | '?' | '*' => '_', _ => c, }) .collect() } // --- Preset Store --- pub struct PresetStore { presets_dir: PathBuf, } impl PresetStore { pub fn new() -> Self { Self { presets_dir: default_config_dir().join("presets"), } } pub fn with_base_dir(base: &Path) -> Self { Self { presets_dir: base.join("presets"), } } fn ensure_dir(&self) -> Result<()> { std::fs::create_dir_all(&self.presets_dir).map_err(PixstripError::Io) } fn preset_path(&self, name: &str) -> PathBuf { let filename = format!("{}.json", sanitize_filename(name)); self.presets_dir.join(filename) } pub fn save(&self, preset: &Preset) -> Result<()> { self.ensure_dir()?; let path = self.preset_path(&preset.name); let json = serde_json::to_string_pretty(preset) .map_err(|e| PixstripError::Preset(e.to_string()))?; std::fs::write(&path, json).map_err(PixstripError::Io) } pub fn load(&self, name: &str) -> Result { let path = self.preset_path(name); if !path.exists() { return Err(PixstripError::Preset(format!( "Preset '{}' not found", name ))); } let data = std::fs::read_to_string(&path).map_err(PixstripError::Io)?; serde_json::from_str(&data).map_err(|e| PixstripError::Preset(e.to_string())) } pub fn list(&self) -> Result> { if !self.presets_dir.exists() { return Ok(Vec::new()); } let mut presets = Vec::new(); for entry in std::fs::read_dir(&self.presets_dir).map_err(PixstripError::Io)? { let entry = entry.map_err(PixstripError::Io)?; let path = entry.path(); if path.extension().and_then(|e| e.to_str()) == Some("json") { let data = std::fs::read_to_string(&path).map_err(PixstripError::Io)?; if let Ok(preset) = serde_json::from_str::(&data) { presets.push(preset); } } } presets.sort_by(|a, b| a.name.cmp(&b.name)); Ok(presets) } pub fn delete(&self, name: &str) -> Result<()> { let path = self.preset_path(name); if !path.exists() { return Err(PixstripError::Preset(format!( "Preset '{}' not found", name ))); } std::fs::remove_file(&path).map_err(PixstripError::Io) } pub fn export_to_file(&self, preset: &Preset, path: &Path) -> Result<()> { let json = serde_json::to_string_pretty(preset) .map_err(|e| PixstripError::Preset(e.to_string()))?; std::fs::write(path, json).map_err(PixstripError::Io) } pub fn import_from_file(&self, path: &Path) -> Result { let data = std::fs::read_to_string(path).map_err(PixstripError::Io)?; let mut preset: Preset = serde_json::from_str(&data).map_err(|e| PixstripError::Preset(e.to_string()))?; preset.is_custom = true; Ok(preset) } } impl Default for PresetStore { fn default() -> Self { Self::new() } } // --- Config Store --- pub struct ConfigStore { config_path: PathBuf, } impl ConfigStore { pub fn new() -> Self { Self { config_path: default_config_dir().join("config.json"), } } pub fn with_base_dir(base: &Path) -> Self { Self { config_path: base.join("config.json"), } } pub fn save(&self, config: &AppConfig) -> Result<()> { if let Some(parent) = self.config_path.parent() { std::fs::create_dir_all(parent).map_err(PixstripError::Io)?; } let json = serde_json::to_string_pretty(config) .map_err(|e| PixstripError::Config(e.to_string()))?; std::fs::write(&self.config_path, json).map_err(PixstripError::Io) } pub fn load(&self) -> Result { if !self.config_path.exists() { return Ok(AppConfig::default()); } let data = std::fs::read_to_string(&self.config_path).map_err(PixstripError::Io)?; serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string())) } } impl Default for ConfigStore { fn default() -> Self { Self::new() } } // --- Session Store --- #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct SessionState { pub last_input_dir: Option, pub last_output_dir: Option, pub last_preset_name: Option, pub current_step: u32, pub window_width: Option, pub window_height: Option, pub window_maximized: bool, // Last-used wizard settings pub resize_enabled: Option, pub resize_width: Option, pub resize_height: Option, pub convert_enabled: Option, pub convert_format: Option, pub compress_enabled: Option, pub quality_preset: Option, pub metadata_enabled: Option, pub metadata_mode: Option, pub watermark_enabled: Option, pub rename_enabled: Option, } pub struct SessionStore { session_path: PathBuf, } impl SessionStore { pub fn new() -> Self { Self { session_path: default_config_dir().join("session.json"), } } pub fn with_base_dir(base: &Path) -> Self { Self { session_path: base.join("session.json"), } } pub fn save(&self, state: &SessionState) -> Result<()> { if let Some(parent) = self.session_path.parent() { std::fs::create_dir_all(parent).map_err(PixstripError::Io)?; } let json = serde_json::to_string_pretty(state) .map_err(|e| PixstripError::Config(e.to_string()))?; std::fs::write(&self.session_path, json).map_err(PixstripError::Io) } pub fn load(&self) -> Result { if !self.session_path.exists() { return Ok(SessionState::default()); } let data = std::fs::read_to_string(&self.session_path).map_err(PixstripError::Io)?; serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string())) } } impl Default for SessionStore { fn default() -> Self { Self::new() } } // --- History Store --- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HistoryEntry { pub timestamp: String, pub input_dir: String, pub output_dir: String, pub preset_name: Option, pub total: usize, pub succeeded: usize, pub failed: usize, pub total_input_bytes: u64, pub total_output_bytes: u64, pub elapsed_ms: u64, pub output_files: Vec, } pub struct HistoryStore { history_path: PathBuf, } impl HistoryStore { pub fn new() -> Self { Self { history_path: default_config_dir().join("history.json"), } } pub fn with_base_dir(base: &Path) -> Self { Self { history_path: base.join("history.json"), } } pub fn add(&self, entry: HistoryEntry) -> Result<()> { let mut entries = self.list()?; entries.push(entry); self.write_all(&entries) } pub fn list(&self) -> Result> { if !self.history_path.exists() { return Ok(Vec::new()); } let data = std::fs::read_to_string(&self.history_path).map_err(PixstripError::Io)?; if data.trim().is_empty() { return Ok(Vec::new()); } serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string())) } pub fn clear(&self) -> Result<()> { self.write_all(&Vec::::new()) } fn write_all(&self, entries: &[HistoryEntry]) -> Result<()> { if let Some(parent) = self.history_path.parent() { std::fs::create_dir_all(parent).map_err(PixstripError::Io)?; } let json = serde_json::to_string_pretty(entries) .map_err(|e| PixstripError::Config(e.to_string()))?; std::fs::write(&self.history_path, json).map_err(PixstripError::Io) } } impl Default for HistoryStore { fn default() -> Self { Self::new() } }