diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2025-09-19 22:59:22 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2025-09-20 23:34:15 +0800 |
| commit | c7a2788158628d3445f0f3574a0d7adfabd0a755 (patch) | |
| tree | 0c0e973b70f07cd4797b60bd17ca6d4b8e0f348d | |
| parent | 6511f6aaf68c3ab3bce10cdd743936b28e45dfa9 (diff) | |
init
| -rw-r--r-- | Cargo.lock | 52 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | src/client.rs | 1 | ||||
| -rw-r--r-- | src/client/connector.rs | 7 | ||||
| -rw-r--r-- | src/client/connector/httpconnector.rs | 4 | ||||
| -rw-r--r-- | src/client/executor.rs | 18 | ||||
| -rw-r--r-- | src/client/main.rs | 1 | ||||
| -rw-r--r-- | src/client/todo.md | 1 | ||||
| -rw-r--r-- | src/executor.rs | 19 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/server/main.rs | 6 | ||||
| -rw-r--r-- | src/server/server.rs | 9 | ||||
| -rw-r--r-- | src/server/server/tcpserver.rs | 2 | ||||
| -rw-r--r-- | src/server/server/tcpserver/server.rs | 146 | ||||
| -rw-r--r-- | src/server/server/tcpserver/tasks.rs | 20 | ||||
| -rw-r--r-- | src/tasks.rs | 14 |
16 files changed, 277 insertions, 30 deletions
@@ -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" @@ -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() + } +} @@ -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, |
