Added automatically starting server with local socket communication

master
hheik 2023-09-29 22:51:23 +03:00
parent 0c3abd32a3
commit 3dc5b89ee5
4 changed files with 251 additions and 17 deletions

View File

@ -8,4 +8,5 @@ edition = "2021"
[dependencies]
argh = "0.1.12"
crossterm = "0.27.0"
interprocess = "1.2.1"
ratatui = "0.23.0"

View File

@ -1,24 +1,16 @@
use argh::FromArgs;
use std::{error::Error, time::Duration};
use crate::CliArgs;
pub mod app;
pub mod crossterm;
pub mod ui;
/// Demo
#[derive(Debug, FromArgs)]
struct Cli {
/// time in ms between two ticks.
#[argh(option, default = "250")]
tick_rate: u64,
/// whether unicode symbols are used to improve the overall look of the app
#[argh(option, default = "true")]
enhanced_graphics: bool,
}
pub fn run() -> Result<(), Box<dyn Error>> {
let cli: Cli = argh::from_env();
let tick_rate = Duration::from_millis(cli.tick_rate);
crossterm::run(tick_rate, cli.enhanced_graphics)?;
pub fn run(args: CliArgs) -> Result<(), Box<dyn Error>> {
crossterm::run(
Duration::from_millis(args.tick_rate),
args.enhanced_graphics,
)?;
Ok(())
}

View File

@ -1,4 +1,47 @@
use argh::FromArgs;
pub mod client;
pub mod server;
fn main() {}
/// rmp: Rust Music Player
#[derive(Debug, FromArgs)]
pub struct CliArgs {
/// run the server
#[argh(switch, short = 's')]
server: bool,
/// kill server
#[argh(switch, short = 'x')]
exit: bool,
/// don't start server even if it's not running
#[argh(switch, short = 'c')]
client_only: bool,
/// time in ms between two ticks.
#[argh(option, default = "250")]
tick_rate: u64,
/// whether unicode symbols are used to improve the overall look of the app
#[argh(option, default = "true")]
enhanced_graphics: bool,
}
fn main() {
let args: CliArgs = argh::from_env();
if args.exit {
server::kill();
return;
}
if args.server {
server::run(args).unwrap();
return;
}
if !args.client_only && !server::is_running().unwrap() {
server::run_in_background();
}
client::run(args).map(|_| server::kill()).unwrap();
}

View File

@ -1,5 +1,203 @@
use interprocess::local_socket::LocalSocketListener;
use std::{fmt::Debug, fs, path::PathBuf, process};
use crate::CliArgs;
pub mod audio_backend;
pub fn run() -> Result<(), ()> {
#[derive(Debug)]
pub enum ServerError {
Other(String),
Io(std::io::Error),
AlreadyStarted,
MissingRuntimeDir(PathBuf),
}
impl ServerError {
fn from_debuggable(err: impl Debug) -> Self {
Self::Other(format!("Unexpected error: {err:?}").to_string())
}
}
pub struct Server {
pub socket: LocalSocketListener,
}
impl Server {
pub fn new(socket_path: PathBuf) -> Result<Self, ServerError> {
Ok(Self {
socket: LocalSocketListener::bind(socket_path).map_err(|err| ServerError::Io(err))?,
})
}
}
pub fn run(args: CliArgs) -> Result<(), ServerError> {
if let Err(err) = os::reserve_pid() {
let exit_code = handle_error(err);
process::exit(exit_code);
}
serve()
}
pub fn kill() {
if let Err(err) = os::kill() {
let exit_code = handle_error(err);
process::exit(exit_code);
}
}
pub fn is_running() -> Result<bool, ServerError> {
match os::is_running() {
Ok(is_running) => Ok(is_running),
Err(err) => {
let exit_code = handle_error(err);
process::exit(exit_code);
}
}
}
pub fn run_in_background() {
if let Err(err) = os::run_in_background() {
let exit_code = handle_error(err);
process::exit(exit_code);
}
}
fn serve() -> Result<(), ServerError> {
let socket_path = os::get_socket_path()?;
if socket_path.exists() {
fs::remove_file(&socket_path).map_err(|err| ServerError::Io(err))?;
}
let server = Server::new(socket_path)?;
println!("Waiting for messages...");
for message in server.socket.incoming() {
match message {
Ok(stream) => {
println!("\tstream: {:?}", stream);
}
Err(err) => {
let exit_code = handle_error(ServerError::Io(err));
process::exit(exit_code);
}
}
}
println!("Reached the end of an infinite loop?");
Ok(())
}
fn handle_error(err: ServerError) -> i32 {
match &err {
ServerError::Other(msg) => {
eprintln!("Unknown error: {msg}");
1
}
ServerError::Io(err) => {
eprintln!("IO error: {err}");
2
}
ServerError::AlreadyStarted => {
eprintln!("Server already running");
100
}
ServerError::MissingRuntimeDir(path) => {
eprintln!("Missing runtime directory: {}", path.to_string_lossy());
101
}
}
}
#[cfg(target_family = "unix")]
mod os {
use std::{
fs,
path::{Path, PathBuf},
process::{id, Command, Stdio},
};
use super::ServerError;
pub fn reserve_pid() -> Result<(), ServerError> {
let pid_path = get_pid_path()?;
is_running()?;
fs::write(&pid_path, id().to_string()).map_err(|err| ServerError::Io(err))?;
Command::new("chmod")
.args(&["600", &pid_path.to_string_lossy()])
.output()
.map_err(|err| ServerError::Io(err))?;
Ok(())
}
pub fn is_running() -> Result<bool, ServerError> {
let pid_path = get_pid_path()?;
match fs::read(&pid_path) {
Ok(old_pid) => {
let old_pid =
String::from_utf8(old_pid).map_err(|err| ServerError::from_debuggable(err))?;
let old_pid = old_pid.trim();
Ok(Command::new("ps")
.args(&["-p", old_pid])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map_err(|err| ServerError::Io(err))?
.success())
}
_ => Ok(false),
}
}
pub fn run_in_background() -> Result<(), ServerError> {
let this = std::env::args().next().unwrap();
Command::new(this)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(&["-s"])
.spawn()
.map_err(|err| ServerError::Io(err))?;
Ok(())
}
pub fn kill() -> Result<(), ServerError> {
let pid_path = get_pid_path()?;
let socket_path = get_socket_path()?;
let pid = String::from_utf8(fs::read(&pid_path).map_err(|err| ServerError::Io(err))?)
.map_err(|err| ServerError::from_debuggable(err))?;
let pid = pid.trim();
Command::new("kill")
.arg(pid)
.spawn()
.map_err(|err| ServerError::Io(err))?;
Command::new("rm")
.args(&[
"-f",
&pid_path.to_string_lossy(),
&socket_path.to_string_lossy(),
])
.spawn()
.map_err(|err| ServerError::Io(err))?;
Ok(())
}
pub fn get_socket_path() -> Result<PathBuf, ServerError> {
Ok(get_runtime_dir()?.join("rmp.socket"))
}
fn get_runtime_dir() -> Result<PathBuf, ServerError> {
let uid = String::from_utf8(
Command::new("id")
.arg("-u")
.output()
.map_err(|err| ServerError::Io(err))?
.stdout,
)
.map_err(|err| ServerError::from_debuggable(err))?;
let dir = Path::new("/run/user").join(uid.trim().to_string());
Ok(dir)
}
fn get_pid_path() -> Result<PathBuf, ServerError> {
Ok(get_runtime_dir()?.join("rmp.pid"))
}
}