Added automatically starting server with local socket communication
parent
0c3abd32a3
commit
3dc5b89ee5
|
|
@ -8,4 +8,5 @@ edition = "2021"
|
|||
[dependencies]
|
||||
argh = "0.1.12"
|
||||
crossterm = "0.27.0"
|
||||
interprocess = "1.2.1"
|
||||
ratatui = "0.23.0"
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
45
src/main.rs
45
src/main.rs
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
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 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"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue