Skip to main content

sonos_api/events/
types.rs

1//! Common event types and framework for Sonos UPnP events
2//!
3//! This module provides the core event infrastructure that is service-agnostic.
4//! Service-specific event types are defined in their respective service modules.
5
6use crate::{Result, Service};
7use serde::{Deserialize, Serialize};
8use std::net::IpAddr;
9use std::time::{Duration, SystemTime};
10
11/// An enriched event that includes context and source information
12#[derive(Debug, Clone)]
13pub struct EnrichedEvent<T> {
14    /// Registration ID this event belongs to (for sonos-stream integration)
15    pub registration_id: Option<u64>,
16
17    /// IP address of the speaker that generated this event
18    pub speaker_ip: IpAddr,
19
20    /// UPnP service that generated this event
21    pub service: Service,
22
23    /// Source of this event (UPnP notification or polling)
24    pub event_source: EventSource,
25
26    /// Timestamp when this event was processed
27    pub timestamp: SystemTime,
28
29    /// The actual service-specific event data
30    pub event_data: T,
31}
32
33impl<T> EnrichedEvent<T> {
34    /// Create a new enriched event
35    pub fn new(
36        speaker_ip: IpAddr,
37        service: Service,
38        event_source: EventSource,
39        event_data: T,
40    ) -> Self {
41        Self {
42            registration_id: None,
43            speaker_ip,
44            service,
45            event_source,
46            timestamp: SystemTime::now(),
47            event_data,
48        }
49    }
50
51    /// Create a new enriched event with registration ID (for sonos-stream integration)
52    pub fn with_registration_id(
53        registration_id: u64,
54        speaker_ip: IpAddr,
55        service: Service,
56        event_source: EventSource,
57        event_data: T,
58    ) -> Self {
59        Self {
60            registration_id: Some(registration_id),
61            speaker_ip,
62            service,
63            event_source,
64            timestamp: SystemTime::now(),
65            event_data,
66        }
67    }
68
69    /// Map the event data to a different type
70    pub fn map<U, F>(self, f: F) -> EnrichedEvent<U>
71    where
72        F: FnOnce(T) -> U,
73    {
74        EnrichedEvent {
75            registration_id: self.registration_id,
76            speaker_ip: self.speaker_ip,
77            service: self.service,
78            event_source: self.event_source,
79            timestamp: self.timestamp,
80            event_data: f(self.event_data),
81        }
82    }
83}
84
85/// Source of an event - indicates whether it came from UPnP events or polling
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub enum EventSource {
88    /// Event came from a UPnP NOTIFY message
89    UPnPNotification {
90        /// UPnP subscription ID
91        subscription_id: String,
92    },
93
94    /// Event was generated by polling device state
95    PollingDetection {
96        /// Current polling interval
97        poll_interval: Duration,
98    },
99
100    /// Event was generated during resync operation
101    ResyncOperation,
102}
103
104/// Trait for parsing service-specific events from XML
105pub trait EventParser: Send + Sync {
106    /// The event data type this parser produces
107    type EventData: Send + Sync + 'static;
108
109    /// Parse UPnP event XML and extract service-specific event data
110    fn parse_upnp_event(&self, xml: &str) -> Result<Self::EventData>;
111
112    /// Get the service type this parser handles
113    fn service_type(&self) -> Service;
114}
115
116/// Registry for service-specific event parsers
117#[derive(Default)]
118pub struct EventParserRegistry {
119    parsers: std::collections::HashMap<Service, Box<dyn EventParserDyn>>,
120}
121
122impl EventParserRegistry {
123    /// Create a new empty registry
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    /// Register a parser for a specific service
129    pub fn register<P>(&mut self, parser: P)
130    where
131        P: EventParser + 'static,
132        P::EventData: 'static,
133    {
134        let service = parser.service_type();
135        self.parsers
136            .insert(service, Box::new(ParserWrapper::new(parser)));
137    }
138
139    /// Get a parser for a specific service
140    pub fn get_parser(&self, service: &Service) -> Option<&dyn EventParserDyn> {
141        self.parsers.get(service).map(|p| p.as_ref())
142    }
143
144    /// Check if a parser is registered for a service
145    pub fn has_parser(&self, service: &Service) -> bool {
146        self.parsers.contains_key(service)
147    }
148
149    /// Get all registered service types
150    pub fn supported_services(&self) -> Vec<Service> {
151        self.parsers.keys().cloned().collect()
152    }
153}
154
155/// Dynamic trait for type-erased event parsing
156pub trait EventParserDyn: Send + Sync {
157    /// Parse event XML to a dynamic event type
158    fn parse_upnp_event_dyn(&self, xml: &str) -> Result<Box<dyn std::any::Any + Send + Sync>>;
159
160    /// Get the service type
161    fn service_type(&self) -> Service;
162}
163
164/// Wrapper to convert typed EventParser to dynamic EventParserDyn
165struct ParserWrapper<P> {
166    parser: P,
167}
168
169impl<P> ParserWrapper<P>
170where
171    P: EventParser,
172    P::EventData: 'static,
173{
174    fn new(parser: P) -> Self {
175        Self { parser }
176    }
177}
178
179impl<P> EventParserDyn for ParserWrapper<P>
180where
181    P: EventParser + Send + Sync,
182    P::EventData: Send + Sync + 'static,
183{
184    fn parse_upnp_event_dyn(&self, xml: &str) -> Result<Box<dyn std::any::Any + Send + Sync>> {
185        let event_data = self.parser.parse_upnp_event(xml)?;
186        Ok(Box::new(event_data))
187    }
188
189    fn service_type(&self) -> Service {
190        self.parser.service_type()
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[derive(Debug, Clone, PartialEq)]
199    struct TestEventData {
200        value: String,
201    }
202
203    struct TestParser;
204
205    impl EventParser for TestParser {
206        type EventData = TestEventData;
207
208        fn parse_upnp_event(&self, xml: &str) -> Result<Self::EventData> {
209            Ok(TestEventData {
210                value: xml.to_string(),
211            })
212        }
213
214        fn service_type(&self) -> Service {
215            Service::AVTransport
216        }
217    }
218
219    #[test]
220    fn test_enriched_event_creation() {
221        let ip: IpAddr = "192.168.1.100".parse().unwrap();
222        let source = EventSource::UPnPNotification {
223            subscription_id: "uuid:123".to_string(),
224        };
225        let data = TestEventData {
226            value: "test".to_string(),
227        };
228
229        let event = EnrichedEvent::new(ip, Service::AVTransport, source, data.clone());
230
231        assert_eq!(event.speaker_ip, ip);
232        assert_eq!(event.service, Service::AVTransport);
233        assert_eq!(event.event_data, data);
234        assert!(event.registration_id.is_none());
235    }
236
237    #[test]
238    fn test_enriched_event_with_registration_id() {
239        let ip: IpAddr = "192.168.1.100".parse().unwrap();
240        let source = EventSource::UPnPNotification {
241            subscription_id: "uuid:123".to_string(),
242        };
243        let data = TestEventData {
244            value: "test".to_string(),
245        };
246
247        let event =
248            EnrichedEvent::with_registration_id(42, ip, Service::AVTransport, source, data.clone());
249
250        assert_eq!(event.registration_id, Some(42));
251        assert_eq!(event.event_data, data);
252    }
253
254    #[test]
255    fn test_event_mapping() {
256        let ip: IpAddr = "192.168.1.100".parse().unwrap();
257        let source = EventSource::UPnPNotification {
258            subscription_id: "uuid:123".to_string(),
259        };
260        let data = TestEventData {
261            value: "test".to_string(),
262        };
263
264        let event = EnrichedEvent::new(ip, Service::AVTransport, source, data);
265        let mapped_event = event.map(|data| data.value.len());
266
267        assert_eq!(mapped_event.event_data, 4); // "test".len()
268    }
269
270    #[test]
271    fn test_parser_registry() {
272        let mut registry = EventParserRegistry::new();
273        assert!(!registry.has_parser(&Service::AVTransport));
274
275        registry.register(TestParser);
276        assert!(registry.has_parser(&Service::AVTransport));
277
278        let supported = registry.supported_services();
279        assert!(supported.contains(&Service::AVTransport));
280    }
281}