subx_cli/config/
manager.rs1use 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#[derive(Debug)]
16pub enum ConfigError {
17 Io(std::io::Error),
19 ParseError(String),
21 InvalidValue(String, String),
23 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#[derive(Debug, Clone)]
61pub enum ConfigChangeEvent {
62 Initial,
64 Updated,
66 Error(ConfigError),
68}
69
70pub struct ConfigManager {
72 sources: Vec<Box<dyn ConfigSource>>,
73 config: Arc<RwLock<PartialConfig>>,
74 change_notifier: watch::Sender<ConfigChangeEvent>,
75}
76
77impl ConfigManager {
78 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 pub fn add_source(mut self, source: Box<dyn ConfigSource>) -> Self {
90 self.sources.push(source);
91 self
92 }
93
94 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 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 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 pub fn subscribe_changes(&self) -> watch::Receiver<ConfigChangeEvent> {
161 self.change_notifier.subscribe()
162 }
163
164 pub fn watch(self) -> notify::Result<(watch::Receiver<ConfigChangeEvent>, RecommendedWatcher)> {
167 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 if let Err(e) = this.load() {
200 let _ = tx.send(ConfigChangeEvent::Error(e));
201 }
202 Ok((rx, watcher))
203 }
204}