ricecoder_ide/
config_hot_reload.rs1use crate::config::ConfigManager;
7use crate::error::IdeResult;
8use crate::hot_reload::HotReloadManager;
9use crate::lsp_monitor::LspMonitor;
10use crate::provider_chain::ProviderChainManager;
11use crate::types::IdeIntegrationConfig;
12use std::path::Path;
13use std::sync::Arc;
14use tokio::sync::RwLock;
15use tracing::{debug, info, warn};
16
17pub struct ConfigHotReloadCoordinator {
19 config: Arc<RwLock<IdeIntegrationConfig>>,
21 hot_reload: Arc<HotReloadManager>,
23 lsp_monitor: Arc<RwLock<Option<LspMonitor>>>,
25 provider_chain: Arc<RwLock<Option<ProviderChainManager>>>,
27 config_path: String,
29}
30
31impl ConfigHotReloadCoordinator {
32 pub fn new(config_path: impl AsRef<Path>) -> Self {
34 let config_path_str = config_path.as_ref().to_string_lossy().to_string();
35 ConfigHotReloadCoordinator {
36 config: Arc::new(RwLock::new(ConfigManager::default_config())),
37 hot_reload: Arc::new(HotReloadManager::new(&config_path_str)),
38 lsp_monitor: Arc::new(RwLock::new(None)),
39 provider_chain: Arc::new(RwLock::new(None)),
40 config_path: config_path_str,
41 }
42 }
43
44 pub async fn load_config(&self) -> IdeResult<IdeIntegrationConfig> {
46 debug!("Loading initial configuration from {}", self.config_path);
47 let config = ConfigManager::load_from_file(&self.config_path).await?;
48 let mut current_config = self.config.write().await;
49 *current_config = config.clone();
50 info!("Configuration loaded successfully");
51 Ok(config)
52 }
53
54 pub async fn get_config(&self) -> IdeIntegrationConfig {
56 self.config.read().await.clone()
57 }
58
59 pub async fn reload_config(&self) -> IdeResult<()> {
61 debug!("Reloading configuration from {}", self.config_path);
62 let new_config = ConfigManager::load_from_file(&self.config_path).await?;
63
64 let mut current_config = self.config.write().await;
65 *current_config = new_config.clone();
66
67 info!("Configuration reloaded successfully");
68
69 if let Some(provider_chain) = self.provider_chain.read().await.as_ref() {
71 provider_chain.update_config(new_config).await?;
72 }
73
74 Ok(())
75 }
76
77 pub async fn start_watching(&self, check_interval_ms: u64) -> IdeResult<()> {
79 info!(
80 "Starting configuration file watcher with {}ms interval",
81 check_interval_ms
82 );
83
84 let coordinator = self.clone_arc();
86 self.hot_reload
87 .on_config_change(Box::new(move || {
88 let coordinator = coordinator.clone();
89 tokio::spawn(async move {
90 if let Err(e) = coordinator.reload_config().await {
91 warn!("Failed to reload configuration: {}", e);
92 }
93 });
94 }))
95 .await?;
96
97 self.hot_reload.start_watching(check_interval_ms).await?;
99
100 Ok(())
101 }
102
103 pub async fn start_lsp_health_checks(&self, interval_ms: u64) -> IdeResult<()> {
105 let config = self.config.read().await;
106
107 if !config.providers.external_lsp.enabled {
108 debug!("External LSP is disabled, skipping health checks");
109 return Ok(());
110 }
111
112 let monitor = LspMonitor::new(config.providers.external_lsp.servers.clone());
113
114 let coordinator = self.clone_arc();
116 let callback = Arc::new(move |language: &str, available: bool| {
117 let coordinator = coordinator.clone();
118 let language = language.to_string();
119 tokio::spawn(async move {
120 if let Err(e) = coordinator
121 .hot_reload
122 .notify_provider_availability_changed(&language, available)
123 .await
124 {
125 warn!("Failed to notify provider availability change: {}", e);
126 }
127 });
128 });
129
130 monitor.on_availability_changed(callback).await?;
131
132 monitor.start_health_checks(interval_ms).await?;
134
135 let mut lsp_monitor = self.lsp_monitor.write().await;
136 *lsp_monitor = Some(monitor);
137
138 info!("LSP health checks started");
139 Ok(())
140 }
141
142 pub async fn set_provider_chain(&self, provider_chain: ProviderChainManager) {
144 let mut pc = self.provider_chain.write().await;
145 *pc = Some(provider_chain);
146 }
147
148 fn clone_arc(&self) -> Arc<Self> {
150 Arc::new(ConfigHotReloadCoordinator {
151 config: self.config.clone(),
152 hot_reload: self.hot_reload.clone(),
153 lsp_monitor: self.lsp_monitor.clone(),
154 provider_chain: self.provider_chain.clone(),
155 config_path: self.config_path.clone(),
156 })
157 }
158}
159
160impl Clone for ConfigHotReloadCoordinator {
161 fn clone(&self) -> Self {
162 ConfigHotReloadCoordinator {
163 config: self.config.clone(),
164 hot_reload: self.hot_reload.clone(),
165 lsp_monitor: self.lsp_monitor.clone(),
166 provider_chain: self.provider_chain.clone(),
167 config_path: self.config_path.clone(),
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[tokio::test]
177 async fn test_coordinator_creation() {
178 let coordinator = ConfigHotReloadCoordinator::new("/tmp/config.yaml");
179 assert_eq!(coordinator.config_path, "/tmp/config.yaml");
180 }
181
182 #[tokio::test]
183 async fn test_get_default_config() {
184 let coordinator = ConfigHotReloadCoordinator::new("/tmp/config.yaml");
185 let config = coordinator.get_config().await;
186 assert!(config.providers.external_lsp.enabled);
187 assert!(config.providers.builtin_providers.enabled);
188 }
189
190 #[tokio::test]
191 async fn test_clone_coordinator() {
192 let coordinator = ConfigHotReloadCoordinator::new("/tmp/config.yaml");
193 let cloned = coordinator.clone();
194 assert_eq!(cloned.config_path, coordinator.config_path);
195 }
196}