diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 64ff1dd..a580744 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -92,6 +92,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "async-recursion" version = "1.0.5" @@ -280,6 +328,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "cmake" version = "0.1.50" @@ -289,6 +377,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "community_settings_core" version = "0.1.0" @@ -624,6 +718,12 @@ dependencies = [ "http", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -1072,6 +1172,7 @@ name = "powertools" version = "1.5.0-ng1" dependencies = [ "async-trait", + "clap", "community_settings_core", "libc", "libryzenadj", @@ -1364,6 +1465,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -1691,6 +1798,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "version_check" version = "0.9.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8a8e2b0..302a2f9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -37,6 +37,8 @@ libryzenadj = { version = "0.13" } # ureq's tls feature does not like musl targets ureq = { version = "2", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true } +clap = { version = "4.4", features = [ "derive" ] } + [features] default = ["online", "decky"] decky = ["usdpl-back/decky"] diff --git a/backend/src/cli/args.rs b/backend/src/cli/args.rs new file mode 100644 index 0000000..9edebad --- /dev/null +++ b/backend/src/cli/args.rs @@ -0,0 +1,42 @@ +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// TCP port for front-end client connection + #[arg(long)] + pub port: Option, + + /// Override log file location + #[arg(long)] + pub log: Option, + + /// Force verbose logging + #[arg(short, long)] + pub verbose: bool, + + /// Specail operation to perform + #[command(subcommand)] + pub op: Option, +} + +impl Args { + pub fn load() -> Self { + Self::parse() + } + + pub fn is_default(&self) -> bool { + self.port.is_none() + && self.log.is_none() + && !self.verbose + && self.op.is_none() + } +} + +#[derive(Subcommand, Debug)] +pub enum Operation { + /// Dump useful system information for adding new device support + DumpSys, + /// Remove all files created by PowerTools, not including $HOME/homebrew/plugins/PowerTools/ + Clean, +} diff --git a/backend/src/cli/clean.rs b/backend/src/cli/clean.rs new file mode 100644 index 0000000..7afe215 --- /dev/null +++ b/backend/src/cli/clean.rs @@ -0,0 +1,21 @@ +pub fn clean_up() -> Result<(), ()> { + let dirs = vec![ + crate::utility::settings_dir_old(), + crate::utility::settings_dir(), + ]; + + if let Err(e) = clean_up_io(dirs.iter()) { + log::error!("Error removing directories: {}", e); + Err(()) + } else { + Ok(()) + } +} + +fn clean_up_io(directories: impl Iterator>) -> std::io::Result<()> { + let results = directories.map(|dir| std::fs::remove_dir_all(dir)); + for res in results { + res?; + } + Ok(()) +} diff --git a/backend/src/cli/dump_sys.rs b/backend/src/cli/dump_sys.rs new file mode 100644 index 0000000..03e4c0c --- /dev/null +++ b/backend/src/cli/dump_sys.rs @@ -0,0 +1,66 @@ +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::thread::{self, JoinHandle}; +use std::sync::mpsc::{channel, Sender}; +use std::io::Write; + +pub fn dump_sys_info() -> Result<(), ()> { + let (tx, rx) = channel(); + let mut join_handles = Vec::new(); + let useful_files = vec![ + PathBuf::from("/proc/ioports"), + PathBuf::from("/proc/cpuinfo"), + PathBuf::from("/etc/os-release"), + ]; + for file in useful_files { + join_handles.push(read_file(file, tx.clone())); + } + + let useful_commands = vec![ + "dmidecode", + ]; + for cmd in useful_commands.into_iter() { + join_handles.push(execute_command(cmd, tx.clone())); + } + + for join_handle in join_handles.into_iter() { + if let Err(e) = join_handle.join() { + log::error!("Thread failed to complete: {:?}", e); + } + } + + let mut dump_file = std::fs::File::create("powertools_sys_dump.txt").expect("Failed to create dump file"); + for response in rx.into_iter() { + dump_file.write( + &format!("{} v{} ###### {} ######\n{}\n", + crate::consts::PACKAGE_NAME, + crate::consts::PACKAGE_VERSION, + response.0, + response.1.unwrap_or("[None]".to_owned()) + ).into_bytes() + ).expect("Failed to write to dump file"); + } + Ok(()) +} + +fn read_file(file: impl AsRef + Send + 'static, tx: Sender<(String, Option)>) -> JoinHandle<()> { + thread::spawn(move || { + let file = file.as_ref(); + tx.send( + (file.display().to_string(), + std::fs::read_to_string(file).ok()) + ).expect("Failed to send file contents"); + }) +} + +fn execute_command(command: &'static str, tx: Sender<(String, Option)>) -> JoinHandle<()> { + thread::spawn(move || { + tx.send( + (command.to_owned(), Command::new(command) + .output() + .map(|out| String::from_utf8_lossy(&out.stdout).into_owned()) + .ok() + )).expect("Failed to send command output"); + } + ) +} diff --git a/backend/src/cli/mod.rs b/backend/src/cli/mod.rs new file mode 100644 index 0000000..720332d --- /dev/null +++ b/backend/src/cli/mod.rs @@ -0,0 +1,12 @@ +mod args; +mod clean; +mod dump_sys; + +pub use args::{Args, Operation}; + +pub fn do_op(op: &Operation) -> Result<(), ()> { + match op { + Operation::DumpSys => dump_sys::dump_sys_info(), + Operation::Clean => clean::clean_up(), + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 0a245ca..f9116c7 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod api; +mod cli; mod persist; mod settings; mod state; @@ -19,13 +20,18 @@ use usdpl_back::core::serdes::Primitive; use usdpl_back::Instance; fn main() -> Result<(), ()> { + let args = cli::Args::load(); #[cfg(debug_assertions)] let log_filepath = usdpl_back::api::dirs::home() .unwrap_or_else(|| "/tmp/".into()) .join(PACKAGE_NAME.to_owned() + ".log"); #[cfg(not(debug_assertions))] let log_filepath = std::path::Path::new("/tmp").join(format!("{}.log", PACKAGE_NAME)); + let log_filepath = args.log.clone().unwrap_or(log_filepath); println!("Logging to: {:?}", log_filepath); + if !args.is_default() { + println!("CLI arguments, as parsed: {:?}", &args); + } #[cfg(debug_assertions)] let old_log_filepath = usdpl_back::api::dirs::home() .unwrap_or_else(|| "/tmp/".into()) @@ -44,7 +50,7 @@ fn main() -> Result<(), ()> { }, #[cfg(not(debug_assertions))] { - LevelFilter::Info + if args.verbose { LevelFilter::Debug } else { LevelFilter::Info } }, Default::default(), std::fs::File::create(&log_filepath).expect("Failed to create log file"), @@ -52,6 +58,15 @@ fn main() -> Result<(), ()> { ) .unwrap(); log::debug!("Logging to: {:?}.", log_filepath); + log::info!("CLI arguments, as parsed: {:?}", &args); + + // sepcial operation start-up + if let Some(op) = &args.op { + return cli::do_op(op); + // do not continue with regular startup + } + + // regular start-up log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); log::info!( @@ -125,7 +140,7 @@ fn main() -> Result<(), ()> { let (message_getter, message_dismisser) = api::message::MessageHandler::new().to_callables(); - let instance = Instance::new(PORT) + let instance = Instance::new(args.port.unwrap_or(PORT)) .register("V_INFO", |_: Vec| { #[cfg(debug_assertions)] {