mod device_info;
mod logger;
mod watcher;
use clap::{Parser, Subcommand};
use logger::{logger_task, Logger};
use std::env;
use std::fs;
use std::path::Path;
use tokio::sync::mpsc;
use watcher::UsbWatcher;
#[derive(Parser)]
#[command(name = "usbwatch")]
#[command(about = "A cross-platform USB device monitor")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(long_about = format!("USBWatch v{}\nA cross-platform USB device monitoring tool\n\nRepository: {}",
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_REPOSITORY")))]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(long, global = true)]
json: bool,
#[arg(long, value_name = "PATH", global = true)]
logfile: Option<String>,
}
#[derive(Subcommand)]
enum Commands {
Monitor,
Install,
Uninstall,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
match cli.command.unwrap_or(Commands::Monitor) {
Commands::Monitor => run_monitor(cli.json, cli.logfile).await,
Commands::Install => install_binary(),
Commands::Uninstall => uninstall_binary(),
}
}
async fn run_monitor(
json: bool,
logfile: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
println!(
"๐ USB Device Monitor - usbwatch v{}",
env!("CARGO_PKG_VERSION")
);
println!("Press Ctrl+C to stop monitoring...");
let (tx, rx) = mpsc::channel(100);
let logger = Logger::new(json, logfile.as_deref())?;
let logger_handle = tokio::spawn(logger_task(rx, logger));
let watcher = UsbWatcher::new(tx)?;
let watcher_handle = tokio::spawn(async move {
if let Err(e) = watcher.start_monitoring().await {
eprintln!("USB monitoring error: {e}");
}
});
tokio::select! {
_ = tokio::signal::ctrl_c() => {
println!("\n๐ก Shutting down USB monitor...");
}
_ = watcher_handle => {
println!("๐ก USB monitoring stopped");
}
}
logger_handle.abort();
Ok(())
}
fn install_binary() -> Result<(), Box<dyn std::error::Error>> {
let current_exe = env::current_exe()?;
let exe_name = if cfg!(windows) {
"usbwatch.exe"
} else {
"usbwatch"
};
let target_dir = if cfg!(windows) {
let program_files =
env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
Path::new(&program_files).join("usbwatch")
} else {
Path::new("/usr/local/bin").to_path_buf()
};
let target_path = target_dir.join(exe_name);
if cfg!(windows) {
fs::create_dir_all(&target_dir)?;
}
fs::copy(¤t_exe, &target_path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&target_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&target_path, perms)?;
}
println!(
"โ
Successfully installed usbwatch to {}",
target_path.display()
);
if cfg!(windows) {
println!(
"๐ Note: You may need to add {} to your PATH environment variable",
target_dir.display()
);
}
println!("๐ You can now run 'usbwatch' from anywhere!");
Ok(())
}
fn uninstall_binary() -> Result<(), Box<dyn std::error::Error>> {
let exe_name = if cfg!(windows) {
"usbwatch.exe"
} else {
"usbwatch"
};
let target_dir = if cfg!(windows) {
let program_files =
env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
Path::new(&program_files).join("usbwatch")
} else {
Path::new("/usr/local/bin").to_path_buf()
};
let target_path = target_dir.join(exe_name);
if target_path.exists() {
fs::remove_file(&target_path)?;
println!(
"โ
Successfully uninstalled usbwatch from {}",
target_path.display()
);
if cfg!(windows) && target_dir.read_dir()?.next().is_none() {
fs::remove_dir(&target_dir)?;
println!("๐๏ธ Removed empty directory {}", target_dir.display());
}
} else {
println!("โ usbwatch is not installed at {}", target_path.display());
return Ok(());
}
println!("๐งน usbwatch has been uninstalled");
Ok(())
}