use crate::device_info::UsbDeviceInfo;
use colored::*;
use std::fs::OpenOptions;
use std::io::Write;
use tokio::sync::mpsc;
pub struct Logger {
output_json: bool,
log_file: Option<std::fs::File>,
colorful: bool,
}
impl Logger {
pub fn new(
output_json: bool,
log_file_path: Option<&str>,
colorful: bool,
) -> Result<Self, Box<dyn std::error::Error>> {
let log_file = if let Some(path) = log_file_path {
Some(
OpenOptions::new()
.create(true)
.append(true)
.open(path)
.map_err(|e| format!("Failed to open log file '{path}': {e}"))?,
)
} else {
None
};
Ok(Self {
output_json,
log_file,
colorful,
})
}
pub fn log_device_event(
&mut self,
device_info: &UsbDeviceInfo,
) -> Result<(), Box<dyn std::error::Error>> {
if self.output_json {
let json = serde_json::to_string(device_info)?;
println!("{json}");
if let Some(file) = &mut self.log_file {
writeln!(file, "{json}")?;
file.flush()?;
}
} else {
let event_icon = match device_info.event_type {
crate::device_info::DeviceEventType::Connected => "🔌",
crate::device_info::DeviceEventType::Disconnected => "❌",
};
let styled_name = if self.colorful {
match device_info.event_type {
crate::device_info::DeviceEventType::Connected => {
device_info.device_name.green().bold()
}
crate::device_info::DeviceEventType::Disconnected => {
device_info.device_name.red().bold()
}
}
} else {
device_info.device_name.normal()
};
let output = format!(
"{} {} | VID: {} PID: {} | Serial: {} | Event: {:?} | {}",
event_icon,
styled_name,
device_info.vendor_id,
device_info.product_id,
device_info.serial_number.as_deref().unwrap_or("-"),
device_info.event_type,
device_info.timestamp
);
println!("{output}");
if let Some(file) = &mut self.log_file {
writeln!(file, "{output}")?;
file.flush()?;
}
}
Ok(())
}
}
pub async fn logger_task(mut rx: mpsc::Receiver<UsbDeviceInfo>, mut logger: Logger) {
while let Some(device_info) = rx.recv().await {
if let Err(e) = logger.log_device_event(&device_info) {
eprintln!("Error logging device event: {e}");
}
}
}