1use std::sync::OnceLock;
7
8pub trait EventEmitter: Send + Sync + 'static {
14 fn emit(&self, event: &str, payload: serde_json::Value);
15
16 fn emit_json(&self, event: &str, payload: &serde_json::value::RawValue) {
19 if let Ok(value) = serde_json::from_str(payload.get()) {
20 self.emit(event, value);
21 }
22 }
23
24 fn prefers_json(&self) -> bool {
26 false
27 }
28}
29
30pub struct NoOpEmitter;
32
33impl EventEmitter for NoOpEmitter {
34 fn emit(&self, _event: &str, _payload: serde_json::Value) {}
35}
36
37static EVENT_EMITTER: OnceLock<Box<dyn EventEmitter>> = OnceLock::new();
39
40pub fn set_event_emitter(emitter: Box<dyn EventEmitter>) {
42 let _ = EVENT_EMITTER.set(emitter);
43}
44
45pub fn emit_event<T: serde::Serialize>(event: &str, payload: &T) {
47 if !crate::db::session_is_live() {
48 return;
49 }
50 if let Some(emitter) = EVENT_EMITTER.get() {
51 if emitter.prefers_json() {
53 if let Ok(json) = serde_json::value::to_raw_value(payload) {
54 emitter.emit_json(event, &json);
55 }
56 } else if let Ok(value) = serde_json::to_value(payload) {
57 emitter.emit(event, value);
58 }
59 }
60}
61
62#[derive(serde::Serialize)]
63struct MessagePayload<'a> {
64 #[serde(skip_serializing_if = "Option::is_none")]
65 old_id: Option<&'a str>,
66 message: &'a crate::types::Message,
67 chat_id: &'a str,
68}
69
70pub fn emit_message_new(chat_id: &str, message: &crate::types::Message) {
72 emit_event("message_new", &MessagePayload { old_id: None, message, chat_id });
73}
74
75pub fn emit_message_replaced(chat_id: &str, old_id: &str, message: &crate::types::Message) {
77 emit_event("message_update", &MessagePayload { old_id: Some(old_id), message, chat_id });
78}
79
80pub fn emit_event_json(event: &str, payload: serde_json::Value) {
82 if !crate::db::session_is_live() {
83 return;
84 }
85 if let Some(emitter) = EVENT_EMITTER.get() {
86 emitter.emit(event, payload);
87 }
88}
89
90pub async fn emit_message_update(chat_id: &str, old_id: &str, message: &mut crate::types::Message) {
98 let _ = crate::db::events::populate_reply_context(message).await;
99 emit_message_replaced(chat_id, old_id, message);
100}
101
102pub fn has_event_emitter() -> bool {
104 EVENT_EMITTER.get().is_some()
105}
106
107pub trait SubscriptionRefresher: Send + Sync + 'static {
117 fn refresh(&self);
118}
119
120pub struct NoOpSubscriptionRefresher;
121impl SubscriptionRefresher for NoOpSubscriptionRefresher {
122 fn refresh(&self) {}
123}
124
125static SUBSCRIPTION_REFRESHER: OnceLock<Box<dyn SubscriptionRefresher>> = OnceLock::new();
126
127pub fn set_subscription_refresher(refresher: Box<dyn SubscriptionRefresher>) {
128 let _ = SUBSCRIPTION_REFRESHER.set(refresher);
129}
130
131pub fn refresh_subscriptions() {
132 if let Some(r) = SUBSCRIPTION_REFRESHER.get() {
133 r.refresh();
134 }
135}
136
137pub trait ProgressReporter: Send + Sync {
139 fn report_progress(&self, percentage: Option<u8>, bytes: Option<u64>, bytes_per_sec: Option<f64>) -> Result<(), &'static str>;
140 fn report_complete(&self) -> Result<(), &'static str>;
141}
142
143pub struct NoOpProgressReporter;
145
146impl ProgressReporter for NoOpProgressReporter {
147 fn report_progress(&self, _: Option<u8>, _: Option<u64>, _: Option<f64>) -> Result<(), &'static str> { Ok(()) }
148 fn report_complete(&self) -> Result<(), &'static str> { Ok(()) }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::sync::atomic::{AtomicUsize, Ordering};
155
156 static REFRESH_CALLS: AtomicUsize = AtomicUsize::new(0);
165 static REFRESH_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
166
167 struct CountingRefresher;
168 impl SubscriptionRefresher for CountingRefresher {
169 fn refresh(&self) {
170 REFRESH_CALLS.fetch_add(1, Ordering::Relaxed);
171 }
172 }
173
174 fn refresh_test_setup() -> std::sync::MutexGuard<'static, ()> {
180 let guard = REFRESH_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
181 set_subscription_refresher(Box::new(CountingRefresher));
182 REFRESH_CALLS.store(0, Ordering::Relaxed);
183 guard
184 }
185
186 #[test]
187 fn refresh_subscriptions_invokes_registered_impl_once() {
188 let _g = refresh_test_setup();
189 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 0);
190
191 refresh_subscriptions();
192
193 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
194 }
195
196 #[test]
197 fn refresh_subscriptions_invokes_registered_impl_per_call() {
198 let _g = refresh_test_setup();
202
203 for _ in 0..5 {
204 refresh_subscriptions();
205 }
206
207 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 5);
208 }
209
210 #[test]
211 fn set_subscription_refresher_is_idempotent() {
212 let _g = refresh_test_setup();
218
219 struct PanicRefresher;
220 impl SubscriptionRefresher for PanicRefresher {
221 fn refresh(&self) {
222 panic!("PanicRefresher::refresh() should never be reached — OnceLock should have ignored the second set_subscription_refresher call");
223 }
224 }
225 set_subscription_refresher(Box::new(PanicRefresher));
226
227 refresh_subscriptions();
231
232 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
233 }
234
235 #[test]
236 fn refresh_subscriptions_is_thread_safe() {
237 let _g = refresh_test_setup();
241 let handles: Vec<_> = (0..10).map(|_| {
242 std::thread::spawn(|| {
243 for _ in 0..100 {
244 refresh_subscriptions();
245 }
246 })
247 }).collect();
248 for h in handles { h.join().unwrap(); }
249
250 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1_000);
251 }
252
253 #[test]
254 fn no_op_subscription_refresher_returns_quietly() {
255 let _g = refresh_test_setup();
258 let initial = REFRESH_CALLS.load(Ordering::Relaxed);
259 let r = NoOpSubscriptionRefresher;
260 r.refresh();
261 r.refresh();
262 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), initial);
263 }
264
265 #[test]
270 fn no_op_emitter_does_not_panic() {
271 let e = NoOpEmitter;
272 e.emit("test_event", serde_json::json!({"k": "v"}));
273 }
275
276 #[test]
277 fn emit_event_when_unregistered_is_silent() {
278 crate::traits::emit_event("test_event", &serde_json::json!({"k": "v"}));
284 }
285
286 #[test]
287 fn emit_event_json_when_unregistered_is_silent() {
288 crate::traits::emit_event_json("test_event", serde_json::json!({"k": "v"}));
289 }
290
291 #[test]
292 fn no_op_progress_reporter_returns_ok() {
293 let r = NoOpProgressReporter;
294 assert!(r.report_progress(Some(50), Some(1024), Some(100.0)).is_ok());
295 assert!(r.report_complete().is_ok());
296 }
297}