use crate::{Result, Service};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone)]
pub struct EnrichedEvent<T> {
pub registration_id: Option<u64>,
pub speaker_ip: IpAddr,
pub service: Service,
pub event_source: EventSource,
pub timestamp: SystemTime,
pub event_data: T,
}
impl<T> EnrichedEvent<T> {
pub fn new(
speaker_ip: IpAddr,
service: Service,
event_source: EventSource,
event_data: T,
) -> Self {
Self {
registration_id: None,
speaker_ip,
service,
event_source,
timestamp: SystemTime::now(),
event_data,
}
}
pub fn with_registration_id(
registration_id: u64,
speaker_ip: IpAddr,
service: Service,
event_source: EventSource,
event_data: T,
) -> Self {
Self {
registration_id: Some(registration_id),
speaker_ip,
service,
event_source,
timestamp: SystemTime::now(),
event_data,
}
}
pub fn map<U, F>(self, f: F) -> EnrichedEvent<U>
where
F: FnOnce(T) -> U,
{
EnrichedEvent {
registration_id: self.registration_id,
speaker_ip: self.speaker_ip,
service: self.service,
event_source: self.event_source,
timestamp: self.timestamp,
event_data: f(self.event_data),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventSource {
UPnPNotification {
subscription_id: String,
},
PollingDetection {
poll_interval: Duration,
},
ResyncOperation,
}
pub trait EventParser: Send + Sync {
type EventData: Send + Sync + 'static;
fn parse_upnp_event(&self, xml: &str) -> Result<Self::EventData>;
fn service_type(&self) -> Service;
}
#[derive(Default)]
pub struct EventParserRegistry {
parsers: std::collections::HashMap<Service, Box<dyn EventParserDyn>>,
}
impl EventParserRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register<P>(&mut self, parser: P)
where
P: EventParser + 'static,
P::EventData: 'static,
{
let service = parser.service_type();
self.parsers
.insert(service, Box::new(ParserWrapper::new(parser)));
}
pub fn get_parser(&self, service: &Service) -> Option<&dyn EventParserDyn> {
self.parsers.get(service).map(|p| p.as_ref())
}
pub fn has_parser(&self, service: &Service) -> bool {
self.parsers.contains_key(service)
}
pub fn supported_services(&self) -> Vec<Service> {
self.parsers.keys().cloned().collect()
}
}
pub trait EventParserDyn: Send + Sync {
fn parse_upnp_event_dyn(&self, xml: &str) -> Result<Box<dyn std::any::Any + Send + Sync>>;
fn service_type(&self) -> Service;
}
struct ParserWrapper<P> {
parser: P,
}
impl<P> ParserWrapper<P>
where
P: EventParser,
P::EventData: 'static,
{
fn new(parser: P) -> Self {
Self { parser }
}
}
impl<P> EventParserDyn for ParserWrapper<P>
where
P: EventParser + Send + Sync,
P::EventData: Send + Sync + 'static,
{
fn parse_upnp_event_dyn(&self, xml: &str) -> Result<Box<dyn std::any::Any + Send + Sync>> {
let event_data = self.parser.parse_upnp_event(xml)?;
Ok(Box::new(event_data))
}
fn service_type(&self) -> Service {
self.parser.service_type()
}
}
pub fn extract_xml_value(xml: &str, tag: &str) -> Option<String> {
let start_tag = format!("<{tag}>");
let end_tag = format!("</{tag}>");
if let Some(start_pos) = xml.find(&start_tag) {
let content_start = start_pos + start_tag.len();
if let Some(end_pos) = xml[content_start..].find(&end_tag) {
let value = xml[content_start..content_start + end_pos].trim();
if !value.is_empty() {
return Some(value.to_string());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
struct TestEventData {
value: String,
}
struct TestParser;
impl EventParser for TestParser {
type EventData = TestEventData;
fn parse_upnp_event(&self, xml: &str) -> Result<Self::EventData> {
Ok(TestEventData {
value: xml.to_string(),
})
}
fn service_type(&self) -> Service {
Service::AVTransport
}
}
#[test]
fn test_enriched_event_creation() {
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let source = EventSource::UPnPNotification {
subscription_id: "uuid:123".to_string(),
};
let data = TestEventData {
value: "test".to_string(),
};
let event = EnrichedEvent::new(ip, Service::AVTransport, source, data.clone());
assert_eq!(event.speaker_ip, ip);
assert_eq!(event.service, Service::AVTransport);
assert_eq!(event.event_data, data);
assert!(event.registration_id.is_none());
}
#[test]
fn test_enriched_event_with_registration_id() {
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let source = EventSource::UPnPNotification {
subscription_id: "uuid:123".to_string(),
};
let data = TestEventData {
value: "test".to_string(),
};
let event =
EnrichedEvent::with_registration_id(42, ip, Service::AVTransport, source, data.clone());
assert_eq!(event.registration_id, Some(42));
assert_eq!(event.event_data, data);
}
#[test]
fn test_event_mapping() {
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let source = EventSource::UPnPNotification {
subscription_id: "uuid:123".to_string(),
};
let data = TestEventData {
value: "test".to_string(),
};
let event = EnrichedEvent::new(ip, Service::AVTransport, source, data);
let mapped_event = event.map(|data| data.value.len());
assert_eq!(mapped_event.event_data, 4); }
#[test]
fn test_parser_registry() {
let mut registry = EventParserRegistry::new();
assert!(!registry.has_parser(&Service::AVTransport));
registry.register(TestParser);
assert!(registry.has_parser(&Service::AVTransport));
let supported = registry.supported_services();
assert!(supported.contains(&Service::AVTransport));
}
#[test]
fn test_xml_value_extraction() {
let xml = "<Test>value</Test>";
assert_eq!(extract_xml_value(xml, "Test"), Some("value".to_string()));
let xml = "<Test></Test>";
assert_eq!(extract_xml_value(xml, "Test"), None);
let xml = "<Other>value</Other>";
assert_eq!(extract_xml_value(xml, "Test"), None);
}
}