subx_cli/config/
manager.rs

1//! Configuration manager core module.
2
3use std::io;
4use std::sync::{Arc, RwLock};
5
6use log::debug;
7use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher};
8use tokio::sync::watch;
9
10use crate::config::partial::PartialConfig;
11use crate::config::source::ConfigSource;
12use std::cmp::Reverse;
13
14/// Error type for configuration operations.
15#[derive(Debug)]
16pub enum ConfigError {
17    /// I/O error when reading or writing configuration.
18    Io(std::io::Error),
19    /// Parsing error for configuration content.
20    ParseError(String),
21    /// Invalid configuration value: (field, message).
22    InvalidValue(String, String),
23    /// General validation error.
24    ValidationError(String),
25}
26
27impl From<std::io::Error> for ConfigError {
28    fn from(err: std::io::Error) -> Self {
29        ConfigError::Io(err)
30    }
31}
32
33impl std::fmt::Display for ConfigError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            ConfigError::Io(err) => write!(f, "I/O error: {}", err),
37            ConfigError::ParseError(err) => write!(f, "Parse error: {}", err),
38            ConfigError::InvalidValue(field, msg) => {
39                write!(f, "Invalid value for {}: {}", field, msg)
40            }
41            ConfigError::ValidationError(err) => write!(f, "Validation error: {}", err),
42        }
43    }
44}
45
46impl std::error::Error for ConfigError {}
47
48impl Clone for ConfigError {
49    fn clone(&self) -> Self {
50        match self {
51            ConfigError::Io(err) => ConfigError::Io(io::Error::new(err.kind(), err.to_string())),
52            ConfigError::ParseError(s) => ConfigError::ParseError(s.clone()),
53            ConfigError::InvalidValue(f, m) => ConfigError::InvalidValue(f.clone(), m.clone()),
54            ConfigError::ValidationError(s) => ConfigError::ValidationError(s.clone()),
55        }
56    }
57}
58
59/// Events signaled when configuration changes or errors occur.
60#[derive(Debug, Clone)]
61pub enum ConfigChangeEvent {
62    /// Initial event when watcher starts.
63    Initial,
64    /// Configuration successfully updated.
65    Updated,
66    /// Error occurred during configuration load.
67    Error(ConfigError),
68}
69
70/// Manager to load and merge configuration from multiple sources.
71pub struct ConfigManager {
72    sources: Vec<Box<dyn ConfigSource>>,
73    config: Arc<RwLock<PartialConfig>>,
74    change_notifier: watch::Sender<ConfigChangeEvent>,
75}
76
77impl ConfigManager {
78    /// Create a new configuration manager.
79    pub fn new() -> Self {
80        let (tx, _rx) = watch::channel(ConfigChangeEvent::Initial);
81        Self {
82            sources: Vec::new(),
83            config: Arc::new(RwLock::new(PartialConfig::default())),
84            change_notifier: tx,
85        }
86    }
87
88    /// Add a configuration source.
89    pub fn add_source(mut self, source: Box<dyn ConfigSource>) -> Self {
90        self.sources.push(source);
91        self
92    }
93
94    /// Load configuration by merging all sources in order of priority.
95    pub fn load(&self) -> Result<(), ConfigError> {
96        debug!("ConfigManager: Starting to load configuration");
97        let result: Result<(), ConfigError> = (|| {
98            let mut merged = PartialConfig::default();
99            let mut sources = self.sources.iter().collect::<Vec<_>>();
100            // 按優先順序由低到高合併:先載入優先權低的來源,再讓優先權高的來源覆蓋
101            sources.sort_by_key(|s| Reverse(s.priority()));
102
103            debug!("ConfigManager: Loading {} sources in order", sources.len());
104            for (i, source) in sources.iter().enumerate() {
105                debug!(
106                    "ConfigManager: Loading source {} - '{}' (priority {})",
107                    i + 1,
108                    source.source_name(),
109                    source.priority()
110                );
111                let cfg = source.load()?;
112                debug!(
113                    "ConfigManager: Source '{}' returned cfg.ai.max_sample_length = {:?}",
114                    source.source_name(),
115                    cfg.ai.max_sample_length
116                );
117                merged.merge(cfg)?;
118                debug!(
119                    "ConfigManager: After merging '{}', merged.ai.max_sample_length = {:?}",
120                    source.source_name(),
121                    merged.ai.max_sample_length
122                );
123            }
124            let mut lock = self.config.write().unwrap();
125            *lock = merged;
126            debug!(
127                "ConfigManager: Final stored config.ai.max_sample_length = {:?}",
128                lock.ai.max_sample_length
129            );
130            Ok(())
131        })();
132        match result {
133            Ok(_) => {
134                let _ = self.change_notifier.send(ConfigChangeEvent::Updated);
135                Ok(())
136            }
137            Err(err) => {
138                let _ = self
139                    .change_notifier
140                    .send(ConfigChangeEvent::Error(err.clone()));
141                Err(err)
142            }
143        }
144    }
145
146    /// Get current configuration.
147    pub fn config(&self) -> Arc<RwLock<PartialConfig>> {
148        Arc::clone(&self.config)
149    }
150}
151
152impl Default for ConfigManager {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158impl ConfigManager {
159    /// Subscribe to configuration change events.
160    pub fn subscribe_changes(&self) -> watch::Receiver<ConfigChangeEvent> {
161        self.change_notifier.subscribe()
162    }
163
164    /// Watch file-based configuration sources for changes and auto-reload.
165    /// Returns a receiver for change events and a watcher handle to keep alive.
166    pub fn watch(self) -> notify::Result<(watch::Receiver<ConfigChangeEvent>, RecommendedWatcher)> {
167        // Wrap manager in Arc to share with watcher closure
168        let this = Arc::new(self);
169        let tx = this.change_notifier.clone();
170        let rx = this.change_notifier.subscribe();
171        let this_clone = Arc::clone(&this);
172        let tx_clone = tx.clone();
173        let mut watcher: RecommendedWatcher = RecommendedWatcher::new(
174            move |res: notify::Result<notify::Event>| match res {
175                Ok(event) => {
176                    if matches!(
177                        event.kind,
178                        EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)
179                    ) {
180                        if let Err(e) = this_clone.load() {
181                            let _ = tx_clone.send(ConfigChangeEvent::Error(e));
182                        }
183                    }
184                }
185                Err(err) => {
186                    let _ = tx_clone.send(ConfigChangeEvent::Error(ConfigError::Io(
187                        io::Error::other(err.to_string()),
188                    )));
189                }
190            },
191            notify::Config::default(),
192        )?;
193        for source in &this.sources {
194            for path in source.watch_paths() {
195                watcher.watch(&path, RecursiveMode::NonRecursive)?;
196            }
197        }
198        // initial load trigger
199        if let Err(e) = this.load() {
200            let _ = tx.send(ConfigChangeEvent::Error(e));
201        }
202        Ok((rx, watcher))
203    }
204}