summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-09-19 22:59:22 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-09-20 23:34:15 +0800
commitc7a2788158628d3445f0f3574a0d7adfabd0a755 (patch)
tree0c0e973b70f07cd4797b60bd17ca6d4b8e0f348d
parent6511f6aaf68c3ab3bce10cdd743936b28e45dfa9 (diff)
init
-rw-r--r--Cargo.lock52
-rw-r--r--Cargo.toml6
-rw-r--r--src/client.rs1
-rw-r--r--src/client/connector.rs7
-rw-r--r--src/client/connector/httpconnector.rs4
-rw-r--r--src/client/executor.rs18
-rw-r--r--src/client/main.rs1
-rw-r--r--src/client/todo.md1
-rw-r--r--src/executor.rs19
-rw-r--r--src/lib.rs1
-rw-r--r--src/server/main.rs6
-rw-r--r--src/server/server.rs9
-rw-r--r--src/server/server/tcpserver.rs2
-rw-r--r--src/server/server/tcpserver/server.rs146
-rw-r--r--src/server/server/tcpserver/tasks.rs20
-rw-r--r--src/tasks.rs14
16 files changed, 277 insertions, 30 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 05e92a5..c1dd77b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -51,6 +51,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "bincode"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
+dependencies = [
+ "bincode_derive",
+ "serde",
+ "unty",
+]
+
+[[package]]
+name = "bincode_derive"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
+dependencies = [
+ "virtue",
+]
+
+[[package]]
name = "bitflags"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -811,8 +831,10 @@ dependencies = [
name = "remote-control-rs"
version = "0.1.0"
dependencies = [
+ "bincode",
"gethostname",
"reqwest",
+ "serde",
"signal-hook",
"tokio",
"url",
@@ -979,18 +1001,28 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.219"
+version = "1.0.225"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
dependencies = [
"proc-macro2",
"quote",
@@ -1302,6 +1334,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
+name = "unty"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
+
+[[package]]
name = "url"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1326,6 +1364,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
+name = "virtue"
+version = "0.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 3d1f892..31f3f30 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,13 @@ url = { version = "2" }
signal-hook = "0.3.18"
gethostname = "1.0.2"
whoami = "1.6.1"
+serde = { version = "1.0.225", features = ["derive"] }
+bincode = { version = "2.0.1", features = ["serde"] }
[[bin]]
name = "client"
path = "src/client/main.rs"
+
+[[bin]]
+name = "server"
+path = "src/server/main.rs"
diff --git a/src/client.rs b/src/client.rs
index 2377549..c07b9fa 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,6 +1,7 @@
use std::net::IpAddr;
use whoami::{DesktopEnv, Language};
+#[derive(PartialEq)]
pub struct Client<'a> {
pub ip: Option<IpAddr>,
pub hostname: String,
diff --git a/src/client/connector.rs b/src/client/connector.rs
index d977ace..f57e388 100644
--- a/src/client/connector.rs
+++ b/src/client/connector.rs
@@ -6,11 +6,12 @@ use std::{
result,
};
-use crate::{client::Client, executor::Executor};
-use remote_control_rs::tasks::NETWORK_T;
+use crate::client::Client;
+use remote_control_rs::{executor::Executor, tasks::NETWORK_T};
pub type Result<T: Display, E: Debug> = result::Result<T, E>;
+// TODO: adapter to convert the types to strings so normal http server work too
pub trait Connection {
type Ok: std::fmt::Display + AsRef<str> = String;
type Err: std::fmt::Debug;
@@ -66,7 +67,7 @@ pub trait Connection {
None => return,
};
// FIXME: handle the errors... peak prod code
- self.ntfy(Executor::new(task.try_into().unwrap(), it).unwrap().run().unwrap().clone().unwrap()).unwrap();
+ self.ntfy(Executor::new(task.try_into().unwrap(), it).unwrap().run().as_ref().unwrap().clone()).unwrap();
}
Result::Err(e) => {
#[cfg(debug_assertions)]
diff --git a/src/client/connector/httpconnector.rs b/src/client/connector/httpconnector.rs
index dbd094c..60405f4 100644
--- a/src/client/connector/httpconnector.rs
+++ b/src/client/connector/httpconnector.rs
@@ -1,5 +1,5 @@
-use crate::{connector::Connection, executor::Executor};
-use remote_control_rs::tasks::NETWORK_T;
+use crate::connector::Connection;
+use remote_control_rs::{executor::Executor, tasks::NETWORK_T};
use reqwest::{
Error, StatusCode, Url,
blocking::{Client, RequestBuilder},
diff --git a/src/client/executor.rs b/src/client/executor.rs
deleted file mode 100644
index 26ac0ba..0000000
--- a/src/client/executor.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use remote_control_rs::tasks::{IntoTaskErr, Task};
-
-pub struct Executor<'a, 't> {
- task: &'t Task,
- args: Box<[&'a str]>,
- output: Option<Result<String, String>>,
-}
-
-impl<'a, 't> Executor<'a, 't> {
- pub fn new(task: &'t Task, args: impl IntoIterator<Item = &'a str>) -> Result<Self, IntoTaskErr> {
- Ok(Self { task: task, args: args.into_iter().collect(), output: None })
- }
-
- pub fn run(&mut self) -> Option<&Result<String, String>> {
- self.output = Some((self.task.exec)(&self.args));
- self.output.as_ref()
- }
-}
diff --git a/src/client/main.rs b/src/client/main.rs
index be37341..0669197 100644
--- a/src/client/main.rs
+++ b/src/client/main.rs
@@ -10,7 +10,6 @@ use signal_hook::iterator::Signals;
pub(crate) mod client;
pub(crate) mod connector;
-pub(crate) mod executor;
fn main() {
#[cfg(not(debug_assertions))]
diff --git a/src/client/todo.md b/src/client/todo.md
index 5f316b8..8c099ef 100644
--- a/src/client/todo.md
+++ b/src/client/todo.md
@@ -3,3 +3,4 @@
- [x] collect basic stuff
- [ ] better typed task
- [ ] websocket/SSE(streaming response) http server option
+- [ ] multiple task at server
diff --git a/src/executor.rs b/src/executor.rs
new file mode 100644
index 0000000..9f66ec3
--- /dev/null
+++ b/src/executor.rs
@@ -0,0 +1,19 @@
+use crate::tasks::{CompletedTask, IntoTaskErr, Task, TaskOutput};
+
+pub struct Executor<'a, 't> {
+ task: &'t Task,
+ args: Box<[&'a str]>,
+ complete_task: Option<CompletedTask>,
+}
+
+impl<'a, 't> Executor<'a, 't> {
+ pub fn new(task: &'t Task, args: impl IntoIterator<Item = &'a str>) -> Result<Self, IntoTaskErr> {
+ Ok(Self { task: task, args: args.into_iter().collect(), complete_task: None })
+ }
+
+ pub fn run(&mut self) -> &TaskOutput {
+ let output = (self.task.exec)(&self.args);
+ self.complete_task = Some(CompletedTask { name: self.task.name, output: Some(output) });
+ self.complete_task.as_ref().unwrap().output.as_ref().unwrap()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index c97d0c4..52cda7b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
#![allow(clippy::redundant_field_names)]
pub mod client;
+pub mod executor;
pub mod tasks;
diff --git a/src/server/main.rs b/src/server/main.rs
new file mode 100644
index 0000000..a8bbad8
--- /dev/null
+++ b/src/server/main.rs
@@ -0,0 +1,6 @@
+pub(crate) mod server;
+
+#[tokio::main]
+async fn main() {
+ todo!();
+}
diff --git a/src/server/server.rs b/src/server/server.rs
new file mode 100644
index 0000000..fcc6e7f
--- /dev/null
+++ b/src/server/server.rs
@@ -0,0 +1,9 @@
+use remote_control_rs::tasks::Task;
+
+pub mod tcpserver;
+
+pub trait Server<'a> {
+ fn update(&mut self, task: Task);
+
+ async fn listen(&mut self) -> !;
+}
diff --git a/src/server/server/tcpserver.rs b/src/server/server/tcpserver.rs
new file mode 100644
index 0000000..1af118c
--- /dev/null
+++ b/src/server/server/tcpserver.rs
@@ -0,0 +1,2 @@
+pub mod server;
+pub mod tasks;
diff --git a/src/server/server/tcpserver/server.rs b/src/server/server/tcpserver/server.rs
new file mode 100644
index 0000000..551aea1
--- /dev/null
+++ b/src/server/server/tcpserver/server.rs
@@ -0,0 +1,146 @@
+use std::{
+ cell::RefCell,
+ net::SocketAddr,
+ sync::{Arc, Weak},
+};
+
+use remote_control_rs::{client, tasks::Task};
+use tokio::{
+ io::{self, AsyncReadExt},
+ net::{TcpListener, TcpStream},
+};
+
+use crate::server::{
+ Server,
+ tcpserver::tasks::{Request, RequestType},
+};
+
+struct TCPServer<'a> {
+ this: RefCell<Weak<Self>>,
+ task: Option<Task>,
+ listener: TcpListener,
+ clients: RefCell<Vec<Client<'a>>>,
+}
+
+impl<'a> TCPServer<'a> {
+ fn new(listener: TcpListener) -> Arc<Self> {
+ let s = Arc::new(Self { this: RefCell::new(Weak::new()), listener, task: None, clients: RefCell::new(Vec::new()) });
+ *s.this.borrow_mut() = Arc::downgrade(&s);
+ s
+ }
+
+ fn remove(&self, client: &Client<'a>) {
+ self.clients.borrow_mut().retain(|c| c != client);
+ }
+}
+
+impl<'a> Server<'a> for TCPServer<'a> {
+ fn update(&mut self, task: Task) {
+ self.task = Some(task);
+ }
+
+ async fn listen(&mut self) -> ! {
+ loop {
+ match self.listener.accept().await {
+ Ok((socket, addr)) => self.clients.borrow_mut().push(Client::new(self.this.borrow().upgrade().unwrap(), socket, addr)), // the unwrap shouldn't panics, self has to be alive
+ Err(e) => eprintln!("Failed to accept connection: {e}"),
+ };
+ }
+ }
+}
+
+struct Client<'a> {
+ server: Arc<TCPServer<'a>>,
+ stream: TcpStream,
+ addr: SocketAddr,
+ data: Option<client::Client<'a>>,
+}
+
+impl<'a> Client<'a> {
+ fn new(server: Arc<TCPServer<'a>>, stream: TcpStream, addr: SocketAddr) -> Self {
+ Self { server, stream, addr, data: None }
+ }
+
+ fn handle_read(&mut self, buf: io::Result<usize>) -> bool {
+ match buf {
+ Ok(0) => self.server.remove(self),
+ Ok(_) => {}
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return false,
+ Err(e) => {
+ eprintln!("Failed to read from client: {e}");
+ return false;
+ }
+ }
+ true
+ }
+
+ // TODO: use framed
+ async fn read(&mut self) -> Option<(Vec<u8>, usize)> {
+ const usize: usize = std::mem::size_of::<usize>();
+ let mut len = vec![0; usize]; // vec! only take usize
+ if !self.handle_read(self.stream.try_read(&mut len)) {
+ false;
+ }
+ let len = usize::from_be_bytes(match len.try_into() {
+ Ok(n) => n,
+ Err(e) => {
+ eprintln!("Failed to convert length bytes to length usize: {:?}", e);
+ return None;
+ }
+ }); // TODO: move to a shared fn with client to ensure we use same endian
+ let mut buf = vec![0; len];
+ let read = self.stream.read_exact(&mut buf).await;
+ if !self.handle_read(read) {
+ return None;
+ }
+ Some((buf, len))
+ }
+
+ async fn poll(&mut self) -> ! {
+ loop {
+ if let Err(e) = self.stream.readable().await {
+ eprintln!("Failed waiting for client to be readable: {e}");
+ continue;
+ }
+ if let Some((buf, len)) = self.read().await {
+ let req: RequestType = match bincode::serde::decode_from_slice(buf.as_slice(), bincode::config::standard()) {
+ Ok((decoded, size)) => {
+ if size != len {
+ eprintln!("Decoded size {size} does not match expected size {len}, decode succeeded anyways?");
+ }
+ decoded
+ }
+ Err(e) => {
+ eprintln!("Failed to decode request from client {}: {e}", self.addr);
+ continue;
+ }
+ };
+ match req {
+ // TODO: seperate send and respond
+ RequestType::Send(req) => match req {
+ Request::Get(req) => {
+ todo!()
+ }
+ Request::Post(req) => {
+ todo!()
+ }
+ },
+ RequestType::Respond(res) => match res {
+ Request::Get(req) => {
+ todo!()
+ }
+ Request::Post(req) => {
+ todo!()
+ }
+ },
+ }
+ }
+ }
+ }
+}
+
+impl<'a> PartialEq for Client<'a> {
+ fn eq(&self, other: &Self) -> bool {
+ self.data == other.data
+ }
+}
diff --git a/src/server/server/tcpserver/tasks.rs b/src/server/server/tcpserver/tasks.rs
new file mode 100644
index 0000000..23e6f9e
--- /dev/null
+++ b/src/server/server/tcpserver/tasks.rs
@@ -0,0 +1,20 @@
+use remote_control_rs::tasks::{CompletedTask, Name};
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub enum RequestType {
+ Send(Request),
+ Respond(Request),
+}
+
+#[derive(Deserialize)]
+pub enum Request {
+ Get(TaskType),
+ Post(TaskType),
+}
+
+#[derive(Deserialize)]
+pub enum TaskType {
+ Completed(CompletedTask),
+ Pending(Name),
+}
diff --git a/src/tasks.rs b/src/tasks.rs
index 0e196b2..b979d4f 100644
--- a/src/tasks.rs
+++ b/src/tasks.rs
@@ -1,11 +1,21 @@
use std::fmt::Display;
+use serde::Deserialize;
+
+pub type TaskOutput = Result<String, String>;
+
pub struct Task {
pub name: Name,
- pub exec: fn(&[&str]) -> Result<String, String>,
+ pub exec: fn(&[&str]) -> TaskOutput,
}
-#[derive(Debug)]
+#[derive(Deserialize)]
+pub struct CompletedTask {
+ pub name: Name,
+ pub output: Option<TaskOutput>,
+}
+
+#[derive(Debug, Deserialize, Clone, Copy)]
pub enum Name {
Nop,
Exec,