rush_sync_server/core/
config.rs

1// Enhanced src/core/config.rs - COMPLETE IMPLEMENTATION
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// TOML Deserializer Structures
10#[derive(Debug, Serialize, Deserialize)]
11struct ConfigFile {
12    general: GeneralConfig,
13    #[serde(default)]
14    server: Option<ServerConfigToml>,
15    #[serde(default)]
16    logging: Option<LoggingConfigToml>,
17    #[serde(default)]
18    theme: Option<HashMap<String, ThemeDefinitionConfig>>,
19    language: LanguageConfig,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23struct GeneralConfig {
24    max_messages: usize,
25    typewriter_delay: u64,
26    input_max_length: usize,
27    max_history: usize,
28    poll_rate: u64,
29    log_level: String,
30    #[serde(default = "default_theme")]
31    current_theme: String,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35struct LanguageConfig {
36    current: String,
37}
38
39// FIXED: Server Configuration TOML Structure with auto_open_browser
40#[derive(Debug, Serialize, Deserialize, Clone)]
41struct ServerConfigToml {
42    #[serde(default = "default_port_start")]
43    port_range_start: u16,
44    #[serde(default = "default_port_end")]
45    port_range_end: u16,
46    #[serde(default = "default_max_concurrent")]
47    max_concurrent: usize,
48    #[serde(default = "default_shutdown_timeout")]
49    shutdown_timeout: u64,
50    #[serde(default = "default_startup_delay")]
51    startup_delay_ms: u64,
52    #[serde(default = "default_workers")]
53    workers: usize,
54    #[serde(default = "default_auto_open_browser")]
55    auto_open_browser: bool,
56}
57
58// NEW: Logging Configuration TOML Structure
59#[derive(Debug, Serialize, Deserialize, Clone)]
60struct LoggingConfigToml {
61    #[serde(default = "default_max_file_size")]
62    max_file_size_mb: u64,
63    #[serde(default = "default_max_archive_files")]
64    max_archive_files: u8,
65    #[serde(default = "default_compress_archives")]
66    compress_archives: bool,
67    #[serde(default = "default_log_requests")]
68    log_requests: bool,
69    #[serde(default = "default_log_security")]
70    log_security_alerts: bool,
71    #[serde(default = "default_log_performance")]
72    log_performance: bool,
73}
74
75#[derive(Debug, Serialize, Deserialize, Clone)]
76struct ThemeDefinitionConfig {
77    input_text: String,
78    input_bg: String,
79    output_text: String,
80    output_bg: String,
81    #[serde(default = "default_prefix")]
82    input_cursor_prefix: String,
83    #[serde(default = "default_input_color")]
84    input_cursor_color: String,
85    #[serde(default = "default_cursor")]
86    input_cursor: String,
87    #[serde(default = "default_cursor")]
88    output_cursor: String,
89    #[serde(default = "default_output_color")]
90    output_cursor_color: String,
91}
92
93// Default functions for TOML deserialization
94fn default_theme() -> String {
95    "dark".into()
96}
97fn default_prefix() -> String {
98    "/// ".into()
99}
100fn default_input_color() -> String {
101    "LightBlue".into()
102}
103fn default_output_color() -> String {
104    "White".into()
105}
106fn default_cursor() -> String {
107    "PIPE".into()
108}
109
110// Server defaults
111fn default_port_start() -> u16 {
112    8080
113}
114fn default_port_end() -> u16 {
115    8180
116}
117fn default_max_concurrent() -> usize {
118    10
119}
120fn default_shutdown_timeout() -> u64 {
121    5
122}
123fn default_startup_delay() -> u64 {
124    500
125}
126fn default_workers() -> usize {
127    1
128}
129fn default_auto_open_browser() -> bool {
130    true
131}
132
133// Logging defaults
134fn default_max_file_size() -> u64 {
135    100
136}
137fn default_max_archive_files() -> u8 {
138    9
139}
140fn default_compress_archives() -> bool {
141    true
142}
143fn default_log_requests() -> bool {
144    true
145}
146fn default_log_security() -> bool {
147    true
148}
149fn default_log_performance() -> bool {
150    true
151}
152
153// Main Configuration Structures
154#[derive(Clone)]
155pub struct Config {
156    config_path: Option<String>,
157    pub max_messages: usize,
158    pub typewriter_delay: Duration,
159    pub input_max_length: usize,
160    pub max_history: usize,
161    pub poll_rate: Duration,
162    pub log_level: String,
163    pub theme: Theme,
164    pub current_theme_name: String,
165    pub language: String,
166    pub debug_info: Option<String>,
167    // NEW: Server and Logging configs
168    pub server: ServerConfig,
169    pub logging: LoggingConfig,
170}
171
172#[derive(Clone)]
173pub struct ServerConfig {
174    pub port_range_start: u16,
175    pub port_range_end: u16,
176    pub max_concurrent: usize,
177    pub shutdown_timeout: u64,
178    pub startup_delay_ms: u64,
179    pub workers: usize,
180    pub auto_open_browser: bool,
181}
182
183#[derive(Clone)]
184pub struct LoggingConfig {
185    pub max_file_size_mb: u64,
186    pub max_archive_files: u8,
187    pub compress_archives: bool,
188    pub log_requests: bool,
189    pub log_security_alerts: bool,
190    pub log_performance: bool,
191}
192
193#[derive(Clone)]
194pub struct Theme {
195    pub input_text: AppColor,
196    pub input_bg: AppColor,
197    pub output_text: AppColor,
198    pub output_bg: AppColor,
199    pub input_cursor_prefix: String,
200    pub input_cursor_color: AppColor,
201    pub input_cursor: String,
202    pub output_cursor: String,
203    pub output_cursor_color: AppColor,
204}
205
206impl Default for Theme {
207    fn default() -> Self {
208        Self {
209            input_text: AppColor::new(Color::White),
210            input_bg: AppColor::new(Color::Black),
211            output_text: AppColor::new(Color::White),
212            output_bg: AppColor::new(Color::Black),
213            input_cursor_prefix: "/// ".into(),
214            input_cursor_color: AppColor::new(Color::LightBlue),
215            input_cursor: "PIPE".into(),
216            output_cursor: "PIPE".into(),
217            output_cursor_color: AppColor::new(Color::White),
218        }
219    }
220}
221
222impl Default for ServerConfig {
223    fn default() -> Self {
224        Self {
225            port_range_start: 8080,
226            port_range_end: 8180,
227            max_concurrent: 10,
228            shutdown_timeout: 5,
229            startup_delay_ms: 500,
230            workers: 1,
231            auto_open_browser: true,
232        }
233    }
234}
235
236impl Default for LoggingConfig {
237    fn default() -> Self {
238        Self {
239            max_file_size_mb: 100,
240            max_archive_files: 9,
241            compress_archives: true,
242            log_requests: true,
243            log_security_alerts: true,
244            log_performance: true,
245        }
246    }
247}
248
249impl Config {
250    pub async fn load() -> Result<Self> {
251        Self::load_with_messages(true).await
252    }
253
254    pub async fn load_with_messages(show_messages: bool) -> Result<Self> {
255        // Try existing configs
256        for path in crate::setup::setup_toml::get_config_paths() {
257            if path.exists() {
258                if let Ok(config) = Self::from_file(&path).await {
259                    if show_messages {
260                        Self::log_startup(&config);
261                    }
262                    Self::apply_language(&config).await;
263                    return Ok(config);
264                }
265            }
266        }
267
268        // Create new config
269        let path = crate::setup::setup_toml::ensure_config_exists().await?;
270        let mut config = Self::from_file(&path).await?;
271
272        if show_messages {
273            config.debug_info = Some(format!("New config: {}", path.display()));
274            Self::log_startup(&config);
275        }
276
277        Self::apply_language(&config).await;
278        Ok(config)
279    }
280
281    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
282        let content = tokio::fs::read_to_string(&path)
283            .await
284            .map_err(AppError::Io)?;
285        let file: ConfigFile =
286            toml::from_str(&content).map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
287
288        let poll_rate = Self::clamp(file.general.poll_rate, 16, 1000, 16);
289        let typewriter = Self::clamp(file.general.typewriter_delay, 0, 2000, 50);
290        let theme = Self::load_theme(&file).unwrap_or_default();
291
292        // FIXED: Load server config with all fields including auto_open_browser
293        let server = file
294            .server
295            .map_or_else(ServerConfig::default, |s| ServerConfig {
296                port_range_start: s.port_range_start,
297                port_range_end: s.port_range_end,
298                max_concurrent: s.max_concurrent,
299                shutdown_timeout: s.shutdown_timeout,
300                startup_delay_ms: s.startup_delay_ms,
301                workers: s.workers,
302                auto_open_browser: s.auto_open_browser,
303            });
304
305        // Load logging config with defaults
306        let logging = file
307            .logging
308            .map_or_else(LoggingConfig::default, |l| LoggingConfig {
309                max_file_size_mb: l.max_file_size_mb,
310                max_archive_files: l.max_archive_files,
311                compress_archives: l.compress_archives,
312                log_requests: l.log_requests,
313                log_security_alerts: l.log_security_alerts,
314                log_performance: l.log_performance,
315            });
316
317        let config = Self {
318            config_path: Some(path.as_ref().to_string_lossy().into_owned()),
319            max_messages: file.general.max_messages,
320            typewriter_delay: Duration::from_millis(typewriter),
321            input_max_length: file.general.input_max_length,
322            max_history: file.general.max_history,
323            poll_rate: Duration::from_millis(poll_rate),
324            log_level: file.general.log_level,
325            theme,
326            current_theme_name: file.general.current_theme,
327            language: file.language.current,
328            debug_info: None,
329            server,
330            logging,
331        };
332
333        // Auto-save corrected values
334        if poll_rate != file.general.poll_rate || typewriter != file.general.typewriter_delay {
335            let _ = config.save().await;
336        }
337
338        Ok(config)
339    }
340
341    fn clamp(value: u64, min: u64, max: u64, default: u64) -> u64 {
342        if value < min || value > max {
343            default
344        } else {
345            value
346        }
347    }
348
349    fn load_theme(file: &ConfigFile) -> Option<Theme> {
350        let themes = file.theme.as_ref()?;
351        let def = themes.get(&file.general.current_theme)?;
352        Theme::from_config(def).ok()
353    }
354
355    // FIXED: Enhanced save with server and logging configs including auto_open_browser
356    pub async fn save(&self) -> Result<()> {
357        let Some(path) = &self.config_path else {
358            return Ok(());
359        };
360
361        let themes = Self::load_existing_themes().await.unwrap_or_default();
362        let file = ConfigFile {
363            general: GeneralConfig {
364                max_messages: self.max_messages,
365                typewriter_delay: self.typewriter_delay.as_millis() as u64,
366                input_max_length: self.input_max_length,
367                max_history: self.max_history,
368                poll_rate: self.poll_rate.as_millis() as u64,
369                log_level: self.log_level.clone(),
370                current_theme: self.current_theme_name.clone(),
371            },
372            server: Some(ServerConfigToml {
373                port_range_start: self.server.port_range_start,
374                port_range_end: self.server.port_range_end,
375                max_concurrent: self.server.max_concurrent,
376                shutdown_timeout: self.server.shutdown_timeout,
377                startup_delay_ms: self.server.startup_delay_ms,
378                workers: self.server.workers,
379                auto_open_browser: self.server.auto_open_browser,
380            }),
381            logging: Some(LoggingConfigToml {
382                max_file_size_mb: self.logging.max_file_size_mb,
383                max_archive_files: self.logging.max_archive_files,
384                compress_archives: self.logging.compress_archives,
385                log_requests: self.logging.log_requests,
386                log_security_alerts: self.logging.log_security_alerts,
387                log_performance: self.logging.log_performance,
388            }),
389            theme: if themes.is_empty() {
390                None
391            } else {
392                Some(themes)
393            },
394            language: LanguageConfig {
395                current: self.language.clone(),
396            },
397        };
398
399        let content = toml::to_string_pretty(&file)
400            .map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
401
402        // Ensure dir exists
403        if let Some(parent) = std::path::PathBuf::from(path).parent() {
404            tokio::fs::create_dir_all(parent)
405                .await
406                .map_err(AppError::Io)?;
407        }
408
409        tokio::fs::write(path, content).await.map_err(AppError::Io)
410    }
411
412    // Rest of existing methods...
413    pub async fn change_theme(&mut self, name: &str) -> Result<()> {
414        let themes = Self::load_existing_themes().await?;
415        let def = themes
416            .get(name)
417            .ok_or_else(|| AppError::Validation(format!("Theme '{}' not found", name)))?;
418
419        self.theme = Theme::from_config(def)?;
420        self.current_theme_name = name.into();
421        self.save().await
422    }
423
424    async fn load_existing_themes() -> Result<HashMap<String, ThemeDefinitionConfig>> {
425        for path in crate::setup::setup_toml::get_config_paths() {
426            if path.exists() {
427                let content = tokio::fs::read_to_string(&path)
428                    .await
429                    .map_err(AppError::Io)?;
430                let file: ConfigFile = toml::from_str(&content)
431                    .map_err(|e| AppError::Validation(format!("TOML: {}", e)))?;
432
433                if let Some(themes) = file.theme {
434                    return Ok(themes);
435                }
436            }
437        }
438        Ok(HashMap::new())
439    }
440
441    pub fn get_performance_info(&self) -> String {
442        let fps = 1000.0 / self.poll_rate.as_millis() as f64;
443        let typewriter = if self.typewriter_delay.as_millis() > 0 {
444            1000.0 / self.typewriter_delay.as_millis() as f64
445        } else {
446            f64::INFINITY
447        };
448        format!(
449            "Performance: {:.1} FPS, Typewriter: {:.1} chars/sec, Max Servers: {}",
450            fps, typewriter, self.server.max_concurrent
451        )
452    }
453
454    async fn apply_language(config: &Config) {
455        let _ = crate::commands::lang::LanguageService::new()
456            .load_and_apply_from_config(config)
457            .await;
458    }
459
460    fn log_startup(config: &Config) {
461        if config.poll_rate.as_millis() < 16 {
462            log::warn!("Performance: poll_rate sehr niedrig!");
463        }
464        log::info!("Rush Sync Server v{}", crate::core::constants::VERSION);
465        log::info!(
466            "Server Config: Ports {}-{}, Max: {}",
467            config.server.port_range_start,
468            config.server.port_range_end,
469            config.server.max_concurrent
470        );
471    }
472}
473
474impl Theme {
475    fn from_config(def: &ThemeDefinitionConfig) -> Result<Self> {
476        Ok(Self {
477            input_text: AppColor::from_string(&def.input_text)?,
478            input_bg: AppColor::from_string(&def.input_bg)?,
479            output_text: AppColor::from_string(&def.output_text)?,
480            output_bg: AppColor::from_string(&def.output_bg)?,
481            input_cursor_prefix: def.input_cursor_prefix.clone(),
482            input_cursor_color: AppColor::from_string(&def.input_cursor_color)?,
483            input_cursor: def.input_cursor.clone(),
484            output_cursor: def.output_cursor.clone(),
485            output_cursor_color: AppColor::from_string(&def.output_cursor_color)?,
486        })
487    }
488}
489
490impl Default for Config {
491    fn default() -> Self {
492        Self {
493            config_path: None,
494            max_messages: DEFAULT_BUFFER_SIZE,
495            typewriter_delay: Duration::from_millis(50),
496            input_max_length: DEFAULT_BUFFER_SIZE,
497            max_history: 30,
498            poll_rate: Duration::from_millis(DEFAULT_POLL_RATE),
499            log_level: "info".into(),
500            theme: Theme::default(),
501            current_theme_name: "dark".into(),
502            language: crate::i18n::DEFAULT_LANGUAGE.into(),
503            debug_info: None,
504            server: ServerConfig::default(),
505            logging: LoggingConfig::default(),
506        }
507    }
508}