rush_sync_server/core/
config.rs

1// =====================================================
2// FILE: core/config.rs - BEREINIGT nach History-Refactoring
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#[derive(Debug, Serialize, Deserialize)]
12struct ConfigFile {
13    general: GeneralConfig,
14    theme: ThemeConfig,
15    prompt: PromptConfig,
16    language: LanguageConfig,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20struct GeneralConfig {
21    max_messages: usize,
22    typewriter_delay: u64,
23    input_max_length: usize,
24    max_history: usize, // ✅ BLEIBT für Rückwärtskompatibilität
25    poll_rate: u64,
26}
27
28#[derive(Debug, Serialize, Deserialize)]
29struct ThemeConfig {
30    input_text: String,
31    input_bg: String,
32    cursor: String,
33    output_text: String,
34    output_bg: String,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38struct PromptConfig {
39    text: String,
40    color: String,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
44struct LanguageConfig {
45    current: String,
46}
47
48pub struct Config {
49    config_path: Option<String>,
50    pub max_messages: usize,
51    pub typewriter_delay: Duration,
52    pub input_max_length: usize,
53    pub max_history: usize, // ✅ BLEIBT - wird von HistoryConfig verwendet
54    pub poll_rate: Duration,
55    pub theme: Theme,
56    pub prompt: Prompt,
57    pub language: String,
58    pub debug_info: Option<String>,
59}
60
61pub struct Theme {
62    pub input_text: AppColor,
63    pub input_bg: AppColor,
64    pub cursor: AppColor,
65    pub output_text: AppColor,
66    pub output_bg: AppColor,
67}
68
69pub struct Prompt {
70    pub text: String,
71    pub color: AppColor,
72}
73
74impl Config {
75    pub async fn load() -> Result<Self> {
76        Self::load_with_messages(true).await
77    }
78
79    pub async fn load_with_messages(show_messages: bool) -> Result<Self> {
80        let mut last_error = None;
81
82        for path in crate::setup::setup_toml::get_config_paths() {
83            if path.exists() {
84                match Self::from_file(&path).await {
85                    Ok(config) => {
86                        // ✅ SPRACHE DELEGIERT an LanguageConfig
87                        if let Err(e) = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await {
88                            if show_messages {
89                                log::warn!(
90                                    "{}",
91                                    get_translation(
92                                        "system.config.language_set_failed",
93                                        &[&e.to_string()]
94                                    )
95                                );
96                            }
97                        }
98
99                        if show_messages {
100                            log::debug!(
101                                "{}",
102                                get_translation(
103                                    "system.config.loaded",
104                                    &[&path.display().to_string()]
105                                )
106                            );
107
108                            log::info!(
109                                "{}",
110                                crate::i18n::get_command_translation(
111                                    "system.startup.version",
112                                    &[crate::core::constants::VERSION]
113                                )
114                            );
115                        }
116
117                        return Ok(config);
118                    }
119                    Err(e) => {
120                        last_error = Some(e);
121                        continue;
122                    }
123                }
124            }
125        }
126
127        if show_messages {
128            log::info!("{}", get_translation("system.config.no_existing", &[]));
129        }
130
131        match crate::setup::setup_toml::ensure_config_exists().await {
132            Ok(config_path) => {
133                match Self::from_file(&config_path).await {
134                    Ok(mut config) => {
135                        if show_messages {
136                            let plain_msg = get_translation(
137                                "system.config.new_default",
138                                &[&config_path.display().to_string()],
139                            );
140                            log::info!("{}", plain_msg);
141                            config.debug_info = Some(plain_msg);
142
143                            log::info!(
144                                "{}",
145                                crate::i18n::get_command_translation(
146                                    "system.startup.version",
147                                    &[crate::core::constants::VERSION]
148                                )
149                            );
150                        }
151
152                        let _ = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await;
153
154                        Ok(config)
155                    }
156                    Err(e) => {
157                        if show_messages {
158                            log::error!(
159                                "{}",
160                                get_translation("system.config.load_error", &[&format!("{:?}", e)])
161                            );
162                        }
163                        Err(e)
164                    }
165                }
166            }
167            Err(e) => {
168                if show_messages {
169                    log::error!(
170                        "{}",
171                        get_translation("system.config.setup_failed", &[&format!("{:?}", e)])
172                    );
173                    if let Some(last_e) = last_error {
174                        log::debug!(
175                            "{}",
176                            get_translation(
177                                "system.config.last_error",
178                                &[&format!("{:?}", last_e)]
179                            )
180                        );
181                    }
182                }
183                Err(e)
184            }
185        }
186    }
187
188    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
189        let content = tokio::fs::read_to_string(&path)
190            .await
191            .map_err(AppError::Io)?;
192
193        let config_file: ConfigFile = toml::from_str(&content)
194            .map_err(|e| AppError::Validation(format!("Ungültiges TOML-Format: {}", e)))?;
195
196        Ok(Self {
197            config_path: Some(path.as_ref().to_string_lossy().into_owned()),
198            max_messages: config_file.general.max_messages,
199            typewriter_delay: Duration::from_millis(config_file.general.typewriter_delay),
200            input_max_length: config_file.general.input_max_length,
201            max_history: config_file.general.max_history, // ✅ BLEIBT für HistoryConfig
202            poll_rate: Duration::from_millis(config_file.general.poll_rate),
203            theme: Theme::from_config(&config_file.theme)?,
204            prompt: Prompt::from_config(&config_file.prompt)?,
205            language: config_file.language.current,
206            debug_info: None,
207        })
208    }
209
210    pub async fn save(&self) -> Result<()> {
211        if let Some(path) = &self.config_path {
212            let config_file = ConfigFile {
213                general: GeneralConfig {
214                    max_messages: self.max_messages,
215                    typewriter_delay: self.typewriter_delay.as_millis() as u64,
216                    input_max_length: self.input_max_length,
217                    max_history: self.max_history, // ✅ BLEIBT
218                    poll_rate: self.poll_rate.as_millis() as u64,
219                },
220                theme: ThemeConfig {
221                    input_text: self.theme.input_text.to_string(),
222                    input_bg: self.theme.input_bg.to_string(),
223                    cursor: self.theme.cursor.to_string(),
224                    output_text: self.theme.output_text.to_string(),
225                    output_bg: self.theme.output_bg.to_string(),
226                },
227                prompt: PromptConfig {
228                    text: self.prompt.text.clone(),
229                    color: self.prompt.color.to_string(),
230                },
231                language: LanguageConfig {
232                    current: self.language.clone(),
233                },
234            };
235
236            let content = toml::to_string_pretty(&config_file)
237                .map_err(|e| AppError::Validation(format!("Serialisierungsfehler: {}", e)))?;
238
239            if let Some(parent) = PathBuf::from(path).parent() {
240                if !parent.exists() {
241                    tokio::fs::create_dir_all(parent)
242                        .await
243                        .map_err(AppError::Io)?;
244                }
245            }
246
247            tokio::fs::write(path, content)
248                .await
249                .map_err(AppError::Io)?;
250        }
251        Ok(())
252    }
253}
254
255impl Theme {
256    fn from_config(config: &ThemeConfig) -> Result<Self> {
257        Ok(Self {
258            input_text: AppColor::from_string(&config.input_text)?,
259            input_bg: AppColor::from_string(&config.input_bg)?,
260            cursor: AppColor::from_string(&config.cursor)?,
261            output_text: AppColor::from_string(&config.output_text)?,
262            output_bg: AppColor::from_string(&config.output_bg)?,
263        })
264    }
265}
266
267impl Prompt {
268    fn from_config(config: &PromptConfig) -> Result<Self> {
269        Ok(Self {
270            text: config.text.clone(),
271            color: AppColor::from_string(&config.color)?,
272        })
273    }
274}
275
276crate::impl_default!(
277    Config,
278    Self {
279        config_path: None,
280        max_messages: DEFAULT_BUFFER_SIZE,
281        typewriter_delay: Duration::from_millis(50),
282        input_max_length: DEFAULT_BUFFER_SIZE,
283        max_history: 30,
284        poll_rate: Duration::from_millis(DEFAULT_POLL_RATE),
285        theme: Theme::default(),
286        prompt: Prompt::default(),
287        language: crate::i18n::DEFAULT_LANGUAGE.to_string(),
288        debug_info: None,
289    }
290);
291
292crate::impl_default!(
293    Theme,
294    Self {
295        input_text: AppColor::new(Color::Black),
296        input_bg: AppColor::new(Color::Black),
297        cursor: AppColor::new(Color::Black),
298        output_text: AppColor::new(Color::White),
299        output_bg: AppColor::new(Color::White),
300    }
301);
302
303crate::impl_default!(
304    Prompt,
305    Self {
306        text: "/// ".to_string(),
307        color: AppColor::new(Color::Black),
308    }
309);