rush_sync_server/core/
config.rs

1// =====================================================
2// FILE: src/core/config.rs - VOLLSTÄNDIG mit BOUNDS CHECKING
3// =====================================================
4
5use crate::core::constants::{DEFAULT_BUFFER_SIZE, DEFAULT_POLL_RATE};
6use crate::core::prelude::*;
7use crate::ui::color::AppColor;
8use serde::{Deserialize, Serialize};
9use std::path::{Path, PathBuf};
10
11// ✅ SICHERE BOUNDS für Performance
12const MIN_POLL_RATE: u64 = 16; // 60 FPS maximum
13const MAX_POLL_RATE: u64 = 1000; // 1 FPS minimum
14const MAX_TYPEWRITER_DELAY: u64 = 2000; // Maximum 2 Sekunden
15
16// ✅ ALLE STRUCT DEFINITIONEN (vorher fehlten die!)
17#[derive(Debug, Serialize, Deserialize)]
18struct ConfigFile {
19    general: GeneralConfig,
20    theme: ThemeConfig,
21    prompt: PromptConfig,
22    language: LanguageConfig,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26struct GeneralConfig {
27    max_messages: usize,
28    typewriter_delay: u64,
29    input_max_length: usize,
30    max_history: usize,
31    poll_rate: u64,
32    log_level: String,
33}
34
35#[derive(Debug, Serialize, Deserialize)]
36struct ThemeConfig {
37    input_text: String,
38    input_bg: String,
39    cursor: String,
40    output_text: String,
41    output_bg: String,
42}
43
44#[derive(Debug, Serialize, Deserialize)]
45struct PromptConfig {
46    text: String,
47    color: String,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51struct LanguageConfig {
52    current: String,
53}
54
55// ✅ HAUPTSTRUCTS (müssen public sein!)
56pub struct Config {
57    config_path: Option<String>,
58    pub max_messages: usize,
59    pub typewriter_delay: Duration,
60    pub input_max_length: usize,
61    pub max_history: usize,
62    pub poll_rate: Duration,
63    pub log_level: String,
64    pub theme: Theme,
65    pub prompt: Prompt,
66    pub language: String,
67    pub debug_info: Option<String>,
68}
69
70pub struct Theme {
71    pub input_text: AppColor,
72    pub input_bg: AppColor,
73    pub cursor: AppColor,
74    pub output_text: AppColor,
75    pub output_bg: AppColor,
76}
77
78pub struct Prompt {
79    pub text: String,
80    pub color: AppColor,
81}
82
83impl Config {
84    pub async fn load() -> Result<Self> {
85        Self::load_with_messages(true).await
86    }
87
88    pub async fn load_with_messages(show_messages: bool) -> Result<Self> {
89        // ✅ 1. PRÜFE ob Config bereits existiert
90        for path in crate::setup::setup_toml::get_config_paths() {
91            if path.exists() {
92                match Self::from_file(&path).await {
93                    Ok(config) => {
94                        // ✅ PERFORMANCE WARNING nur bei problematischen Werten
95                        if config.poll_rate.as_millis() < 16 && show_messages {
96                            log::warn!(
97                                "⚡ PERFORMANCE: poll_rate sehr niedrig! {}",
98                                config.get_performance_info()
99                            );
100                        }
101
102                        // Sprache setzen (ohne Log-Spam)
103                        if let Err(e) = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await {
104                            if show_messages {
105                                log::warn!(
106                                    "{}",
107                                    get_translation(
108                                        "system.config.language_set_failed",
109                                        &[&e.to_string()]
110                                    )
111                                );
112                            }
113                        }
114
115                        // ✅ VERSION nur einmal beim echten Start
116                        if show_messages {
117                            log::info!(
118                                "{}",
119                                crate::i18n::get_command_translation(
120                                    "system.startup.version",
121                                    &[crate::core::constants::VERSION]
122                                )
123                            );
124                        }
125
126                        return Ok(config);
127                    }
128                    Err(_e) => {
129                        continue;
130                    }
131                }
132            }
133        }
134
135        // ✅ 2. KEINE CONFIGS GEFUNDEN - Neue erstellen
136        if show_messages {
137            log::info!("{}", get_translation("system.config.no_existing", &[]));
138        }
139
140        match crate::setup::setup_toml::ensure_config_exists().await {
141            Ok(config_path) => {
142                match Self::from_file(&config_path).await {
143                    Ok(mut config) => {
144                        // ✅ NUR BEI FIRST-RUN zeigen
145                        if show_messages {
146                            let plain_msg = get_translation(
147                                "system.config.new_default",
148                                &[&config_path.display().to_string()],
149                            );
150                            log::info!("{}", plain_msg);
151                            config.debug_info = Some(plain_msg);
152
153                            log::info!(
154                                "{}",
155                                crate::i18n::get_command_translation(
156                                    "system.startup.version",
157                                    &[crate::core::constants::VERSION]
158                                )
159                            );
160                        }
161
162                        let _ = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await;
163
164                        Ok(config)
165                    }
166                    Err(e) => {
167                        if show_messages {
168                            log::error!(
169                                "{}",
170                                get_translation("system.config.load_error", &[&format!("{:?}", e)])
171                            );
172                        }
173                        Err(e)
174                    }
175                }
176            }
177            Err(e) => {
178                if show_messages {
179                    log::error!(
180                        "{}",
181                        get_translation("system.config.setup_failed", &[&format!("{:?}", e)])
182                    );
183                }
184                Err(e)
185            }
186        }
187    }
188
189    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
190        let content = tokio::fs::read_to_string(&path)
191            .await
192            .map_err(AppError::Io)?;
193
194        let config_file: ConfigFile = toml::from_str(&content)
195            .map_err(|e| AppError::Validation(format!("Ungültiges TOML-Format: {}", e)))?;
196
197        // ✅ BOUNDS CHECKING mit Warnungen
198        let original_poll_rate = config_file.general.poll_rate;
199        let original_typewriter_delay = config_file.general.typewriter_delay;
200
201        let poll_rate = Self::validate_poll_rate(original_poll_rate);
202        let typewriter_delay = Self::validate_typewriter_delay(original_typewriter_delay);
203
204        let config = Self {
205            config_path: Some(path.as_ref().to_string_lossy().into_owned()),
206            max_messages: config_file.general.max_messages,
207            typewriter_delay: Duration::from_millis(typewriter_delay),
208            input_max_length: config_file.general.input_max_length,
209            max_history: config_file.general.max_history,
210            poll_rate: Duration::from_millis(poll_rate),
211            log_level: config_file.general.log_level,
212            theme: Theme::from_config(&config_file.theme)?,
213            prompt: Prompt::from_config(&config_file.prompt)?,
214            language: config_file.language.current,
215            debug_info: None,
216        };
217
218        // ✅ KORRIGIERTE WERTE ZURÜCKSCHREIBEN (falls geändert)
219        let values_changed =
220            original_poll_rate != poll_rate || original_typewriter_delay != typewriter_delay;
221
222        if values_changed {
223            log::warn!("🔧 Ungültige Config-Werte korrigiert und gespeichert:");
224            if original_poll_rate != poll_rate {
225                log::warn!("   poll_rate: {}ms → {}ms", original_poll_rate, poll_rate);
226            }
227            if original_typewriter_delay != typewriter_delay {
228                log::warn!(
229                    "   typewriter_delay: {}ms → {}ms",
230                    original_typewriter_delay,
231                    typewriter_delay
232                );
233            }
234
235            // ✅ SOFORT ZURÜCKSCHREIBEN damit beim nächsten Start korrekte Werte geladen werden
236            if let Err(e) = config.save().await {
237                log::warn!("Konnte korrigierte Config nicht speichern: {}", e);
238            } else {
239                log::info!("✅ Korrigierte Werte in Config-Datei gespeichert");
240            }
241        }
242
243        Ok(config)
244    }
245
246    // ✅ POLL_RATE Validierung
247    fn validate_poll_rate(value: u64) -> u64 {
248        match value {
249            0 => {
250                log::warn!(
251                    "poll_rate = 0 nicht erlaubt, verwende Minimum: {}ms",
252                    MIN_POLL_RATE
253                );
254                MIN_POLL_RATE
255            }
256            v if v < MIN_POLL_RATE => {
257                log::warn!(
258                    "poll_rate = {}ms zu schnell (Performance!), verwende Minimum: {}ms",
259                    v,
260                    MIN_POLL_RATE
261                );
262                MIN_POLL_RATE
263            }
264            v if v > MAX_POLL_RATE => {
265                log::warn!(
266                    "poll_rate = {}ms zu langsam, verwende Maximum: {}ms",
267                    v,
268                    MAX_POLL_RATE
269                );
270                MAX_POLL_RATE
271            }
272            v => {
273                if v < 33 {
274                    log::info!("poll_rate = {}ms (sehr schnell, aber OK)", v);
275                }
276                v
277            }
278        }
279    }
280
281    // ✅ TYPEWRITER_DELAY Validierung (0 = deaktiviert bleibt 0!)
282    fn validate_typewriter_delay(value: u64) -> u64 {
283        match value {
284            0 => {
285                log::info!("typewriter_delay = 0 → Typewriter-Effekt deaktiviert");
286                0 // ✅ BLEIBT 0 für echte Deaktivierung!
287            }
288            v if v > MAX_TYPEWRITER_DELAY => {
289                log::warn!(
290                    "typewriter_delay = {}ms zu langsam, verwende Maximum: {}ms",
291                    v,
292                    MAX_TYPEWRITER_DELAY
293                );
294                MAX_TYPEWRITER_DELAY
295            }
296            v => v,
297        }
298    }
299
300    // ✅ PERFORMANCE INFO für Debug
301    pub fn get_performance_info(&self) -> String {
302        let fps = 1000.0 / self.poll_rate.as_millis() as f64;
303        let typewriter_chars_per_sec = if self.typewriter_delay.as_millis() > 0 {
304            1000.0 / self.typewriter_delay.as_millis() as f64
305        } else {
306            f64::INFINITY
307        };
308
309        format!(
310            "Performance: {:.1} FPS, Typewriter: {:.1} chars/sec",
311            fps, typewriter_chars_per_sec
312        )
313    }
314
315    pub async fn save(&self) -> Result<()> {
316        if let Some(path) = &self.config_path {
317            let config_file = ConfigFile {
318                general: GeneralConfig {
319                    max_messages: self.max_messages,
320                    typewriter_delay: self.typewriter_delay.as_millis() as u64,
321                    input_max_length: self.input_max_length,
322                    max_history: self.max_history,
323                    poll_rate: self.poll_rate.as_millis() as u64,
324                    log_level: self.log_level.clone(),
325                },
326                theme: ThemeConfig {
327                    input_text: self.theme.input_text.to_string(),
328                    input_bg: self.theme.input_bg.to_string(),
329                    cursor: self.theme.cursor.to_string(),
330                    output_text: self.theme.output_text.to_string(),
331                    output_bg: self.theme.output_bg.to_string(),
332                },
333                prompt: PromptConfig {
334                    text: self.prompt.text.clone(),
335                    color: self.prompt.color.to_string(),
336                },
337                language: LanguageConfig {
338                    current: self.language.clone(),
339                },
340            };
341
342            let content = toml::to_string_pretty(&config_file)
343                .map_err(|e| AppError::Validation(format!("Serialisierungsfehler: {}", e)))?;
344
345            if let Some(parent) = PathBuf::from(path).parent() {
346                if !parent.exists() {
347                    tokio::fs::create_dir_all(parent)
348                        .await
349                        .map_err(AppError::Io)?;
350                }
351            }
352
353            tokio::fs::write(path, content)
354                .await
355                .map_err(AppError::Io)?;
356        }
357        Ok(())
358    }
359}
360
361impl Theme {
362    fn from_config(config: &ThemeConfig) -> Result<Self> {
363        Ok(Self {
364            input_text: AppColor::from_string(&config.input_text)?,
365            input_bg: AppColor::from_string(&config.input_bg)?,
366            cursor: AppColor::from_string(&config.cursor)?,
367            output_text: AppColor::from_string(&config.output_text)?,
368            output_bg: AppColor::from_string(&config.output_bg)?,
369        })
370    }
371}
372
373impl Prompt {
374    fn from_config(config: &PromptConfig) -> Result<Self> {
375        Ok(Self {
376            text: config.text.clone(),
377            color: AppColor::from_string(&config.color)?,
378        })
379    }
380}
381
382crate::impl_default!(
383    Config,
384    Self {
385        config_path: None,
386        max_messages: DEFAULT_BUFFER_SIZE,
387        typewriter_delay: Duration::from_millis(50), // ✅ Sicherer Default
388        input_max_length: DEFAULT_BUFFER_SIZE,
389        max_history: 30,
390        poll_rate: Duration::from_millis(DEFAULT_POLL_RATE), // ✅ 16ms = 60fps
391        log_level: "info".to_string(),
392        theme: Theme::default(),
393        prompt: Prompt::default(),
394        language: crate::i18n::DEFAULT_LANGUAGE.to_string(),
395        debug_info: None,
396    }
397);
398
399crate::impl_default!(
400    Theme,
401    Self {
402        input_text: AppColor::new(Color::Black),
403        input_bg: AppColor::new(Color::Black),
404        cursor: AppColor::new(Color::Black),
405        output_text: AppColor::new(Color::White),
406        output_bg: AppColor::new(Color::White),
407    }
408);
409
410crate::impl_default!(
411    Prompt,
412    Self {
413        text: "/// ".to_string(),
414        color: AppColor::new(Color::Black),
415    }
416);
417
418// ✅ DEBUG: Performance-Warnung zur Compile-Zeit
419#[cfg(debug_assertions)]
420impl Config {
421    pub fn debug_performance_warning(&self) {
422        if self.poll_rate.as_millis() < 16 {
423            log::warn!(
424                "🔥 PERFORMANCE WARNING: poll_rate = {}ms verursacht hohe CPU-Last!",
425                self.poll_rate.as_millis()
426            );
427            log::warn!("💡 EMPFEHLUNG: Setze poll_rate auf 16-33ms für bessere Performance");
428        }
429
430        if self.typewriter_delay.as_millis() < 10 {
431            log::warn!(
432                "⚡ PERFORMANCE INFO: typewriter_delay = {}ms (sehr schnell)",
433                self.typewriter_delay.as_millis()
434            );
435        }
436
437        log::info!("📊 AKTUELLE WERTE: {}", self.get_performance_info());
438    }
439}