use std::fmt::Display; use crate::tasks::{IntoTaskErr, Task as BaseTask}; use serde::Deserialize; #[derive(Clone, Copy)] pub struct Task(pub BaseTask); #[derive(Debug, Deserialize, Clone, Copy, PartialEq)] pub enum Name { Nop, Exec, Network, } impl Display for Name { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:?}", self) } } pub const TASKS: &[Task] = &[ Task(BaseTask { name: Name::Nop, exec: no_op, }), Task(BaseTask { name: Name::Exec, exec: exec, }), NETWORK_T, ]; pub const NETWORK_T: Task = Task(BaseTask { name: Name::Network, exec: network, }); fn no_op(_: &[&str], _: &()) -> Result { Ok("".to_string()) } fn exec(cmds: &[&str], _: &()) -> Result { use std::process::Command; // TODO: fmt and add exit code, and add support for both stdout and stderr, and // support for no output match Command::new(cmds[0]) .args(&cmds[1..]) .output() { Ok(o) => { if o.status .success() { Ok(String::from_utf8_lossy(&o.stdout).to_string()) } else { Err(String::from_utf8_lossy(&o.stderr).to_string()) } } Err(e) => Err(e.to_string()), } } fn network(switch: &[&str], _: &()) -> Result { use std::process::{Command, Stdio}; let switch: bool = switch[0] .parse::() .map_err(|e| e.to_string())?; // TODO: windows and linux // todo let .. = Command::new('networksetup') #[cfg(target_os = "macos")] { use std::io::Read; match Command::new("networksetup") .arg("-listallhardwareports") .stdout(Stdio::piped()) .spawn() { Ok(devs) => { match Command::new("awk") .arg(r#"/Wi-Fi|AirPort/{getline; print $2}"#) .stdin(Stdio::from(match devs.stdout { Some(s) => s, None => match devs.stderr { Some(mut e) => { let mut reason = String::new(); if let Err(e) = e.read_to_string(&mut reason) { return Err(format!("networksetup failed to list hardware: {e}")); } return Err(format!("networksetup failed to list hardware: {reason}")); } None => Err("networksetup failed to list hardware".to_string())?, }, })) .stdout(Stdio::piped()) .spawn() { Ok(dev) => match Command::new("networksetup") .arg("-setairportpower") .arg(match dev.stdout { Some(mut o) => { let mut output = String::new(); if let Err(e) = o.read_to_string(&mut output) { return Err(format!("awk failed to get device from networksetup list hardware: {e}")); }; output } None => match dev.stderr { Some(mut e) => { let mut reason = String::new(); if let Err(e) = e.read_to_string(&mut reason) { return Err(format!("awk failed to get device from networksetup list hardware: {e}")); } return Err(format!("awk failed to get device from networksetup list hardware: {reason}")); } None => Err("awk failed to get device from networksetup list hardware".to_string())?, }, }) .arg(if switch { "on" } else { "off" }) .output() { Ok(o) => { if o.status .success() { Ok(String::from_utf8_lossy(&o.stdout).to_string()) } else { Err(String::from_utf8_lossy(&o.stderr).to_string()) } } Err(e) => Err(e.to_string()), }, Err(e) => Err(e.to_string()), } } Err(e) => Err(e.to_string()), } } #[cfg(not(target_os = "macos"))] Err("not implemented".to_string()) } impl TryFrom<&str> for &'static Task { type Error = IntoTaskErr; fn try_from(value: &str) -> Result { for task in TASKS { if task .0 .name .to_string() == value { return Ok(task); } } Err(IntoTaskErr::NotFound(value.to_string())) } } // ts uses Copy trait, try avoid that impl TryFrom<&str> for Task { type Error = IntoTaskErr; fn try_from(value: &str) -> Result { for task in TASKS { if task .0 .name .to_string() == value { return Ok(*task); } } Err(IntoTaskErr::NotFound(value.to_string())) } } impl From for BaseTask { fn from(value: Task) -> Self { value.0 } } impl AsRef> for Task { fn as_ref(&self) -> &BaseTask { &self.0 } }