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