support for cmdline args and better error handling (#4)

* support for cmdline args and better error handling

* make config.toml optional

* dont abuse panic for error handling

* update doc

* more panics removed

* more unwraps removed
This commit is contained in:
Ferdinand Schober
2023-02-18 04:03:10 +01:00
committed by GitHub
parent deb1548e21
commit a2d2e904f8
9 changed files with 205 additions and 83 deletions

View File

@@ -1,4 +1,4 @@
use std::net::SocketAddr;
use std::{net::SocketAddr, error::Error, fmt::Display};
use crate::{config, dns};
@@ -35,20 +35,29 @@ pub struct ClientManager {
pub type ClientHandle = u32;
#[derive(Debug)]
struct ClientConfigError;
impl Display for ClientConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "neither ip nor hostname specified")
}
}
impl Error for ClientConfigError {}
impl ClientManager {
fn add_client(&mut self, client: &config::Client, pos: Position) {
fn add_client(&mut self, client: &config::Client, pos: Position) -> Result<(), Box<dyn Error>> {
let ip = match client.ip {
Some(ip) => ip,
None => match &client.host_name {
Some(host_name) => match dns::resolve(host_name) {
Ok(ip) => ip,
Err(e) => panic!("{}", e),
},
None => panic!("neither ip nor hostname specified"),
Some(host_name) => dns::resolve(host_name)?,
None => return Err(Box::new(ClientConfigError{})),
},
};
let addr = SocketAddr::new(ip, client.port.unwrap_or(42069));
self.register_client(addr, pos);
Ok(())
}
fn new_id(&mut self) -> ClientHandle {
@@ -56,7 +65,7 @@ impl ClientManager {
self.next_id
}
pub fn new(config: &config::Config) -> Self {
pub fn new(config: &config::Config) -> Result<Self, Box<dyn Error>> {
let mut client_manager = ClientManager {
next_id: 0,
@@ -64,25 +73,11 @@ impl ClientManager {
};
// add clients from config
for client in vec![
&config.client.left,
&config.client.right,
&config.client.top,
&config.client.bottom,
] {
if let Some(client) = client {
let pos = match client {
client if Some(client) == config.client.left.as_ref() => Position::Left,
client if Some(client) == config.client.right.as_ref() => Position::Right,
client if Some(client) == config.client.top.as_ref() => Position::Top,
client if Some(client) == config.client.bottom.as_ref() => Position::Bottom,
_ => panic!(),
};
client_manager.add_client(client, pos);
}
for (client, pos) in config.clients.iter() {
client_manager.add_client(&client, *pos)?;
}
client_manager
Ok(client_manager)
}
pub fn register_client(&mut self, addr: SocketAddr, pos: Position) {

View File

@@ -1,17 +1,17 @@
use serde_derive::{Deserialize, Serialize};
use core::fmt;
use std::net::IpAddr;
use std::{error::Error, fs};
use std::env;
use toml;
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub client: Clients,
pub port: Option<u16>,
pub backend: Option<String>,
}
use crate::client::Position;
#[derive(Serialize, Deserialize, Debug)]
pub struct Clients {
pub struct ConfigToml {
pub port: Option<u16>,
pub backend: Option<String>,
pub left: Option<Client>,
pub right: Option<Client>,
pub top: Option<Client>,
@@ -25,9 +25,94 @@ pub struct Client {
pub port: Option<u16>,
}
impl Config {
pub fn new(path: &str) -> Result<Config, Box<dyn Error>> {
#[derive(Debug, Clone)]
struct MissingParameter {
arg: &'static str,
}
impl fmt::Display for MissingParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Missing a parameter for argument: {}", self.arg)
}
}
impl Error for MissingParameter {}
impl ConfigToml {
pub fn new(path: &str) -> Result<ConfigToml, Box<dyn Error>> {
let config = fs::read_to_string(path)?;
Ok(toml::from_str::<_>(&config)?)
}
}
fn find_arg(key: &'static str) -> Result<Option<String>, MissingParameter> {
let args: Vec<String> = env::args().collect();
for (i, arg) in args.iter().enumerate() {
if arg != key {
continue;
}
match args.get(i+1) {
None => return Err(MissingParameter { arg: key }),
Some(arg) => return Ok(Some(arg.clone())),
};
}
Ok(None)
}
pub struct Config {
pub backend: Option<String>,
pub port: u16,
pub clients: Vec<(Client, Position)>,
}
impl Config {
pub fn new() -> Result<Self, Box<dyn Error>> {
let config_path = "config.toml";
let config_toml = match ConfigToml::new(config_path) {
Err(e) => {
eprintln!("config.toml: {e}");
eprintln!("Continuing without config file ...");
None
},
Ok(c) => Some(c),
};
let backend = match find_arg("--backend")? {
None => match &config_toml {
Some(c) => c.backend.clone(),
None => None,
},
backend => backend,
};
const DEFAULT_PORT: u16 = 4242;
let port = match find_arg("--port")? {
Some(port) => port.parse::<u16>()?,
None => match &config_toml {
Some(c) => c.port.unwrap_or(DEFAULT_PORT),
None => DEFAULT_PORT,
}
};
let mut clients: Vec<(Client, Position)> = vec![];
if let Some(config_toml) = config_toml {
if let Some(c) = config_toml.right {
clients.push((c, Position::Right))
}
if let Some(c) = config_toml.left {
clients.push((c, Position::Left))
}
if let Some(c) = config_toml.top {
clients.push((c, Position::Top))
}
if let Some(c) = config_toml.bottom {
clients.push((c, Position::Bottom))
}
}
Ok(Config { backend, clients, port })
}
}

View File

@@ -1,4 +1,4 @@
use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver};
use std::{thread::{JoinHandle, self}, sync::mpsc::Receiver, error::Error};
#[cfg(unix)]
use std::env;
@@ -14,11 +14,11 @@ enum Backend {
Libei,
}
pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>, backend: Option<String>) -> JoinHandle<()> {
pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>, backend: Option<String>) -> Result<JoinHandle<()>, Box<dyn Error>> {
#[cfg(windows)]
let _backend = backend;
thread::Builder::new()
Ok(thread::Builder::new()
.name("event consumer".into())
.spawn(move || {
#[cfg(windows)]
@@ -72,6 +72,5 @@ pub fn start(consume_rx: Receiver<(Event, ClientHandle)>, clients: Vec<Client>,
consumer::x11::run(consume_rx, clients);
},
}
})
.unwrap()
})?)
}

View File

@@ -20,13 +20,13 @@ pub struct Server {
}
impl Server {
pub fn new(port: u16) -> Self {
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
pub fn new(port: u16) -> Result<Self, Box<dyn Error>> {
let listen_addr = SocketAddr::new("0.0.0.0".parse()?, port);
let sending = Arc::new(AtomicBool::new(false));
Server {
Ok(Server {
listen_addr,
sending,
}
})
}
pub fn run(
@@ -36,7 +36,7 @@ impl Server {
consume_tx: SyncSender<(Event, ClientHandle)>,
) -> Result<(JoinHandle<()>, JoinHandle<()>), Box<dyn Error>> {
let udp_socket = UdpSocket::bind(self.listen_addr)?;
let rx = udp_socket.try_clone().unwrap();
let rx = udp_socket.try_clone()?;
let tx = udp_socket;
let sending = self.sending.clone();
@@ -92,8 +92,7 @@ impl Server {
.expect("event consumer unavailable");
}
}
})
.unwrap();
})?;
let sending = self.sending.clone();
@@ -124,8 +123,7 @@ impl Server {
}
}
}
})
.unwrap();
})?;
Ok((receiver, sender))
}

View File

@@ -1,4 +1,4 @@
use std::sync::mpsc;
use std::{sync::mpsc, process, env};
use lan_mouse::{
client::ClientManager,
@@ -6,12 +6,24 @@ use lan_mouse::{
config, event, request,
};
fn usage() {
eprintln!("usage: {} [--backend <backend>] [--port <port>]",
env::args().next().unwrap_or("lan-mouse".into()));
}
pub fn main() {
// parse config file
let config = config::Config::new("config.toml").unwrap();
let config = match config::Config::new() {
Err(e) => {
eprintln!("{e}");
usage();
process::exit(1);
}
Ok(config) => config,
};
// port or default
let port = config.port.unwrap_or(42069);
let port = config.port;
// event channel for producing events
let (produce_tx, produce_rx) = mpsc::sync_channel(128);
@@ -20,26 +32,64 @@ pub fn main() {
let (consume_tx, consume_rx) = mpsc::sync_channel(128);
// create client manager
let mut client_manager = ClientManager::new(&config);
let mut client_manager = match ClientManager::new(&config) {
Err(e) => {
eprintln!("{e}");
process::exit(1);
}
Ok(m) => m,
};
// start receiving client connection requests
let (request_server, request_thread) = request::Server::listen(port).unwrap();
// start producing and consuming events
let event_producer = producer::start(produce_tx, client_manager.get_clients(), request_server);
let event_consumer = consumer::start(consume_rx, client_manager.get_clients(), config.backend);
let event_producer = match producer::start(produce_tx, client_manager.get_clients(), request_server) {
Err(e) => {
eprintln!("Could not start event producer: {e}");
None
},
Ok(p) => Some(p),
};
let event_consumer = match consumer::start(consume_rx, client_manager.get_clients(), config.backend) {
Err(e) => {
eprintln!("Could not start event consumer: {e}");
None
},
Ok(p) => Some(p),
};
if event_consumer.is_none() && event_producer.is_none() {
process::exit(1);
}
// start sending and receiving events
let event_server = event::server::Server::new(port);
let (receiver, sender) = event_server
.run(&mut client_manager, produce_rx, consume_tx)
.unwrap();
let event_server = match event::server::Server::new(port) {
Ok(s) => s,
Err(e) => {
eprintln!("{e}");
process::exit(1);
}
};
let (receiver, sender) = match event_server.run(&mut client_manager, produce_rx, consume_tx) {
Ok((r,s)) => (r,s),
Err(e) => {
eprintln!("{e}");
process::exit(1);
}
};
request_thread.join().unwrap();
// stop receiving events and terminate event-consumer
receiver.join().unwrap();
sender.join().unwrap();
if let Some(thread) = event_consumer {
thread.join().unwrap();
}
event_producer.join().unwrap();
event_consumer.join().unwrap();
// stop producing events and terminate event-sender
if let Some(thread) = event_producer {
thread.join().unwrap();
}
sender.join().unwrap();
}

View File

@@ -1,6 +1,6 @@
#[cfg(unix)]
use std::env;
use std::{thread::{JoinHandle, self}, sync::mpsc::SyncSender};
use std::{thread::{JoinHandle, self}, sync::mpsc::SyncSender, error::Error};
use crate::{client::{Client, ClientHandle}, event::Event, request::Server};
@@ -16,8 +16,8 @@ pub fn start(
produce_tx: SyncSender<(Event, ClientHandle)>,
clients: Vec<Client>,
request_server: Server,
) -> JoinHandle<()> {
thread::Builder::new()
) -> Result<JoinHandle<()>, Box<dyn Error>> {
Ok(thread::Builder::new()
.name("event producer".into())
.spawn(move || {
#[cfg(windows)]
@@ -48,6 +48,5 @@ pub fn start(
producer::wayland::run(produce_tx, request_server, clients);
}
}
})
.unwrap()
})?)
}

View File

@@ -60,11 +60,13 @@ impl Server {
pub fn listen(port: u16) -> Result<(Server, JoinHandle<()>), Box<dyn Error>> {
let data: Arc<RwLock<HashMap<Request, MmapMut>>> = Arc::new(RwLock::new(HashMap::new()));
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
let listen_addr = SocketAddr::new("0.0.0.0".parse()?, port);
let server = Server { data };
let server_copy = server.clone();
let thread = thread::spawn(move || {
let listen_socket = TcpListener::bind(listen_addr).unwrap();
let listen_socket = TcpListener::bind(listen_addr)?;
let thread = thread::Builder::new()
.name("tcp server".into())
.spawn(move || {
for stream in listen_socket.incoming() {
match stream {
Ok(stream) => {
@@ -75,7 +77,7 @@ impl Server {
}
}
}
});
})?;
Ok((server_copy, thread))
}