rush_sync_server/core/
config.rs

1// ## FILE: src/core/config.rs - KOMPRIMIERTE VERSION
2use crate::core::constants::{DEFAULT_BUFFER_SIZE, DEFAULT_POLL_RATE};
3use crate::core::prelude::*;
4use crate::ui::color::AppColor;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::Path;
8
9#[derive(Debug, Serialize, Deserialize)]
10struct ConfigFile {
11    general: GeneralConfig,
12    #[serde(default)]
13    theme: Option<HashMap<String, ThemeDefinitionConfig>>,
14    language: LanguageConfig,
15}
16
17#[derive(Debug, Serialize, Deserialize)]
18struct GeneralConfig {
19    max_messages: usize,
20    typewriter_delay: u64,
21    input_max_length: usize,
22    max_history: usize,
23    poll_rate: u64,
24    log_level: String,
25    #[serde(default = "default_theme")]
26    current_theme: String,
27}
28
29#[derive(Debug, Serialize, Deserialize)]
30struct LanguageConfig {
31    current: String,
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
35struct ThemeDefinitionConfig {
36    input_text: String,
37    input_bg: String,
38    output_text: String,
39    output_bg: String,
40    #[serde(default = "default_prefix")]
41    input_cursor_prefix: String,
42    #[serde(default = "default_input_color")]
43    input_cursor_color: String,
44    #[serde(default = "default_cursor")]
45    input_cursor: String,
46    #[serde(default = "default_cursor")]
47    output_cursor: String,
48    #[serde(default = "default_output_color")]
49    output_cursor_color: String,
50}
51
52// ✅ KOMPRIMIERTE DEFAULTS
53fn default_theme() -> String {
54    "dark".into()
55}
56fn default_prefix() -> String {
57    "/// ".into()
58}
59fn default_input_color() -> String {
60    "LightBlue".into()
61}
62fn default_output_color() -> String {
63    "White".into()
64}
65fn default_cursor() -> String {
66    "PIPE".into()
67}
68
69#[derive(Clone)]
70pub struct Config {
71    config_path: Option<String>,
72    pub max_messages: usize,
73    pub typewriter_delay: Duration,
74    pub input_max_length: usize,
75    pub max_history: usize,
76    pub poll_rate: Duration,
77    pub log_level: String,
78    pub theme: Theme,
79    pub current_theme_name: String,
80    pub language: String,
81    pub debug_info: Option<String>,
82}
83
84#[derive(Clone)]
85pub struct Theme {
86    pub input_text: AppColor,
87    pub input_bg: AppColor,
88    pub output_text: AppColor,
89    pub output_bg: AppColor,
90    pub input_cursor_prefix: String,
91    pub input_cursor_color: AppColor,
92    pub input_cursor: String,
93    pub output_cursor: String,
94    pub output_cursor_color: AppColor,
95}
96
97impl Default for Theme {
98    fn default() -> Self {
99        Self {
100            input_text: AppColor::new(Color::White),
101            input_bg: AppColor::new(Color::Black),
102            output_text: AppColor::new(Color::White),
103            output_bg: AppColor::new(Color::Black),
104            input_cursor_prefix: "/// ".into(),
105            input_cursor_color: AppColor::new(Color::LightBlue),
106            input_cursor: "PIPE".into(),
107            output_cursor: "PIPE".into(),
108            output_cursor_color: AppColor::new(Color::White),
109        }
110    }
111}
112
113impl Config {
114    pub async fn load() -> Result<Self> {
115        Self::load_with_messages(true).await
116    }
117
118    pub async fn load_with_messages(show_messages: bool) -> Result<Self> {
119        // Try existing configs
120        for path in crate::setup::setup_toml::get_config_paths() {
121            if path.exists() {
122                if let Ok(config) = Self::from_file(&path).await {
123                    if show_messages {
124                        Self::log_startup(&config);
125                    }
126                    Self::apply_language(&config).await;
127                    return Ok(config);
128                }
129            }
130        }
131
132        // Create new config
133        let path = crate::setup::setup_toml::ensure_config_exists().await?;
134        let mut config = Self::from_file(&path).await?;
135
136        if show_messages {
137            config.debug_info = Some(format!("New config: {}", path.display()));
138            Self::log_startup(&config);
139        }
140
141        Self::apply_language(&config).await;
142        Ok(config)
143    }
144
145    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
146        let content = tokio::fs::read_to_string(&path)
147            .await
148            .map_err(AppError::Io)?;
149        let file: ConfigFile =
150            toml::from_str(&content).map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
151
152        let poll_rate = Self::clamp(file.general.poll_rate, 16, 1000, 16);
153        let typewriter = Self::clamp(file.general.typewriter_delay, 0, 2000, 50);
154        let theme = Self::load_theme(&file).unwrap_or_default();
155
156        let config = Self {
157            config_path: Some(path.as_ref().to_string_lossy().into_owned()),
158            max_messages: file.general.max_messages,
159            typewriter_delay: Duration::from_millis(typewriter),
160            input_max_length: file.general.input_max_length,
161            max_history: file.general.max_history,
162            poll_rate: Duration::from_millis(poll_rate),
163            log_level: file.general.log_level,
164            theme,
165            current_theme_name: file.general.current_theme,
166            language: file.language.current,
167            debug_info: None,
168        };
169
170        // Auto-save corrected values
171        if poll_rate != file.general.poll_rate || typewriter != file.general.typewriter_delay {
172            let _ = config.save().await;
173        }
174
175        Ok(config)
176    }
177
178    fn clamp(value: u64, min: u64, max: u64, default: u64) -> u64 {
179        if value < min || value > max {
180            default
181        } else {
182            value
183        }
184    }
185
186    fn load_theme(file: &ConfigFile) -> Option<Theme> {
187        let themes = file.theme.as_ref()?;
188        let def = themes.get(&file.general.current_theme)?;
189        Theme::from_config(def).ok()
190    }
191
192    // ✅ KOMPRIMIERTES SAVE
193    pub async fn save(&self) -> Result<()> {
194        let Some(path) = &self.config_path else {
195            return Ok(());
196        };
197
198        let themes = Self::load_existing_themes().await.unwrap_or_default();
199        let file = ConfigFile {
200            general: GeneralConfig {
201                max_messages: self.max_messages,
202                typewriter_delay: self.typewriter_delay.as_millis() as u64,
203                input_max_length: self.input_max_length,
204                max_history: self.max_history,
205                poll_rate: self.poll_rate.as_millis() as u64,
206                log_level: self.log_level.clone(),
207                current_theme: self.current_theme_name.clone(),
208            },
209            theme: if themes.is_empty() {
210                None
211            } else {
212                Some(themes)
213            },
214            language: LanguageConfig {
215                current: self.language.clone(),
216            },
217        };
218
219        let content = toml::to_string_pretty(&file)
220            .map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
221
222        // Ensure dir exists
223        if let Some(parent) = std::path::PathBuf::from(path).parent() {
224            tokio::fs::create_dir_all(parent)
225                .await
226                .map_err(AppError::Io)?;
227        }
228
229        tokio::fs::write(path, content).await.map_err(AppError::Io)
230    }
231
232    // ✅ KOMPRIMIERTES THEME SWITCHING
233    pub async fn change_theme(&mut self, name: &str) -> Result<()> {
234        let themes = Self::load_existing_themes().await?;
235        let def = themes
236            .get(name)
237            .ok_or_else(|| AppError::Validation(format!("Theme '{}' not found", name)))?;
238
239        self.theme = Theme::from_config(def)?;
240        self.current_theme_name = name.into();
241        self.save().await
242    }
243
244    async fn load_existing_themes() -> Result<HashMap<String, ThemeDefinitionConfig>> {
245        for path in crate::setup::setup_toml::get_config_paths() {
246            if path.exists() {
247                let content = tokio::fs::read_to_string(&path)
248                    .await
249                    .map_err(AppError::Io)?;
250                let file: ConfigFile = toml::from_str(&content)
251                    .map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
252
253                if let Some(themes) = file.theme {
254                    return Ok(themes);
255                }
256            }
257        }
258        Ok(HashMap::new())
259    }
260
261    // ✅ PERFORMANCE INFO (komprimiert)
262    pub fn get_performance_info(&self) -> String {
263        let fps = 1000.0 / self.poll_rate.as_millis() as f64;
264        let typewriter = if self.typewriter_delay.as_millis() > 0 {
265            1000.0 / self.typewriter_delay.as_millis() as f64
266        } else {
267            f64::INFINITY
268        };
269        format!(
270            "Performance: {:.1} FPS, Typewriter: {:.1} chars/sec",
271            fps, typewriter
272        )
273    }
274
275    // ✅ HELPER METHODS (komprimiert)
276    async fn apply_language(config: &Config) {
277        let _ = crate::commands::lang::LanguageService::new()
278            .load_and_apply_from_config(config)
279            .await;
280    }
281
282    fn log_startup(config: &Config) {
283        if config.poll_rate.as_millis() < 16 {
284            log::warn!("⚡ PERFORMANCE: poll_rate sehr niedrig!");
285        }
286        log::info!("Rush Sync Server v{}", crate::core::constants::VERSION);
287    }
288}
289
290impl Theme {
291    fn from_config(def: &ThemeDefinitionConfig) -> Result<Self> {
292        Ok(Self {
293            input_text: AppColor::from_string(&def.input_text)?,
294            input_bg: AppColor::from_string(&def.input_bg)?,
295            output_text: AppColor::from_string(&def.output_text)?,
296            output_bg: AppColor::from_string(&def.output_bg)?,
297            input_cursor_prefix: def.input_cursor_prefix.clone(),
298            input_cursor_color: AppColor::from_string(&def.input_cursor_color)?,
299            input_cursor: def.input_cursor.clone(),
300            output_cursor: def.output_cursor.clone(),
301            output_cursor_color: AppColor::from_string(&def.output_cursor_color)?,
302        })
303    }
304}
305
306impl Default for Config {
307    fn default() -> Self {
308        Self {
309            config_path: None,
310            max_messages: DEFAULT_BUFFER_SIZE,
311            typewriter_delay: Duration::from_millis(50),
312            input_max_length: DEFAULT_BUFFER_SIZE,
313            max_history: 30,
314            poll_rate: Duration::from_millis(DEFAULT_POLL_RATE),
315            log_level: "info".into(),
316            theme: Theme::default(),
317            current_theme_name: "dark".into(),
318            language: crate::i18n::DEFAULT_LANGUAGE.into(),
319            debug_info: None,
320        }
321    }
322}