mod bay;
mod device;
mod links;
use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::time::Instant;
use crate::event::Event;
use crate::wire::{BayFeatures, BayUid, DeviceUid, LinkFeature};
pub(crate) use bay::Bay;
pub(crate) use device::{Device, HelloInfo};
pub(crate) use links::{BayLink, BayLinks};
#[derive(Debug)]
pub(crate) struct State {
pub(crate) uid: DeviceUid,
pub(crate) devices: HashMap<DeviceUid, Device>,
pub(crate) links: BayLinks,
}
impl State {
pub(crate) fn new(uid: DeviceUid) -> Self {
Self {
uid,
devices: HashMap::new(),
links: BayLinks::default(),
}
}
pub(crate) fn device(&self, uid: DeviceUid) -> Option<&Device> {
self.devices.get(&uid)
}
pub(crate) fn device_mut(&mut self, uid: DeviceUid) -> Option<&mut Device> {
self.devices.get_mut(&uid)
}
pub(crate) fn device_by_serial(&self, serial: &str) -> Option<&Device> {
self.devices.values().find(|d| d.hello.serial == serial)
}
pub(crate) fn bay(&self, uid: BayUid) -> Option<&Bay> {
self.device(uid.device)?.bay(uid.port)
}
pub(crate) fn bay_mut(&mut self, uid: BayUid) -> Option<&mut Bay> {
self.device_mut(uid.device)?.bay_mut(uid.port)
}
pub(crate) fn bay_by_serial_and_name(&self, serial: &str, port_name: &str) -> Option<BayUid> {
Some(self.device_by_serial(serial)?.bay_by_name(port_name)?.uid())
}
pub(crate) fn bay_by_stream_ip(&self, ip: Ipv4Addr, audio: bool) -> Option<BayUid> {
self.devices.values().find_map(|device| {
let input = device.first_input()?;
let source = device.v2ip_source_for(input)?;
let stream = if audio { source.audio } else { source.video };
(stream.ip == ip).then(|| input.uid())
})
}
pub(crate) fn link_key(&self, uid: BayUid) -> Option<BayUid> {
let device = self.device(uid.device)?;
Some(device.link_key(device.bay(uid.port)?))
}
pub(crate) fn linked_bay(&self, uid: BayUid) -> Option<BayUid> {
let link = self.links.get(self.link_key(uid)?)?;
if !link.is_configured() {
return None;
}
self.bay_by_serial_and_name(&link.linked_serial, &link.linked_bay)
}
pub(crate) fn volume_bay(&self, uid: BayUid) -> BayUid {
if self.bay(uid).is_some_and(Bay::has_volume_control) {
return uid;
}
match self.linked_bay(uid) {
Some(far) if self.bay(far).is_some_and(Bay::has_volume_control) => far,
_ => uid,
}
}
pub(crate) fn apply_hello(
&mut self,
uid: DeviceUid,
hello: HelloInfo,
now: Instant,
ev: &mut Vec<Event>,
) {
self.devices
.entry(uid)
.or_insert_with(|| Device::new(uid, hello.clone(), now))
.apply_hello(hello, now, ev);
}
pub(crate) fn update_link(
&mut self,
origin: BayUid,
linked_serial: String,
linked_bay: String,
features: u32,
ev: &mut Vec<Event>,
) {
let Some(key) = self.link_key(origin) else {
return;
};
let Some((origin_serial, origin_bay_name)) = self.bay_identity(origin) else {
return;
};
let new = BayLink {
linked_serial,
linked_bay,
features,
};
if let Some(old) = self.links.get(key) {
if old.linked_serial == new.linked_serial && old.features == new.features {
return;
}
if old.is_configured() {
let old_serial = old.linked_serial.clone();
let far = self.bay_by_serial_and_name(&old_serial, &old.linked_bay.clone());
ev.push(Event::BayUnlinked {
bay: origin,
linked_serial: old_serial,
bay_name: origin_bay_name.clone(),
});
if let Some(far) = far {
ev.push(Event::BayUnlinked {
bay: far,
linked_serial: origin_serial.clone(),
bay_name: origin_bay_name.clone(),
});
}
}
}
self.links.insert(key, new.clone());
if !new.is_configured() {
return;
}
let far = self.bay_by_serial_and_name(&new.linked_serial, &new.linked_bay);
let features = self.link_features(origin, far);
ev.push(Event::BayLinked {
bay: origin,
linked_serial: new.linked_serial.clone(),
bay_name: origin_bay_name.clone(),
features,
});
if let Some(far) = far {
ev.push(Event::BayLinked {
bay: far,
linked_serial: origin_serial,
bay_name: origin_bay_name,
features,
});
}
}
fn bay_identity(&self, uid: BayUid) -> Option<(String, String)> {
let device = self.device(uid.device)?;
let bay = device.bay(uid.port)?;
Some((device.serial().to_owned(), bay.port_name.clone()))
}
fn link_features(&self, origin: BayUid, far: Option<BayUid>) -> LinkFeature {
let Some(far) = far else {
return LinkFeature::NONE;
};
if !self.link_is_connected(origin, far) {
return LinkFeature::NONE;
}
let (Some(left), Some(right)) = (self.bay(origin), self.bay(far)) else {
return LinkFeature::NONE;
};
let (left, right) = (left.features, right.features);
let mut rv = LinkFeature::NONE;
let pairs = [
(
BayFeatures::HDMI_OUT,
BayFeatures::HDMI_IN,
LinkFeature::VIDEO_HDMI,
),
(
BayFeatures::AUDIO_DIG_OUT,
BayFeatures::AUDIO_DIG_IN,
LinkFeature::AUDIO_OPTICAL,
),
(
BayFeatures::AUDIO_ANA_OUT,
BayFeatures::AUDIO_ANA_IN,
LinkFeature::AUDIO_ANALOG,
),
(BayFeatures::IR_OUT, BayFeatures::IR_IN, LinkFeature::IR),
(BayFeatures::RC_OUT, BayFeatures::RC_IN, LinkFeature::RC),
];
for (out, into, feature) in pairs {
if (left.has(out) && right.has(into)) || (left.has(into) && right.has(out)) {
rv |= feature;
}
}
rv
}
fn link_is_connected(&self, origin: BayUid, far: BayUid) -> bool {
let Some(far_key) = self.link_key(far) else {
return false;
};
let Some(far_link) = self.links.get(far_key) else {
return false;
};
let Some((origin_serial, origin_bay_name)) = self.bay_identity(origin) else {
return false;
};
far_link.is_configured()
&& far_link.linked_serial == origin_serial
&& far_link.linked_bay == origin_bay_name
}
}