Added automatically starting server with local socket communication
parent
0c3abd32a3
commit
3dc5b89ee5
|
|
@ -8,4 +8,5 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argh = "0.1.12"
|
argh = "0.1.12"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
interprocess = "1.2.1"
|
||||||
ratatui = "0.23.0"
|
ratatui = "0.23.0"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,16 @@
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
use std::{error::Error, time::Duration};
|
use std::{error::Error, time::Duration};
|
||||||
|
|
||||||
|
use crate::CliArgs;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod crossterm;
|
pub mod crossterm;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
/// Demo
|
pub fn run(args: CliArgs) -> Result<(), Box<dyn Error>> {
|
||||||
#[derive(Debug, FromArgs)]
|
crossterm::run(
|
||||||
struct Cli {
|
Duration::from_millis(args.tick_rate),
|
||||||
/// time in ms between two ticks.
|
args.enhanced_graphics,
|
||||||
#[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)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
src/main.rs
45
src/main.rs
|
|
@ -1,4 +1,47 @@
|
||||||
|
use argh::FromArgs;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod server;
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
200
src/server.rs
200
src/server.rs
|
|
@ -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 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(())
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue