onedrop_engine/
safe_loader.rs1use crate::engine::MilkEngine;
4use crate::error::{EngineError, Result};
5use std::path::Path;
6
7pub struct SafePresetLoader;
9
10impl SafePresetLoader {
11 pub fn load_with_fallback<P: AsRef<Path>>(engine: &mut MilkEngine, path: P) -> Result<()> {
18 let path_ref = path.as_ref();
19
20 match engine.load_preset(path_ref) {
21 Ok(()) => {
22 log::info!("Successfully loaded preset: {}", path_ref.display());
23 Ok(())
24 }
25 Err(e) => {
26 log::error!(
27 "Failed to load preset {}: {}. Loading default preset.",
28 path_ref.display(),
29 e
30 );
31
32 match engine.load_default_preset() {
34 Ok(()) => {
35 log::info!("Successfully loaded default preset as fallback");
36 Ok(())
37 }
38 Err(fallback_err) => {
39 log::error!("CRITICAL: Failed to load default preset: {}", fallback_err);
41 Err(EngineError::PresetLoadFailed(format!(
42 "Both preset and fallback failed: {} / {}",
43 e, fallback_err
44 )))
45 }
46 }
47 }
48 }
49 }
50
51 pub fn load_with_retry<P: AsRef<Path>>(
58 engine: &mut MilkEngine,
59 path: P,
60 max_retries: usize,
61 ) -> Result<()> {
62 let path_ref = path.as_ref();
63 let mut last_error = None;
64
65 for attempt in 0..=max_retries {
66 match engine.load_preset(path_ref) {
67 Ok(()) => {
68 if attempt > 0 {
69 log::info!(
70 "Successfully loaded preset {} after {} retries",
71 path_ref.display(),
72 attempt
73 );
74 }
75 return Ok(());
76 }
77 Err(e) => {
78 log::warn!(
79 "Attempt {}/{} failed to load preset {}: {}",
80 attempt + 1,
81 max_retries + 1,
82 path_ref.display(),
83 e
84 );
85 last_error = Some(e);
86
87 if attempt < max_retries {
89 let wait_ms = 100 * (1 << attempt); std::thread::sleep(std::time::Duration::from_millis(wait_ms));
91 }
92 }
93 }
94 }
95
96 log::error!(
98 "All {} attempts failed for preset {}. Loading default preset.",
99 max_retries + 1,
100 path_ref.display()
101 );
102
103 engine.load_default_preset().map_err(|fallback_err| {
104 EngineError::PresetLoadFailed(format!(
105 "Preset loading failed after {} retries, and fallback also failed: {} / {}",
106 max_retries,
107 last_error
108 .map(|e| e.to_string())
109 .unwrap_or_else(|| "Unknown".to_string()),
110 fallback_err
111 ))
112 })
113 }
114
115 pub fn validate_preset<P: AsRef<Path>>(path: P) -> Result<()> {
119 let path_ref = path.as_ref();
120
121 if !path_ref.exists() {
123 return Err(EngineError::PresetLoadFailed(format!(
124 "File does not exist: {}",
125 path_ref.display()
126 )));
127 }
128
129 let bytes = std::fs::read(path_ref)?;
131 let content = String::from_utf8_lossy(&bytes).into_owned();
132
133 onedrop_parser::parse_preset(&content)?;
135
136 log::debug!("Preset {} is valid", path_ref.display());
137 Ok(())
138 }
139
140 pub fn scan_directory<P: AsRef<Path>>(dir: P) -> Vec<std::path::PathBuf> {
144 let dir_ref = dir.as_ref();
145 let mut valid_presets = Vec::new();
146
147 if let Ok(entries) = std::fs::read_dir(dir_ref) {
148 for entry in entries.flatten() {
149 let path = entry.path();
150
151 if path.extension().and_then(|s| s.to_str()) == Some("milk") {
153 if Self::validate_preset(&path).is_ok() {
155 valid_presets.push(path);
156 } else {
157 log::warn!("Skipping invalid preset: {}", path.display());
158 }
159 }
160 }
161 }
162
163 log::info!(
164 "Found {} valid presets in {}",
165 valid_presets.len(),
166 dir_ref.display()
167 );
168 valid_presets
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::engine::EngineConfig;
176
177 #[test]
178 fn test_load_default_preset() {
179 let config = EngineConfig::default();
180 let mut engine = pollster::block_on(MilkEngine::new(config)).unwrap();
181
182 let result = engine.load_default_preset();
184 assert!(result.is_ok());
185 }
186
187 #[test]
188 fn test_validate_preset() {
189 let result = SafePresetLoader::validate_preset("nonexistent.milk");
191 assert!(result.is_err());
192 }
193}