use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default)]
#[allow(dead_code)] pub enum DeviceHandle {
#[cfg(target_os = "linux")]
Linux {
sysfs_path: String,
device_node: Option<String>,
},
#[cfg(target_os = "windows")]
Windows {
instance_id: String,
interface_path: Option<String>,
},
#[cfg(target_os = "macos")]
Macos {
device_id: String,
},
#[default]
Unknown,
}
#[allow(dead_code)] pub trait AsDeviceHandle {
fn as_device_handle(&self) -> &DeviceHandle;
fn has_device_handle(&self) -> bool {
!matches!(self.as_device_handle(), DeviceHandle::Unknown)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsbDeviceInfo {
pub device_name: String,
pub vendor_id: String,
pub product_id: String,
pub serial_number: Option<String>,
pub timestamp: DateTime<Utc>,
pub event_type: DeviceEventType,
#[serde(skip)]
pub device_handle: DeviceHandle,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DeviceEventType {
Connected,
Disconnected,
}
impl UsbDeviceInfo {
#[allow(dead_code)] pub fn new(
device_name: String,
vendor_id: String,
product_id: String,
serial_number: Option<String>,
event_type: DeviceEventType,
) -> Self {
Self {
device_name,
vendor_id,
product_id,
serial_number,
timestamp: Utc::now(),
event_type,
device_handle: DeviceHandle::Unknown,
}
}
#[allow(dead_code)]
pub fn with_handle(
device_name: String,
vendor_id: String,
product_id: String,
serial_number: Option<String>,
event_type: DeviceEventType,
device_handle: DeviceHandle,
) -> Self {
Self {
device_name,
vendor_id,
product_id,
serial_number,
timestamp: Utc::now(),
event_type,
device_handle,
}
}
pub fn format_plain(&self) -> String {
let event_str = match self.event_type {
DeviceEventType::Connected => "CONNECTED",
DeviceEventType::Disconnected => "DISCONNECTED",
};
let serial_str = self
.serial_number
.as_ref()
.map(|s| format!(" Serial: {s}"))
.unwrap_or_default();
format!(
"[{}] {} - {} (VID: {}, PID: {}){}",
self.timestamp.format("%Y-%m-%d %H:%M:%S UTC"),
event_str,
self.device_name,
self.vendor_id,
self.product_id,
serial_str
)
}
}
impl AsDeviceHandle for UsbDeviceInfo {
fn as_device_handle(&self) -> &DeviceHandle {
&self.device_handle
}
}
impl std::fmt::Display for UsbDeviceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format_plain())
}
}
impl std::fmt::Display for DeviceEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceEventType::Connected => write!(f, "Connected"),
DeviceEventType::Disconnected => write!(f, "Disconnected"),
}
}
}