switchy_mdns 0.1.2

Switchy mdns service discovery package
Documentation
use std::net::SocketAddr;

use mdns_sd::{ServiceDaemon, ServiceEvent};
use moosicbox_async_service::{Arc, CancellationToken, JoinError, JoinHandle, tokio::sync::RwLock};
use strum_macros::AsRefStr;
use thiserror::Error;

#[derive(Debug, Clone)]
pub struct MoosicBox {
    pub id: String,
    pub name: String,
    pub host: SocketAddr,
    pub dns: String,
}

#[derive(Debug, Error)]
pub enum Error {
    #[error(transparent)]
    MdnsSd(#[from] mdns_sd::Error),
    #[error(transparent)]
    Send(#[from] kanal::SendError),
    #[error(transparent)]
    Join(#[from] JoinError),
}

#[derive(Debug, AsRefStr)]
pub enum Command {}

impl std::fmt::Display for Command {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_ref())
    }
}

pub struct Context {
    token: CancellationToken,
    handle: Option<JoinHandle<Result<(), Error>>>,
    sender: kanal::AsyncSender<MoosicBox>,
}

impl Context {
    #[must_use]
    pub fn new(sender: kanal::AsyncSender<MoosicBox>) -> Self {
        Self {
            token: CancellationToken::new(),
            handle: None,
            sender,
        }
    }
}

pub mod service {
    moosicbox_async_service::async_service!(super::Command, super::Context, super::Error);
}

#[moosicbox_async_service::async_trait]
impl service::Processor for service::Service {
    type Error = service::Error;

    async fn on_start(&mut self) -> Result<(), Self::Error> {
        let mut ctx = self.ctx.write().await;

        let tx = ctx.sender.clone();
        let token = ctx.token.clone();

        ctx.handle
            .replace(moosicbox_task::spawn("switchy_mdns scanner", async move {
                let mdns = ServiceDaemon::new()?;
                let service_type = "_moosicboxserver._tcp.local.";
                let receiver = mdns.browse(service_type)?;

                log::debug!("mdns scanner: Browsing for {service_type} services...");

                while let Ok(Some(event)) = {
                    moosicbox_async_service::tokio::select! {
                        event = receiver.recv_async() => event.map(Some),
                        () = token.cancelled() => Ok(None)
                    }
                } {
                    if let ServiceEvent::ServiceResolved(info) = event {
                        log::debug!(
                            "mdns scanner: Found server instance: {}",
                            info.get_fullname()
                        );

                        for addr in info.get_addresses().iter().filter(|x| x.is_ipv4()).copied() {
                            let socket_addr = SocketAddr::new(addr, info.get_port());
                            log::debug!("mdns scanner: Server address: {addr}");
                            let dns = info.get_fullname().to_string();

                            let server = MoosicBox {
                                id: dns.split_once('.').expect("Invalid dns").0.to_string(),
                                name: info.get_hostname().to_string(),
                                host: socket_addr,
                                dns,
                            };

                            moosicbox_assert::die_or_propagate!(tx.send(server).await);
                        }
                    }
                }

                Ok::<_, Error>(())
            }));

        drop(ctx);

        Ok(())
    }

    async fn on_shutdown(ctx: Arc<RwLock<Context>>) -> Result<(), Self::Error> {
        let handle = &mut ctx.write().await.handle;

        if let Some(handle) = handle {
            ctx.read().await.token.cancel();
            handle.await??;
        }

        Ok(())
    }

    async fn process_command(
        _ctx: Arc<RwLock<Context>>,
        command: Command,
    ) -> Result<(), Self::Error> {
        log::debug!("mdns scanner: process_command command={command}");
        Ok(())
    }
}