use std::collections::HashMap; use std::time::SystemTime; use crate::avtransport_client::AvTransportClient; use crate::connection_manager_client::ConnectionManagerClient; use crate::media_server::{MediaServerInfo, ServerId}; use crate::model::{RendererId, RendererInfo}; use crate::rendering_control_client::RenderingControlClient; use tracing::debug; #[derive(Clone, Debug)] enum DeviceKey { Renderer(RendererId), Server(ServerId), } #[derive(Debug, Default)] pub struct DeviceRegistry { renderers: HashMap, servers: HashMap, udn_index: HashMap, } /// Read-only view / trait for registry access. /// /// Pour l’instant, on ne rajoute pas AVTransport ici, on se contente /// d’ajouter les helpers dans `impl DeviceRegistry`. pub trait DeviceRegistryRead { fn list_renderers(&self) -> Vec; fn list_servers(&self) -> Vec; fn get_renderer(&self, id: &RendererId) -> Option; fn get_server(&self, id: &ServerId) -> Option; } impl DeviceRegistryRead for DeviceRegistry { fn list_renderers(&self) -> Vec { self.renderers.values().cloned().collect() } fn list_servers(&self) -> Vec { self.servers.values().cloned().collect() } fn get_renderer(&self, id: &RendererId) -> Option { self.renderers.get(id).cloned() } fn get_server(&self, id: &ServerId) -> Option { self.servers.get(id).cloned() } } #[derive(Debug)] pub enum DeviceUpdate { RendererOnline(RendererInfo), RendererOfflineById(RendererId), RendererOfflineByUdn(String), ServerOnline(MediaServerInfo), ServerOfflineById(ServerId), ServerOfflineByUdn(String), } impl DeviceRegistry { pub fn new() -> Self { Self::default() } pub fn apply_update(&mut self, update: DeviceUpdate) { match update { DeviceUpdate::RendererOnline(info) => { let udn = info.udn.to_ascii_lowercase(); let id = info.id.clone(); let mut info = info; info.online = true; info.last_seen = SystemTime::now(); self.renderers.insert(id.clone(), info); self.udn_index.insert(udn, DeviceKey::Renderer(id)); } DeviceUpdate::RendererOfflineById(id) => { if let Some(info) = self.renderers.get_mut(&id) { info.online = false; info.last_seen = SystemTime::now(); } } DeviceUpdate::RendererOfflineByUdn(udn) => { let lookup = udn.to_ascii_lowercase(); if let Some(DeviceKey::Renderer(id)) = self.udn_index.get(&lookup) { if let Some(info) = self.renderers.get_mut(id) { info.online = false; info.last_seen = SystemTime::now(); } } } DeviceUpdate::ServerOnline(info) => { let udn = info.udn.to_ascii_lowercase(); let id = info.id.clone(); let mut info = info; info.online = true; info.last_seen = SystemTime::now(); self.servers.insert(id.clone(), info); self.udn_index.insert(udn, DeviceKey::Server(id)); } DeviceUpdate::ServerOfflineById(id) => { if let Some(info) = self.servers.get_mut(&id) { info.online = false; info.last_seen = SystemTime::now(); } } DeviceUpdate::ServerOfflineByUdn(udn) => { let lookup = udn.to_ascii_lowercase(); if let Some(DeviceKey::Server(id)) = self.udn_index.get(&lookup) { if let Some(info) = self.servers.get_mut(id) { info.online = false; info.last_seen = SystemTime::now(); } } } } } /// Helper: get a renderer by UDN (case-insensitive, via udn_index). pub fn get_renderer_by_udn(&self, udn: &str) -> Option { let lookup = udn.to_ascii_lowercase(); match self.udn_index.get(&lookup) { Some(DeviceKey::Renderer(id)) => self.renderers.get(id).cloned(), _ => None, } } /// Helper: get a server by UDN (case-insensitive, via udn_index). pub fn get_server_by_udn(&self, udn: &str) -> Option { let lookup = udn.to_ascii_lowercase(); match self.udn_index.get(&lookup) { Some(DeviceKey::Server(id)) => self.servers.get(id).cloned(), _ => None, } } /// Construct an AvTransportClient for a given renderer id, if possible. /// /// Returns: /// - Some(client) if the renderer exists AND has avtransport_* fields set /// - None if renderer not found or no AVTransport service. pub fn avtransport_client_for_renderer(&self, id: &RendererId) -> Option { let info = self.renderers.get(id)?; let service_type = info.avtransport_service_type.as_ref()?; let control_url = info.avtransport_control_url.as_ref()?; Some(AvTransportClient::new( control_url.clone(), service_type.clone(), )) } /// Construct an AvTransportClient for a given UDN, if possible. pub fn avtransport_client_for_udn(&self, udn: &str) -> Option { let info = self.get_renderer_by_udn(udn)?; let service_type = info.avtransport_service_type?; let control_url = info.avtransport_control_url?; Some(AvTransportClient::new(control_url, service_type)) } /// Construct a RenderingControlClient for a given renderer id, if possible. pub fn rendering_control_client_for_renderer( &self, id: &RendererId, ) -> Option { let info = self.renderers.get(id)?; let service_type = info.rendering_control_service_type.as_ref()?; let control_url = info.rendering_control_control_url.as_ref()?; Some(RenderingControlClient::new( control_url.clone(), service_type.clone(), )) } /// Construct a ConnectionManagerClient for a given renderer id, if possible. pub fn connection_manager_client_for_renderer( &self, id: &RendererId, ) -> Option { let info = self.renderers.get(id)?; let service_type = info.connection_manager_service_type.as_ref()?; let control_url = info.connection_manager_control_url.as_ref()?; Some(ConnectionManagerClient::new( control_url.clone(), service_type.clone(), )) } pub fn mark_renderer_supports_set_next(&mut self, id: &RendererId) { if let Some(info) = self.renderers.get_mut(id) { if !info.capabilities.has_avtransport_set_next { info.capabilities.has_avtransport_set_next = true; debug!( renderer = id.0.as_str(), "Renderer now marked as supporting AVTransport.SetNextAVTransportURI" ); } } } }