use crate::cable::CableInfo;
use crate::power::PowerDeliveryPort;
use crate::summary::DeviceSummary;
use crate::typec::TypeCPort;
use crate::usb::UsbDevice;
use super::reader::Sysfs;
#[derive(Debug, Clone, Default)]
pub struct Snapshot {
pub usb_devices: Vec<UsbDevice>,
pub typec_ports: Vec<TypeCPort>,
pub pd_ports: Vec<PowerDeliveryPort>,
pub summaries: Vec<DeviceSummary>,
}
#[derive(Debug, Default)]
pub struct DeviceManager {
sysfs: Sysfs,
snapshot: Snapshot,
}
impl Default for Sysfs {
fn default() -> Self {
Sysfs::linux()
}
}
impl DeviceManager {
pub fn new() -> Self {
Self::with_sysfs(Sysfs::linux())
}
pub fn with_sysfs(sysfs: Sysfs) -> Self {
DeviceManager {
sysfs,
snapshot: Snapshot::default(),
}
}
pub fn refresh(&mut self) {
let usb_devices = self.sysfs.usb_devices();
let typec_ports = self.sysfs.typec_ports();
let pd_ports = self.sysfs.pd_ports();
let summaries = build_summaries(&usb_devices, &typec_ports, &pd_ports);
self.snapshot = Snapshot {
usb_devices,
typec_ports,
pd_ports,
summaries,
};
}
pub fn snapshot(&self) -> &Snapshot {
&self.snapshot
}
pub fn devices(&self) -> &[DeviceSummary] {
&self.snapshot.summaries
}
pub fn usb_devices(&self) -> &[UsbDevice] {
&self.snapshot.usb_devices
}
pub fn typec_ports(&self) -> &[TypeCPort] {
&self.snapshot.typec_ports
}
pub fn pd_ports(&self) -> &[PowerDeliveryPort] {
&self.snapshot.pd_ports
}
pub fn sysfs(&self) -> &Sysfs {
&self.sysfs
}
}
pub fn build_summaries(
usb: &[UsbDevice],
ports: &[TypeCPort],
pd: &[PowerDeliveryPort],
) -> Vec<DeviceSummary> {
let mut out = Vec::with_capacity(usb.len() + ports.len());
for tc in ports {
let pd_match = pd
.iter()
.find(|p| p.parent_port_number == tc.port_number)
.cloned()
.or_else(|| {
if pd.len() == 1 && ports.len() == 1 {
Some(pd[0].clone())
} else {
None
}
});
let cable = tc.cable.as_ref().map(CableInfo::from_typec_cable);
out.push(DeviceSummary::from_typec_port(tc, pd_match, cable));
}
for d in usb {
if d.is_root_hub {
continue;
}
out.push(DeviceSummary::from_usb_device(d));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::power::PowerDataObject;
use crate::summary::Category;
use crate::typec::TypeCPartner;
#[test]
fn root_hubs_are_excluded() {
let root = UsbDevice {
is_root_hub: true,
bus_port: "usb1".into(),
..Default::default()
};
let child = UsbDevice {
bus_port: "1-1".into(),
product: "thing".into(),
..Default::default()
};
let summaries = build_summaries(&[root, child], &[], &[]);
assert_eq!(summaries.len(), 1);
assert_eq!(summaries[0].headline, "thing");
assert_eq!(summaries[0].category, Category::UsbDevice);
}
#[test]
fn single_port_pd_pairs_with_single_typec() {
let port = TypeCPort {
port_number: 0,
cable: None,
partner: Some(TypeCPartner::default()),
..Default::default()
};
let pd = PowerDeliveryPort {
parent_port_number: -1,
source_capabilities: vec![PowerDataObject {
power_mw: 60_000,
..Default::default()
}],
max_source_power_mw: 60_000,
..Default::default()
};
let summaries = build_summaries(&[], &[port], &[pd]);
assert_eq!(summaries.len(), 1);
assert!(summaries[0].power_delivery.is_some());
}
#[test]
fn multi_port_pd_matches_by_parent_number() {
let p0 = TypeCPort {
port_number: 0,
partner: Some(TypeCPartner::default()),
..Default::default()
};
let p1 = TypeCPort {
port_number: 1,
partner: Some(TypeCPartner::default()),
..Default::default()
};
let pd_for_p1 = PowerDeliveryPort {
parent_port_number: 1,
source_capabilities: vec![PowerDataObject {
power_mw: 100_000,
..Default::default()
}],
max_source_power_mw: 100_000,
..Default::default()
};
let summaries = build_summaries(&[], &[p0, p1], &[pd_for_p1]);
assert!(summaries[0].power_delivery.is_none());
assert!(summaries[1].power_delivery.is_some());
}
#[test]
fn manager_with_missing_sysfs_yields_empty_snapshot() {
let mut mgr = DeviceManager::with_sysfs(Sysfs::with_root("/no/such/whatcable/root"));
mgr.refresh();
let s = mgr.snapshot();
assert!(s.usb_devices.is_empty());
assert!(s.typec_ports.is_empty());
assert!(s.pd_ports.is_empty());
assert!(s.summaries.is_empty());
}
}