use std::any::Any;
#[cfg(feature = "testutils")]
use std::net::SocketAddr;
use std::time::Duration;
use crate::backend::{BackendKind, IdnBackend, Result};
use crate::device::DacType;
use crate::discovery::{downcast_connect_data, DiscoveredDevice, DiscoveredDeviceInfo, Discoverer};
use crate::protocols::idn::dac::{ServerInfo, ServiceInfo};
use crate::protocols::idn::scan_for_servers;
#[cfg(feature = "testutils")]
use crate::protocols::idn::ServerScanner;
const PREFIX: &str = "idn";
struct ConnectData {
server: ServerInfo,
service: ServiceInfo,
}
pub struct IdnDiscoverer {
scan_timeout: Duration,
#[cfg(feature = "testutils")]
scan_addresses: Vec<SocketAddr>,
}
impl IdnDiscoverer {
pub fn new() -> Self {
Self {
scan_timeout: Duration::from_millis(500),
#[cfg(feature = "testutils")]
scan_addresses: Vec::new(),
}
}
#[cfg(feature = "testutils")]
pub fn with_scan_addresses(addresses: Vec<SocketAddr>) -> Self {
Self {
scan_addresses: addresses,
..Self::new()
}
}
#[cfg(feature = "testutils")]
pub fn scan_address(&mut self, addr: SocketAddr) -> Vec<DiscoveredDevice> {
let Ok(mut scanner) = ServerScanner::new(0) else {
return Vec::new();
};
let Ok(servers) = scanner.scan_address(addr, self.scan_timeout) else {
return Vec::new();
};
servers_to_devices(servers)
}
}
impl Default for IdnDiscoverer {
fn default() -> Self {
Self::new()
}
}
fn format_stable_id(hostname: &str) -> String {
format!("{}:{}", PREFIX, hostname)
}
fn servers_to_devices(servers: Vec<ServerInfo>) -> Vec<DiscoveredDevice> {
servers
.into_iter()
.filter_map(|server| {
let service = server.find_laser_projector().cloned()?;
let ip_address = server.addresses.first().map(|addr| addr.ip());
let hostname = server.hostname.clone();
let stable_id = format_stable_id(&hostname);
let name = ip_address
.map(|ip| ip.to_string())
.unwrap_or_else(|| hostname.clone());
let mut info =
DiscoveredDeviceInfo::new(DacType::Idn, stable_id, name).with_hostname(hostname);
if let Some(ip) = ip_address {
info = info.with_ip(ip);
}
Some(DiscoveredDevice::new(
info,
Box::new(ConnectData { server, service }),
))
})
.collect()
}
impl Discoverer for IdnDiscoverer {
fn dac_type(&self) -> DacType {
DacType::Idn
}
fn prefix(&self) -> &str {
PREFIX
}
fn scan(&mut self) -> Vec<DiscoveredDevice> {
#[cfg(feature = "testutils")]
if !self.scan_addresses.is_empty() {
let Ok(mut scanner) = ServerScanner::new(0) else {
return Vec::new();
};
let mut out = Vec::new();
for addr in &self.scan_addresses {
let Ok(servers) = scanner.scan_address(*addr, self.scan_timeout) else {
continue;
};
out.extend(servers_to_devices(servers));
}
return out;
}
let Ok(servers) = scan_for_servers(self.scan_timeout) else {
return Vec::new();
};
servers_to_devices(servers)
}
fn connect(&mut self, opaque: Box<dyn Any + Send>) -> Result<BackendKind> {
let data = downcast_connect_data::<ConnectData>(opaque, "IDN")?;
Ok(BackendKind::Fifo(Box::new(IdnBackend::new(
data.server,
data.service,
))))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_stable_id_uses_hostname() {
assert_eq!(
format_stable_id("laser-projector.local"),
"idn:laser-projector.local"
);
assert_eq!(format_stable_id("idn-server-7"), "idn:idn-server-7");
}
}