ricecoder_ide/
config_hot_reload.rs

1//! Configuration hot-reload integration
2//!
3//! This module integrates configuration loading, hot-reload watching, and provider
4//! chain updates. It enables runtime configuration changes without restart.
5
6use 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
17/// Configuration hot-reload coordinator
18pub struct ConfigHotReloadCoordinator {
19    /// Current configuration
20    config: Arc<RwLock<IdeIntegrationConfig>>,
21    /// Hot-reload manager
22    hot_reload: Arc<HotReloadManager>,
23    /// LSP monitor
24    lsp_monitor: Arc<RwLock<Option<LspMonitor>>>,
25    /// Provider chain manager
26    provider_chain: Arc<RwLock<Option<ProviderChainManager>>>,
27    /// Configuration file path
28    config_path: String,
29}
30
31impl ConfigHotReloadCoordinator {
32    /// Create a new configuration hot-reload coordinator
33    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    /// Load initial configuration
45    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    /// Get current configuration
55    pub async fn get_config(&self) -> IdeIntegrationConfig {
56        self.config.read().await.clone()
57    }
58
59    /// Reload configuration from file
60    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        // Update provider chain if it exists
70        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    /// Start watching configuration file for changes
78    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        // Register callback for configuration changes
85        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        // Start file watcher
98        self.hot_reload.start_watching(check_interval_ms).await?;
99
100        Ok(())
101    }
102
103    /// Start LSP health checks
104    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        // Register callback for availability changes
115        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        // Start health checks
133        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    /// Set provider chain manager for updates
143    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    /// Clone as Arc for use in callbacks
149    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}