Background service (#43)

better handling of background-service: lan-mouse can now be run without a gui by specifying --daemon as an argument.
Otherwise the servic will be run as a child process and correctly terminate when the window is closed / frontend exits.

Closes #38
This commit is contained in:
Ferdinand Schober
2023-12-09 00:36:01 +01:00
committed by GitHub
parent 9b242f6138
commit 56e5f7a30d
13 changed files with 488 additions and 170 deletions

View File

@@ -1,5 +1,5 @@
use std::io::Result;
use std::str;
use anyhow::{Result, anyhow};
use std::{str, io::ErrorKind, time::Duration, cmp::min};
#[cfg(unix)]
use std::{env, path::{Path, PathBuf}};
@@ -11,6 +11,7 @@ use tokio::io::ReadHalf;
use tokio::net::UnixStream;
#[cfg(unix)]
use tokio::net::UnixListener;
#[cfg(windows)]
use tokio::net::TcpStream;
#[cfg(windows)]
@@ -18,7 +19,7 @@ use tokio::net::TcpListener;
use serde::{Serialize, Deserialize};
use crate::client::{Position, ClientHandle, Client};
use crate::{client::{Position, ClientHandle, Client}, config::{Config, Frontend}};
/// cli frontend
pub mod cli;
@@ -27,6 +28,52 @@ pub mod cli;
#[cfg(all(unix, feature = "gtk"))]
pub mod gtk;
pub fn run_frontend(config: &Config) -> Result<()> {
match config.frontend {
#[cfg(all(unix, feature = "gtk"))]
Frontend::Gtk => { gtk::run(); }
#[cfg(any(not(feature = "gtk"), not(unix)))]
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
Frontend::Cli => { cli::run()?; }
};
Ok(())
}
fn exponential_back_off(duration: &mut Duration) -> &Duration {
let new = duration.saturating_mul(2);
*duration = min(new, Duration::from_secs(1));
duration
}
/// wait for the lan-mouse socket to come online
#[cfg(unix)]
pub fn wait_for_service() -> Result<std::os::unix::net::UnixStream> {
let socket_path = FrontendListener::socket_path()?;
let mut duration = Duration::from_millis(1);
loop {
use std::os::unix::net::UnixStream;
if let Ok(stream) = UnixStream::connect(&socket_path) {
break Ok(stream)
}
// a signaling mechanism or inotify could be used to
// improve this
std::thread::sleep(*exponential_back_off(&mut duration));
}
}
#[cfg(windows)]
pub fn wait_for_service() -> Result<std::net::TcpStream> {
let mut duration = Duration::from_millis(1);
loop {
use std::net::TcpStream;
if let Ok(stream) = TcpStream::connect("127.0.0.1:5252") {
break Ok(stream)
}
std::thread::sleep(*exponential_back_off(&mut duration));
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum FrontendEvent {
/// add a new client
@@ -70,20 +117,54 @@ pub struct FrontendListener {
}
impl FrontendListener {
pub async fn new() -> std::result::Result<Self, Box<dyn std::error::Error>> {
#[cfg(unix)]
pub fn socket_path() -> Result<PathBuf> {
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
Ok(d) => d,
Err(e) => return Err(anyhow!("could not find XDG_RUNTIME_DIR: {e}")),
};
let xdg_runtime_dir = Path::new(xdg_runtime_dir.as_str());
Ok(xdg_runtime_dir.join("lan-mouse-socket.sock"))
}
pub async fn new() -> Option<Result<Self>> {
#[cfg(unix)]
let socket_path = Path::new(env::var("XDG_RUNTIME_DIR")?.as_str()).join("lan-mouse-socket.sock");
#[cfg(unix)]
log::debug!("remove socket: {:?}", socket_path);
#[cfg(unix)]
if socket_path.exists() {
std::fs::remove_file(&socket_path).unwrap();
}
#[cfg(unix)]
let listener = UnixListener::bind(&socket_path)?;
let (socket_path, listener) = {
let socket_path = match Self::socket_path() {
Ok(path) => path,
Err(e) => return Some(Err(e)),
};
log::debug!("remove socket: {:?}", socket_path);
if socket_path.exists() {
// try to connect to see if some other instance
// of lan-mouse is already running
match UnixStream::connect(&socket_path).await {
// connected -> lan-mouse is already running
Ok(_) => return None,
// lan-mouse is not running but a socket was left behind
Err(e) => {
log::debug!("{socket_path:?}: {e} - removing left behind socket");
let _ = std::fs::remove_file(&socket_path);
},
}
}
let listener = match UnixListener::bind(&socket_path) {
Ok(ls) => ls,
// some other lan-mouse instance has bound the socket in the meantime
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
};
(socket_path, listener)
};
#[cfg(windows)]
let listener = TcpListener::bind("127.0.0.1:5252").await?; // abuse tcp
let listener = match TcpListener::bind("127.0.0.1:5252").await {
Ok(ls) => ls,
// some other lan-mouse instance has bound the socket in the meantime
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
};
let adapter = Self {
listener,
@@ -92,7 +173,7 @@ impl FrontendListener {
tx_streams: vec![],
};
Ok(adapter)
Some(Ok(adapter))
}
#[cfg(unix)]
@@ -121,12 +202,25 @@ impl FrontendListener {
let len = payload.len().to_be_bytes();
log::debug!("json: {json}, len: {}", payload.len());
let mut keep = vec![];
// TODO do simultaneously
for tx in self.tx_streams.iter_mut() {
// write len + payload
tx.write(&len).await?;
tx.write(payload).await?;
if let Err(_) = tx.write(&len).await {
keep.push(false);
continue;
}
if let Err(_) = tx.write(payload).await {
keep.push(false);
continue;
}
keep.push(true);
}
// could not find a better solution because async
let mut keep = keep.into_iter();
self.tx_streams.retain(|_| keep.next().unwrap());
Ok(())
}
}