1use std::sync::OnceLock;
7
8pub trait EventEmitter: Send + Sync + 'static {
14 fn emit(&self, event: &str, payload: serde_json::Value);
15}
16
17pub struct NoOpEmitter;
19
20impl EventEmitter for NoOpEmitter {
21 fn emit(&self, _event: &str, _payload: serde_json::Value) {}
22}
23
24static EVENT_EMITTER: OnceLock<Box<dyn EventEmitter>> = OnceLock::new();
26
27pub fn set_event_emitter(emitter: Box<dyn EventEmitter>) {
29 let _ = EVENT_EMITTER.set(emitter);
30}
31
32pub fn emit_event<T: serde::Serialize>(event: &str, payload: &T) {
34 if let Some(emitter) = EVENT_EMITTER.get() {
35 if let Ok(value) = serde_json::to_value(payload) {
36 emitter.emit(event, value);
37 }
38 }
39}
40
41pub fn emit_event_json(event: &str, payload: serde_json::Value) {
43 if let Some(emitter) = EVENT_EMITTER.get() {
44 emitter.emit(event, payload);
45 }
46}
47
48pub async fn emit_message_update(chat_id: &str, old_id: &str, message: &mut crate::types::Message) {
56 let _ = crate::db::events::populate_reply_context(message).await;
57 emit_event(
58 "message_update",
59 &serde_json::json!({ "old_id": old_id, "message": &*message, "chat_id": chat_id }),
60 );
61}
62
63pub fn has_event_emitter() -> bool {
65 EVENT_EMITTER.get().is_some()
66}
67
68pub trait SubscriptionRefresher: Send + Sync + 'static {
78 fn refresh(&self);
79}
80
81pub struct NoOpSubscriptionRefresher;
82impl SubscriptionRefresher for NoOpSubscriptionRefresher {
83 fn refresh(&self) {}
84}
85
86static SUBSCRIPTION_REFRESHER: OnceLock<Box<dyn SubscriptionRefresher>> = OnceLock::new();
87
88pub fn set_subscription_refresher(refresher: Box<dyn SubscriptionRefresher>) {
89 let _ = SUBSCRIPTION_REFRESHER.set(refresher);
90}
91
92pub fn refresh_subscriptions() {
93 if let Some(r) = SUBSCRIPTION_REFRESHER.get() {
94 r.refresh();
95 }
96}
97
98pub trait ProgressReporter: Send + Sync {
100 fn report_progress(&self, percentage: Option<u8>, bytes: Option<u64>, bytes_per_sec: Option<f64>) -> Result<(), &'static str>;
101 fn report_complete(&self) -> Result<(), &'static str>;
102}
103
104pub struct NoOpProgressReporter;
106
107impl ProgressReporter for NoOpProgressReporter {
108 fn report_progress(&self, _: Option<u8>, _: Option<u64>, _: Option<f64>) -> Result<(), &'static str> { Ok(()) }
109 fn report_complete(&self) -> Result<(), &'static str> { Ok(()) }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::sync::atomic::{AtomicUsize, Ordering};
116
117 static REFRESH_CALLS: AtomicUsize = AtomicUsize::new(0);
126 static REFRESH_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
127
128 struct CountingRefresher;
129 impl SubscriptionRefresher for CountingRefresher {
130 fn refresh(&self) {
131 REFRESH_CALLS.fetch_add(1, Ordering::Relaxed);
132 }
133 }
134
135 fn refresh_test_setup() -> std::sync::MutexGuard<'static, ()> {
141 let guard = REFRESH_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
142 set_subscription_refresher(Box::new(CountingRefresher));
143 REFRESH_CALLS.store(0, Ordering::Relaxed);
144 guard
145 }
146
147 #[test]
148 fn refresh_subscriptions_invokes_registered_impl_once() {
149 let _g = refresh_test_setup();
150 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 0);
151
152 refresh_subscriptions();
153
154 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
155 }
156
157 #[test]
158 fn refresh_subscriptions_invokes_registered_impl_per_call() {
159 let _g = refresh_test_setup();
163
164 for _ in 0..5 {
165 refresh_subscriptions();
166 }
167
168 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 5);
169 }
170
171 #[test]
172 fn set_subscription_refresher_is_idempotent() {
173 let _g = refresh_test_setup();
179
180 struct PanicRefresher;
181 impl SubscriptionRefresher for PanicRefresher {
182 fn refresh(&self) {
183 panic!("PanicRefresher::refresh() should never be reached — OnceLock should have ignored the second set_subscription_refresher call");
184 }
185 }
186 set_subscription_refresher(Box::new(PanicRefresher));
187
188 refresh_subscriptions();
192
193 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
194 }
195
196 #[test]
197 fn refresh_subscriptions_is_thread_safe() {
198 let _g = refresh_test_setup();
202 let handles: Vec<_> = (0..10).map(|_| {
203 std::thread::spawn(|| {
204 for _ in 0..100 {
205 refresh_subscriptions();
206 }
207 })
208 }).collect();
209 for h in handles { h.join().unwrap(); }
210
211 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1_000);
212 }
213
214 #[test]
215 fn no_op_subscription_refresher_returns_quietly() {
216 let _g = refresh_test_setup();
219 let initial = REFRESH_CALLS.load(Ordering::Relaxed);
220 let r = NoOpSubscriptionRefresher;
221 r.refresh();
222 r.refresh();
223 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), initial);
224 }
225
226 #[test]
231 fn no_op_emitter_does_not_panic() {
232 let e = NoOpEmitter;
233 e.emit("test_event", serde_json::json!({"k": "v"}));
234 }
236
237 #[test]
238 fn emit_event_when_unregistered_is_silent() {
239 crate::traits::emit_event("test_event", &serde_json::json!({"k": "v"}));
245 }
246
247 #[test]
248 fn emit_event_json_when_unregistered_is_silent() {
249 crate::traits::emit_event_json("test_event", serde_json::json!({"k": "v"}));
250 }
251
252 #[test]
253 fn no_op_progress_reporter_returns_ok() {
254 let r = NoOpProgressReporter;
255 assert!(r.report_progress(Some(50), Some(1024), Some(100.0)).is_ok());
256 assert!(r.report_complete().is_ok());
257 }
258}