ricecoder_ide/
hot_reload.rs1use crate::error::{IdeError, IdeResult};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use tracing::{debug, info, warn};
12
13pub type ConfigChangeCallback = Box<dyn Fn() + Send + Sync>;
15
16pub type ProviderAvailabilityCallback = Box<dyn Fn(&str, bool) + Send + Sync>;
18
19pub struct HotReloadManager {
21 config_path: PathBuf,
23 last_modified: Arc<RwLock<Option<std::time::SystemTime>>>,
25 config_callbacks: Arc<RwLock<Vec<Arc<ConfigChangeCallback>>>>,
27 provider_callbacks: Arc<RwLock<Vec<Arc<ProviderAvailabilityCallback>>>>,
29}
30
31impl HotReloadManager {
32 pub fn new(config_path: impl AsRef<Path>) -> Self {
34 HotReloadManager {
35 config_path: config_path.as_ref().to_path_buf(),
36 last_modified: Arc::new(RwLock::new(None)),
37 config_callbacks: Arc::new(RwLock::new(Vec::new())),
38 provider_callbacks: Arc::new(RwLock::new(Vec::new())),
39 }
40 }
41
42 pub async fn on_config_change(&self, callback: ConfigChangeCallback) -> IdeResult<()> {
44 debug!("Registering configuration change callback");
45 let mut callbacks = self.config_callbacks.write().await;
46 callbacks.push(Arc::new(callback));
47 Ok(())
48 }
49
50 pub async fn on_provider_availability_change(
52 &self,
53 callback: ProviderAvailabilityCallback,
54 ) -> IdeResult<()> {
55 debug!("Registering provider availability change callback");
56 let mut callbacks = self.provider_callbacks.write().await;
57 callbacks.push(Arc::new(callback));
58 Ok(())
59 }
60
61 pub async fn check_config_changed(&self) -> IdeResult<bool> {
63 let metadata = tokio::fs::metadata(&self.config_path)
64 .await
65 .map_err(|e| {
66 IdeError::config_error(format!(
67 "Failed to check configuration file metadata: {}",
68 e
69 ))
70 })?;
71
72 let modified = metadata.modified().map_err(|e| {
73 IdeError::config_error(format!(
74 "Failed to get file modification time: {}",
75 e
76 ))
77 })?;
78
79 let mut last_modified = self.last_modified.write().await;
80 let changed = match *last_modified {
81 None => {
82 *last_modified = Some(modified);
83 true
84 }
85 Some(prev) => {
86 if modified > prev {
87 *last_modified = Some(modified);
88 true
89 } else {
90 false
91 }
92 }
93 };
94
95 if changed {
96 debug!("Configuration file has changed");
97 }
98
99 Ok(changed)
100 }
101
102 pub async fn notify_config_changed(&self) -> IdeResult<()> {
104 info!("Notifying configuration change callbacks");
105 let callbacks = self.config_callbacks.read().await;
106 for callback in callbacks.iter() {
107 callback();
108 }
109 Ok(())
110 }
111
112 pub async fn notify_provider_availability_changed(
114 &self,
115 language: &str,
116 available: bool,
117 ) -> IdeResult<()> {
118 info!(
119 "Notifying provider availability change: {} (available: {})",
120 language, available
121 );
122 let callbacks = self.provider_callbacks.read().await;
123 for callback in callbacks.iter() {
124 callback(language, available);
125 }
126 Ok(())
127 }
128
129 pub async fn start_watching(&self, check_interval_ms: u64) -> IdeResult<()> {
131 info!(
132 "Starting configuration file watcher with {}ms interval",
133 check_interval_ms
134 );
135
136 let config_path = self.config_path.clone();
137 let last_modified = self.last_modified.clone();
138 let config_callbacks = self.config_callbacks.clone();
139
140 tokio::spawn(async move {
141 loop {
142 tokio::time::sleep(tokio::time::Duration::from_millis(check_interval_ms)).await;
143
144 match tokio::fs::metadata(&config_path).await {
145 Ok(metadata) => {
146 if let Ok(modified) = metadata.modified() {
147 let mut last_mod = last_modified.write().await;
148 if let Some(prev) = *last_mod {
149 if modified > prev {
150 *last_mod = Some(modified);
151 info!("Configuration file changed, notifying callbacks");
152 let callbacks = config_callbacks.read().await;
153 for callback in callbacks.iter() {
154 callback();
155 }
156 }
157 } else {
158 *last_mod = Some(modified);
159 }
160 }
161 }
162 Err(e) => {
163 warn!("Failed to check configuration file: {}", e);
164 }
165 }
166 }
167 });
168
169 Ok(())
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use std::sync::atomic::{AtomicUsize, Ordering};
177
178 #[tokio::test]
179 async fn test_hot_reload_manager_creation() {
180 let manager = HotReloadManager::new("/tmp/config.yaml");
181 assert_eq!(manager.config_path, PathBuf::from("/tmp/config.yaml"));
182 }
183
184 #[tokio::test]
185 async fn test_register_config_change_callback() {
186 let manager = HotReloadManager::new("/tmp/config.yaml");
187 let callback = Box::new(|| {});
188 assert!(manager.on_config_change(callback).await.is_ok());
189 }
190
191 #[tokio::test]
192 async fn test_register_provider_availability_callback() {
193 let manager = HotReloadManager::new("/tmp/config.yaml");
194 let callback = Box::new(|_: &str, _: bool| {});
195 assert!(manager.on_provider_availability_change(callback).await.is_ok());
196 }
197
198 #[tokio::test]
199 async fn test_notify_config_changed() {
200 let manager = HotReloadManager::new("/tmp/config.yaml");
201 let call_count = Arc::new(AtomicUsize::new(0));
202 let count_clone = call_count.clone();
203
204 let callback = Box::new(move || {
205 count_clone.fetch_add(1, Ordering::SeqCst);
206 });
207
208 manager.on_config_change(callback).await.unwrap();
209 manager.notify_config_changed().await.unwrap();
210
211 assert_eq!(call_count.load(Ordering::SeqCst), 1);
212 }
213
214 #[tokio::test]
215 async fn test_notify_provider_availability_changed() {
216 let manager = HotReloadManager::new("/tmp/config.yaml");
217 let call_count = Arc::new(AtomicUsize::new(0));
218 let count_clone = call_count.clone();
219
220 let callback = Box::new(move |_: &str, _: bool| {
221 count_clone.fetch_add(1, Ordering::SeqCst);
222 });
223
224 manager
225 .on_provider_availability_change(callback)
226 .await
227 .unwrap();
228 manager
229 .notify_provider_availability_changed("rust", true)
230 .await
231 .unwrap();
232
233 assert_eq!(call_count.load(Ordering::SeqCst), 1);
234 }
235
236 #[tokio::test]
237 async fn test_multiple_callbacks() {
238 let manager = HotReloadManager::new("/tmp/config.yaml");
239 let count1 = Arc::new(AtomicUsize::new(0));
240 let count2 = Arc::new(AtomicUsize::new(0));
241
242 let c1 = count1.clone();
243 manager
244 .on_config_change(Box::new(move || {
245 c1.fetch_add(1, Ordering::SeqCst);
246 }))
247 .await
248 .unwrap();
249
250 let c2 = count2.clone();
251 manager
252 .on_config_change(Box::new(move || {
253 c2.fetch_add(1, Ordering::SeqCst);
254 }))
255 .await
256 .unwrap();
257
258 manager.notify_config_changed().await.unwrap();
259
260 assert_eq!(count1.load(Ordering::SeqCst), 1);
261 assert_eq!(count2.load(Ordering::SeqCst), 1);
262 }
263}