use crate::error::{Error, Result};
use futures::prelude::*;
use http::Uri;
use log::{debug, info, warn};
use rupnp::ssdp::{SearchTarget, URN};
use std::time::Duration;
const AV_TRANSPORT: URN = URN::service("schemas-upnp-org", "AVTransport", 1);
macro_rules! format_device {
($device:expr) => {{
format!(
"[{}] {} @ {}",
$device.device_type(),
$device.friendly_name(),
$device.url()
)
}};
}
#[derive(Debug, Clone)]
pub struct Render {
pub device: rupnp::Device,
pub service: rupnp::Service,
}
#[derive(Debug, Clone)]
pub enum RenderSpec {
Location(String),
Query(u64, String),
First(u64),
}
impl Render {
pub async fn new(render_spec: RenderSpec) -> Result<Self> {
match &render_spec {
RenderSpec::Location(device_url) => {
info!("Render specified by location: {}", device_url);
Self::select_by_url(device_url)
.await?
.ok_or(Error::DevicesRenderNotFound(render_spec))
}
RenderSpec::Query(timeout, device_query) => {
info!("Render specified by query: {}", device_query);
Self::select_by_query(*timeout, device_query)
.await?
.ok_or(Error::DevicesRenderNotFound(render_spec))
}
RenderSpec::First(timeout) => {
info!("No render specified, selecting first one");
Ok(Self::discover(*timeout)
.await?
.first()
.ok_or(Error::DevicesRenderNotFound(render_spec))?
.to_owned())
}
}
}
pub async fn discover(duration_secs: u64) -> Result<Vec<Self>> {
info!(
"Discovering devices in the network, waiting {} seconds...",
duration_secs
);
let search_target = SearchTarget::URN(AV_TRANSPORT);
let devices = rupnp::discover(&search_target, Duration::from_secs(duration_secs))
.await
.map_err(Error::DevicesDiscoverFail)?;
pin_utils::pin_mut!(devices);
let mut renders = Vec::new();
while let Some(device) = devices
.try_next()
.await
.map_err(Error::DevicesNextDeviceError)?
{
debug!("Found device: {}", format_device!(device));
match Self::from_device(device).await {
Some(render) => {
renders.push(render);
}
None => {}
};
}
Ok(renders)
}
pub fn host(&self) -> String {
self.device.url().authority().unwrap().host().to_string()
}
async fn select_by_url(url: &String) -> Result<Option<Self>> {
debug!("Selecting device by url: {}", url);
let uri: Uri = url
.parse()
.map_err(|_| Error::DevicesUrlParseError(url.to_owned()))?;
let device = rupnp::Device::from_url(uri)
.await
.map_err(|err| Error::DevicesCreateError(url.to_owned(), err))?;
Ok(Self::from_device(device).await)
}
async fn select_by_query(duration_secs: u64, query: &String) -> Result<Option<Self>> {
debug!("Selecting device by query: '{}'", query);
for render in Self::discover(duration_secs).await? {
let render_str = render.to_string();
if render_str.contains(query.as_str()) {
return Ok(Some(render));
}
}
Ok(None)
}
async fn from_device(device: rupnp::Device) -> Option<Self> {
debug!(
"Retrieving AVTransport service from device '{}'",
format_device!(device)
);
match device.find_service(&AV_TRANSPORT) {
Some(service) => Some(Self {
device: device.clone(),
service: service.clone(),
}),
None => {
warn!("No AVTransport service found on {}", device.friendly_name());
None
}
}
}
}
impl std::fmt::Display for Render {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"[{}][{}] {} @ {}",
self.device.device_type(),
self.service.service_type(),
self.device.friendly_name(),
self.device.url()
)
}
}