use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::error::{RegistryError, RegistryResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RegistrationId(u64);
impl RegistrationId {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn as_u64(&self) -> u64 {
self.0
}
}
impl std::fmt::Display for RegistrationId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "reg-{}", self.0)
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct SpeakerServicePair {
pub speaker_ip: IpAddr,
pub service: sonos_api::Service,
}
impl SpeakerServicePair {
pub fn new(speaker_ip: IpAddr, service: sonos_api::Service) -> Self {
Self {
speaker_ip,
service,
}
}
}
impl std::fmt::Display for SpeakerServicePair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{:?}", self.speaker_ip, self.service)
}
}
pub struct SpeakerServiceRegistry {
registrations: Arc<RwLock<HashMap<RegistrationId, SpeakerServicePair>>>,
pair_to_registration: Arc<RwLock<HashMap<SpeakerServicePair, RegistrationId>>>,
next_id: Arc<AtomicU64>,
max_registrations: usize,
}
impl SpeakerServiceRegistry {
pub fn new(max_registrations: usize) -> Self {
Self {
registrations: Arc::new(RwLock::new(HashMap::new())),
pair_to_registration: Arc::new(RwLock::new(HashMap::new())),
next_id: Arc::new(AtomicU64::new(1)),
max_registrations,
}
}
pub async fn register(
&self,
speaker_ip: IpAddr,
service: sonos_api::Service,
) -> RegistryResult<RegistrationId> {
let pair = SpeakerServicePair::new(speaker_ip, service);
{
let pair_lookup = self.pair_to_registration.read().await;
if let Some(existing_id) = pair_lookup.get(&pair) {
return Ok(*existing_id);
}
}
let mut registrations = self.registrations.write().await;
let mut pair_lookup = self.pair_to_registration.write().await;
if let Some(existing_id) = pair_lookup.get(&pair) {
return Ok(*existing_id);
}
if registrations.len() >= self.max_registrations {
return Err(RegistryError::RegistryFull {
max_registrations: self.max_registrations,
});
}
let registration_id = RegistrationId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
registrations.insert(registration_id, pair.clone());
pair_lookup.insert(pair, registration_id);
Ok(registration_id)
}
pub async fn unregister(
&self,
registration_id: RegistrationId,
) -> RegistryResult<SpeakerServicePair> {
let mut registrations = self.registrations.write().await;
let mut pair_lookup = self.pair_to_registration.write().await;
let pair = registrations
.remove(®istration_id)
.ok_or(RegistryError::NotFound(registration_id))?;
pair_lookup.remove(&pair);
Ok(pair)
}
pub async fn is_registered(&self, speaker_ip: IpAddr, service: sonos_api::Service) -> bool {
let pair = SpeakerServicePair::new(speaker_ip, service);
let pair_lookup = self.pair_to_registration.read().await;
pair_lookup.contains_key(&pair)
}
pub async fn get_registration_id(
&self,
speaker_ip: IpAddr,
service: sonos_api::Service,
) -> Option<RegistrationId> {
let pair = SpeakerServicePair::new(speaker_ip, service);
let pair_lookup = self.pair_to_registration.read().await;
pair_lookup.get(&pair).copied()
}
pub async fn get_pair(&self, registration_id: RegistrationId) -> Option<SpeakerServicePair> {
let registrations = self.registrations.read().await;
registrations.get(®istration_id).cloned()
}
pub async fn list_registrations(&self) -> Vec<(RegistrationId, SpeakerServicePair)> {
let registrations = self.registrations.read().await;
registrations
.iter()
.map(|(id, pair)| (*id, pair.clone()))
.collect()
}
pub async fn count(&self) -> usize {
let registrations = self.registrations.read().await;
registrations.len()
}
pub fn max_registrations(&self) -> usize {
self.max_registrations
}
pub async fn clear(&self) {
let mut registrations = self.registrations.write().await;
let mut pair_lookup = self.pair_to_registration.write().await;
registrations.clear();
pair_lookup.clear();
}
pub async fn stats(&self) -> RegistryStats {
let registrations = self.registrations.read().await;
let count = registrations.len();
let mut service_counts = HashMap::new();
for pair in registrations.values() {
*service_counts.entry(pair.service).or_insert(0) += 1;
}
RegistryStats {
total_registrations: count,
max_registrations: self.max_registrations,
service_breakdown: service_counts,
}
}
}
#[derive(Debug, Clone)]
pub struct RegistryStats {
pub total_registrations: usize,
pub max_registrations: usize,
pub service_breakdown: HashMap<sonos_api::Service, usize>,
}
impl std::fmt::Display for RegistryStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Registry Stats:")?;
writeln!(
f,
" Total: {}/{}",
self.total_registrations, self.max_registrations
)?;
writeln!(f, " Service breakdown:")?;
for (service, count) in &self.service_breakdown {
writeln!(f, " {service:?}: {count}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_registration_basic() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let service = sonos_api::Service::AVTransport;
let reg_id = registry.register(ip, service).await.unwrap();
assert!(registry.is_registered(ip, service).await);
assert_eq!(registry.count().await, 1);
let reg_id2 = registry.register(ip, service).await.unwrap();
assert_eq!(reg_id, reg_id2);
assert_eq!(registry.count().await, 1); }
#[tokio::test]
async fn test_duplicate_detection() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let service = sonos_api::Service::AVTransport;
let reg_id1 = registry.register(ip, service).await.unwrap();
let reg_id2 = registry.register(ip, service).await.unwrap();
assert_eq!(reg_id1, reg_id2);
assert_eq!(registry.count().await, 1);
}
#[tokio::test]
async fn test_different_services() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let av_reg = registry
.register(ip, sonos_api::Service::AVTransport)
.await
.unwrap();
let rc_reg = registry
.register(ip, sonos_api::Service::RenderingControl)
.await
.unwrap();
assert_ne!(av_reg, rc_reg);
assert_eq!(registry.count().await, 2);
}
#[tokio::test]
async fn test_unregistration() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let service = sonos_api::Service::AVTransport;
let reg_id = registry.register(ip, service).await.unwrap();
assert_eq!(registry.count().await, 1);
let pair = registry.unregister(reg_id).await.unwrap();
assert_eq!(pair.speaker_ip, ip);
assert_eq!(pair.service, service);
assert_eq!(registry.count().await, 0);
assert!(!registry.is_registered(ip, service).await);
}
#[tokio::test]
async fn test_registration_limit() {
let registry = SpeakerServiceRegistry::new(2);
let ip1: IpAddr = "192.168.1.100".parse().unwrap();
let ip2: IpAddr = "192.168.1.101".parse().unwrap();
let ip3: IpAddr = "192.168.1.102".parse().unwrap();
let service = sonos_api::Service::AVTransport;
assert!(registry.register(ip1, service).await.is_ok());
assert!(registry.register(ip2, service).await.is_ok());
let result = registry.register(ip3, service).await;
assert!(matches!(result, Err(RegistryError::RegistryFull { .. })));
}
#[tokio::test]
async fn test_lookups() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let service = sonos_api::Service::AVTransport;
let reg_id = registry.register(ip, service).await.unwrap();
assert_eq!(
registry.get_registration_id(ip, service).await,
Some(reg_id)
);
assert_eq!(
registry.get_pair(reg_id).await,
Some(SpeakerServicePair::new(ip, service))
);
assert_eq!(
registry
.get_registration_id(ip, sonos_api::Service::RenderingControl)
.await,
None
);
assert_eq!(registry.get_pair(RegistrationId::new(999)).await, None);
}
#[tokio::test]
async fn test_list_and_stats() {
let registry = SpeakerServiceRegistry::new(100);
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let _av_reg = registry
.register(ip, sonos_api::Service::AVTransport)
.await
.unwrap();
let _rc_reg = registry
.register(ip, sonos_api::Service::RenderingControl)
.await
.unwrap();
let registrations = registry.list_registrations().await;
assert_eq!(registrations.len(), 2);
let stats = registry.stats().await;
assert_eq!(stats.total_registrations, 2);
assert_eq!(
stats
.service_breakdown
.get(&sonos_api::Service::AVTransport),
Some(&1)
);
assert_eq!(
stats
.service_breakdown
.get(&sonos_api::Service::RenderingControl),
Some(&1)
);
}
#[tokio::test]
async fn test_concurrent_access() {
let registry = Arc::new(SpeakerServiceRegistry::new(100));
let ip: IpAddr = "192.168.1.100".parse().unwrap();
let service = sonos_api::Service::AVTransport;
let handles: Vec<_> = (0..10)
.map(|_| {
let registry = Arc::clone(®istry);
tokio::spawn(async move { registry.register(ip, service).await })
})
.collect();
let results: Vec<_> = futures::future::join_all(handles)
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()
.unwrap()
.into_iter()
.collect::<Result<Vec<_>, _>>()
.unwrap();
let first_id = results[0];
for result in &results {
assert_eq!(*result, first_id);
}
assert_eq!(registry.count().await, 1);
}
}