summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-09-14 17:58:59 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-09-14 17:58:59 +0800
commit46cb6bcbe8ed6254aa06ff155828666d14ef7bed (patch)
treed5254d653c632f83d74d8ea8688ac2fc3155c0b2 /src/client
parent35466de9f0ccabf88e1dfaa97fe39e5e74614f52 (diff)
feat: network control
Diffstat (limited to 'src/client')
-rw-r--r--src/client/connector.rs13
-rw-r--r--src/client/connector/connectors/httpconnector.rs9
-rw-r--r--src/client/executor/tasks.rs108
3 files changed, 128 insertions, 2 deletions
diff --git a/src/client/connector.rs b/src/client/connector.rs
index 6b59f60..3396349 100644
--- a/src/client/connector.rs
+++ b/src/client/connector.rs
@@ -5,6 +5,7 @@ use std::{thread::sleep, time::Duration};
use crate::executor::{Executor, tasks::IntoTask};
+#[derive(Debug)]
pub(crate) enum Result<T: std::fmt::Display + IntoTask, E: std::fmt::Debug> {
Ok(T),
Err(E),
@@ -17,16 +18,26 @@ pub(crate) trait Connection {
// should use channel to notify update
fn update(&self) -> Result<Self::Ok, Self::Err>; // None if no update(content is same)
+ // TODO: queue failed ntfy
fn ntfy(&self, data: String) -> Result<Self::Ok, Self::Err>;
fn is_prev(&mut self, str: &Self::Ok) -> bool;
+ fn force_network(&mut self);
+
+ fn reinit(&mut self) {
+ sleep(Duration::from_millis(500));
+ _ = Executor::new("network", ["on".to_string()]);
+ self.force_network();
+ }
+
fn r#loop(&mut self) -> ! {
loop {
// tradeoff: if I put this at the ened of the loop, then the continue would just skip it, and continue doing update() until with no waiting, this wastes lot of cpu/IO
// however, of course we can add a manual sleep before it continue and put this in the end, but that's code duplication
// the cons of the current approch is that the first time we've entered, it would start sleeping
- sleep(Duration::from_millis(500));
+ self.reinit();
+
match self.update() {
Result::Ok(s) => {
if self.is_prev(&s) {
diff --git a/src/client/connector/connectors/httpconnector.rs b/src/client/connector/connectors/httpconnector.rs
index fb97cc4..2d58d1d 100644
--- a/src/client/connector/connectors/httpconnector.rs
+++ b/src/client/connector/connectors/httpconnector.rs
@@ -3,10 +3,11 @@ use reqwest::{
blocking::{Client, Response},
};
-use crate::connector::*;
+use crate::{connector::*, executor::tasks::TASKS};
use std::{thread::sleep, time::Duration};
pub(crate) struct HTTPConnector {
+ network_updater: Executor,
pull_url: Url,
ntfy_url: Url,
client: Client,
@@ -15,7 +16,9 @@ pub(crate) struct HTTPConnector {
impl HTTPConnector {
pub fn new(pull_url: &str, ntfy_url: &str) -> std::result::Result<Self, url::ParseError> {
+ assert_eq!(TASKS[2].name, "network");
Ok(Self {
+ network_updater: Executor::new(&TASKS[2], ["on".to_string()]).unwrap(),
prev: String::new(),
pull_url: Url::parse(pull_url)?,
ntfy_url: Url::parse(ntfy_url)?,
@@ -88,4 +91,8 @@ impl Connection for HTTPConnector {
}
false
}
+
+ fn force_network(&mut self) {
+ _ = self.network_updater.run();
+ }
}
diff --git a/src/client/executor/tasks.rs b/src/client/executor/tasks.rs
index 4c8cdee..148e262 100644
--- a/src/client/executor/tasks.rs
+++ b/src/client/executor/tasks.rs
@@ -3,6 +3,12 @@ pub(crate) struct Task {
pub exec: fn(&[String]) -> Result<String, String>,
}
+impl PartialEq for &Task {
+ fn eq(&self, other: &Self) -> bool {
+ self.name == other.name
+ }
+}
+
pub(crate) const TASKS: &[Task] = &[
Task {
name: "nop",
@@ -12,6 +18,10 @@ pub(crate) const TASKS: &[Task] = &[
name: "exec",
exec: exec,
},
+ Task {
+ name: "network",
+ exec: network,
+ },
];
fn no_op(_: &[String]) -> Result<String, String> {
@@ -37,6 +47,93 @@ fn exec(cmds: &[String]) -> Result<String, String> {
}
}
+fn network(switch: &[String]) -> Result<String, String> {
+ use std::process::{Command, Stdio};
+ let switch: bool = match switch[0].parse() {
+ Ok(s) => s,
+ Err(e) => return Err(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();
+ match e.read_to_string(&mut reason) {
+ Ok(_) => {}
+ Err(e) => 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();
+ match o.read_to_string(&mut output) {
+ Ok(_) => (),
+ Err(e) => 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();
+ match e.read_to_string(&mut reason) {
+ Ok(_) => {}
+ Err(e) => 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(o.stdout)
+ .unwrap_or("Failed decoding vec<u8> -> String".to_string())
+ .to_string())
+ } else {
+ Err(String::from_utf8(o.stderr)
+ .unwrap_or("Failed decoding vec<u8> -> String".to_string())
+ .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())
+}
+
#[derive(Debug)]
pub(crate) enum IntoTaskErr {
NotFound(String),
@@ -67,3 +164,14 @@ impl IntoTask for &str {
Err(IntoTaskErr::NotFound(self.to_string()))
}
}
+
+impl IntoTask for &Task {
+ fn into_task<'a>(self) -> Result<&'a Task, IntoTaskErr> {
+ for task in TASKS {
+ if task == self {
+ return Ok(task);
+ }
+ }
+ Err(IntoTaskErr::NotFound(self.name.to_string()))
+ }
+}