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                            crate::output::logging::AppLogger::log_plain(
118                                crate::i18n::get_command_translation(
119                                    "system.startup.version",
120                                    &[crate::core::constants::VERSION],
121                                ),
122                            );
123                        }
124
125                        return Ok(config);
126                    }
127                    Err(_e) => {
128                        continue;
129                    }
130                }
131            }
132        }
133
134        // ✅ 2. KEINE CONFIGS GEFUNDEN - Neue erstellen
135        if show_messages {
136            log::info!("{}", get_translation("system.config.no_existing", &[]));
137        }
138
139        match crate::setup::setup_toml::ensure_config_exists().await {
140            Ok(config_path) => {
141                match Self::from_file(&config_path).await {
142                    Ok(mut config) => {
143                        // ✅ NUR BEI FIRST-RUN zeigen
144                        if show_messages {
145                            let plain_msg = get_translation(
146                                "system.config.new_default",
147                                &[&config_path.display().to_string()],
148                            );
149                            log::info!("{}", plain_msg);
150                            config.debug_info = Some(plain_msg);
151
152                            crate::output::logging::AppLogger::log_plain(
153                                crate::i18n::get_command_translation(
154                                    "system.startup.version",
155                                    &[crate::core::constants::VERSION],
156                                ),
157                            );
158                        }
159
160                        let _ = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await;
161
162                        Ok(config)
163                    }
164                    Err(e) => {
165                        if show_messages {
166                            log::error!(
167                                "{}",
168                                get_translation("system.config.load_error", &[&format!("{:?}", e)])
169                            );
170                        }
171                        Err(e)
172                    }
173                }
174            }
175            Err(e) => {
176                if show_messages {
177                    log::error!(
178                        "{}",
179                        get_translation("system.config.setup_failed", &[&format!("{:?}", e)])
180                    );
181                }
182                Err(e)
183            }
184        }
185    }
186
187    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
188        let content = tokio::fs::read_to_string(&path)
189            .await
190            .map_err(AppError::Io)?;
191
192        let config_file: ConfigFile = toml::from_str(&content)
193            .map_err(|e| AppError::Validation(format!("Ungültiges TOML-Format: {}", e)))?;
194
195        // ✅ BOUNDS CHECKING mit Warnungen
196        let original_poll_rate = config_file.general.poll_rate;
197        let original_typewriter_delay = config_file.general.typewriter_delay;
198
199        let poll_rate = Self::validate_poll_rate(original_poll_rate);
200        let typewriter_delay = Self::validate_typewriter_delay(original_typewriter_delay);
201
202        let config = Self {
203            config_path: Some(path.as_ref().to_string_lossy().into_owned()),
204            max_messages: config_file.general.max_messages,
205            typewriter_delay: Duration::from_millis(typewriter_delay),
206            input_max_length: config_file.general.input_max_length,
207            max_history: config_file.general.max_history,
208            poll_rate: Duration::from_millis(poll_rate),
209            log_level: config_file.general.log_level,
210            theme: Theme::from_config(&config_file.theme)?,
211            prompt: Prompt::from_config(&config_file.prompt)?,
212            language: config_file.language.current,
213            debug_info: None,
214        };
215
216        // ✅ KORRIGIERTE WERTE ZURÜCKSCHREIBEN (falls geändert)
217        let values_changed =
218            original_poll_rate != poll_rate || original_typewriter_delay != typewriter_delay;
219
220        if values_changed {
221            log::warn!("🔧 Ungültige Config-Werte korrigiert und gespeichert:");
222            if original_poll_rate != poll_rate {
223                log::warn!("   poll_rate: {}ms → {}ms", original_poll_rate, poll_rate);
224            }
225            if original_typewriter_delay != typewriter_delay {
226                log::warn!(
227                    "   typewriter_delay: {}ms → {}ms",
228                    original_typewriter_delay,
229                    typewriter_delay
230                );
231            }
232
233            // ✅ SOFORT ZURÜCKSCHREIBEN damit beim nächsten Start korrekte Werte geladen werden
234            if let Err(e) = config.save().await {
235                log::warn!("Konnte korrigierte Config nicht speichern: {}", e);
236            } else {
237                log::info!("✅ Korrigierte Werte in Config-Datei gespeichert");
238            }
239        }
240
241        Ok(config)
242    }
243
244    // ✅ POLL_RATE Validierung
245    fn validate_poll_rate(value: u64) -> u64 {
246        match value {
247            0 => {
248                log::warn!(
249                    "poll_rate = 0 nicht erlaubt, verwende Minimum: {}ms",
250                    MIN_POLL_RATE
251                );
252                MIN_POLL_RATE
253            }
254            v if v < MIN_POLL_RATE => {
255                log::warn!(
256                    "poll_rate = {}ms zu schnell (Performance!), verwende Minimum: {}ms",
257                    v,
258                    MIN_POLL_RATE
259                );
260                MIN_POLL_RATE
261            }
262            v if v > MAX_POLL_RATE => {
263                log::warn!(
264                    "poll_rate = {}ms zu langsam, verwende Maximum: {}ms",
265                    v,
266                    MAX_POLL_RATE
267                );
268                MAX_POLL_RATE
269            }
270            v => {
271                if v < 33 {
272                    log::trace!("poll_rate = {}ms (sehr schnell, aber OK)", v);
273                }
274                v
275            }
276        }
277    }
278
279    // ✅ TYPEWRITER_DELAY Validierung (0 = deaktiviert bleibt 0!)
280    fn validate_typewriter_delay(value: u64) -> u64 {
281        match value {
282            0 => {
283                log::info!("typewriter_delay = 0 → Typewriter-Effekt deaktiviert");
284                0 // ✅ BLEIBT 0 für echte Deaktivierung!
285            }
286            v if v > MAX_TYPEWRITER_DELAY => {
287                log::warn!(
288                    "typewriter_delay = {}ms zu langsam, verwende Maximum: {}ms",
289                    v,
290                    MAX_TYPEWRITER_DELAY
291                );
292                MAX_TYPEWRITER_DELAY
293            }
294            v => v,
295        }
296    }
297
298    // ✅ PERFORMANCE INFO für Debug
299    pub fn get_performance_info(&self) -> String {
300        let fps = 1000.0 / self.poll_rate.as_millis() as f64;
301        let typewriter_chars_per_sec = if self.typewriter_delay.as_millis() > 0 {
302            1000.0 / self.typewriter_delay.as_millis() as f64
303        } else {
304            f64::INFINITY
305        };
306
307        format!(
308            "Performance: {:.1} FPS, Typewriter: {:.1} chars/sec",
309            fps, typewriter_chars_per_sec
310        )
311    }
312
313    pub async fn save(&self) -> Result<()> {
314        if let Some(path) = &self.config_path {
315            let config_file = ConfigFile {
316                general: GeneralConfig {
317                    max_messages: self.max_messages,
318                    typewriter_delay: self.typewriter_delay.as_millis() as u64,
319                    input_max_length: self.input_max_length,
320                    max_history: self.max_history,
321                    poll_rate: self.poll_rate.as_millis() as u64,
322                    log_level: self.log_level.clone(),
323                },
324                theme: ThemeConfig {
325                    input_text: self.theme.input_text.to_string(),
326                    input_bg: self.theme.input_bg.to_string(),
327                    cursor: self.theme.cursor.to_string(),
328                    output_text: self.theme.output_text.to_string(),
329                    output_bg: self.theme.output_bg.to_string(),
330                },
331                prompt: PromptConfig {
332                    text: self.prompt.text.clone(),
333                    color: self.prompt.color.to_string(),
334                },
335                language: LanguageConfig {
336                    current: self.language.clone(),
337                },
338            };
339
340            let content = toml::to_string_pretty(&config_file)
341                .map_err(|e| AppError::Validation(format!("Serialisierungsfehler: {}", e)))?;
342
343            if let Some(parent) = PathBuf::from(path).parent() {
344                if !parent.exists() {
345                    tokio::fs::create_dir_all(parent)
346                        .await
347                        .map_err(AppError::Io)?;
348                }
349            }
350
351            tokio::fs::write(path, content)
352                .await
353                .map_err(AppError::Io)?;
354        }
355        Ok(())
356    }
357}
358
359impl Theme {
360    fn from_config(config: &ThemeConfig) -> Result<Self> {
361        Ok(Self {
362            input_text: AppColor::from_string(&config.input_text)?,
363            input_bg: AppColor::from_string(&config.input_bg)?,
364            cursor: AppColor::from_string(&config.cursor)?,
365            output_text: AppColor::from_string(&config.output_text)?,
366            output_bg: AppColor::from_string(&config.output_bg)?,
367        })
368    }
369}
370
371impl Prompt {
372    fn from_config(config: &PromptConfig) -> Result<Self> {
373        Ok(Self {
374            text: config.text.clone(),
375            color: AppColor::from_string(&config.color)?,
376        })
377    }
378}
379
380crate::impl_default!(
381    Config,
382    Self {
383        config_path: None,
384        max_messages: DEFAULT_BUFFER_SIZE,
385        typewriter_delay: Duration::from_millis(50), // ✅ Sicherer Default
386        input_max_length: DEFAULT_BUFFER_SIZE,
387        max_history: 30,
388        poll_rate: Duration::from_millis(DEFAULT_POLL_RATE), // ✅ 16ms = 60fps
389        log_level: "info".to_string(),
390        theme: Theme::default(),
391        prompt: Prompt::default(),
392        language: crate::i18n::DEFAULT_LANGUAGE.to_string(),
393        debug_info: None,
394    }
395);
396
397crate::impl_default!(
398    Theme,
399    Self {
400        input_text: AppColor::new(Color::Black),
401        input_bg: AppColor::new(Color::Black),
402        cursor: AppColor::new(Color::Black),
403        output_text: AppColor::new(Color::White),
404        output_bg: AppColor::new(Color::White),
405    }
406);
407
408crate::impl_default!(
409    Prompt,
410    Self {
411        text: "/// ".to_string(),
412        color: AppColor::new(Color::Black),
413    }
414);
415
416// ✅ DEBUG: Performance-Warnung zur Compile-Zeit
417#[cfg(debug_assertions)]
418impl Config {
419    pub fn debug_performance_warning(&self) {
420        if self.poll_rate.as_millis() < 16 {
421            log::warn!(
422                "🔥 PERFORMANCE WARNING: poll_rate = {}ms verursacht hohe CPU-Last!",
423                self.poll_rate.as_millis()
424            );
425            log::warn!("💡 EMPFEHLUNG: Setze poll_rate auf 16-33ms für bessere Performance");
426        }
427
428        if self.typewriter_delay.as_millis() < 10 {
429            log::warn!(
430                "⚡ PERFORMANCE INFO: typewriter_delay = {}ms (sehr schnell)",
431                self.typewriter_delay.as_millis()
432            );
433        }
434
435        log::info!("📊 AKTUELLE WERTE: {}", self.get_performance_info());
436    }
437}