Make daemon start on server-commands when it's not running

This commit is contained in:
elkowar 2021-07-29 13:03:37 +02:00
parent cf25f9eddb
commit ec4506d9e4
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
5 changed files with 119 additions and 47 deletions

View file

@ -24,7 +24,7 @@ pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) ->
Ok(())
}
pub fn do_server_call(mut stream: UnixStream, action: opts::ActionWithServer) -> Result<Option<app::DaemonResponse>> {
pub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWithServer) -> Result<Option<app::DaemonResponse>> {
log::info!("Forwarding options to server");
stream.set_nonblocking(false).context("Failed to set stream to non-blocking")?;

View file

@ -1,6 +1,9 @@
use std::sync::{Arc, Mutex};
use codespan_reporting::{diagnostic::Diagnostic, term, term::Chars};
use codespan_reporting::{
diagnostic::Diagnostic,
term::{self, Chars},
};
use eww_shared_util::Span;
use simplexpr::eval::EvalError;
use yuck::{config::file_provider::YuckFiles, error::AstError, format_diagnostic::ToDiagnostic, gen_diagnostic};

View file

@ -13,17 +13,21 @@ extern crate gtk;
extern crate gtk_layer_shell as gtk_layer_shell;
use anyhow::*;
use opts::ActionWithServer;
use std::{
os::unix::net,
path::{Path, PathBuf},
time::Duration,
};
use crate::server::ForkResult;
pub mod app;
pub mod application_lifecycle;
pub mod client;
pub mod config;
pub mod display_backend;
pub mod error;
mod error_handling_ctx;
pub mod eww_state;
pub mod geometry;
@ -33,9 +37,9 @@ pub mod script_var_handler;
pub mod server;
pub mod util;
pub mod widgets;
pub mod error;
fn main() {
let eww_binary_name = std::env::args().next().unwrap();
let opts: opts::Opt = opts::Opt::from_env();
let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info };
@ -52,45 +56,55 @@ fn main() {
.unwrap_or_else(EwwPaths::default)
.context("Failed to initialize eww paths")?;
match opts.action {
let would_show_logs = match opts.action {
opts::Action::ClientOnly(action) => {
client::handle_client_only_action(&paths, action)?;
false
}
opts::Action::WithServer(ActionWithServer::KillServer) => {
handle_server_command(&paths, &ActionWithServer::KillServer, 1)?;
false
}
opts::Action::WithServer(action) => {
log::info!("Trying to find server process at socket {}", paths.get_ipc_socket_file().display());
match net::UnixStream::connect(&paths.get_ipc_socket_file()) {
Ok(stream) => {
log::info!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display());
let response =
client::do_server_call(stream, action).context("Error while forwarding command to server")?;
if let Some(response) = response {
println!("{}", response);
if response.is_failure() {
std::process::exit(1);
}
}
}
Err(_) => {
eprintln!("Failed to connect to the eww daemon.");
eprintln!("Make sure to start the eww daemon process by running `eww daemon` first.");
std::process::exit(1);
}
}
}
opts::Action::Daemon => {
// make sure that there isn't already a Eww daemon running.
if check_server_running(paths.get_ipc_socket_file()) {
eprintln!("Eww server already running.");
std::process::exit(1);
} else {
log::info!("Initializing Eww server. ({})", paths.get_ipc_socket_file().display());
if let Err(err) = handle_server_command(&paths, &action, 5) {
// connecting to the daemon failed. Thus, start the daemon here!
log::warn!("Failed to connect to daemon: {}", err);
log::info!("Initializing eww server. ({})", paths.get_ipc_socket_file().display());
let _ = std::fs::remove_file(paths.get_ipc_socket_file());
if !opts.show_logs {
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
}
println!("Run `eww logs` to see any errors, warnings or information while editing your configuration.");
server::initialize_server(paths)?;
let (command, response_recv) = action.into_daemon_command();
let fork_result = server::initialize_server(paths.clone(), Some(command))?;
let is_parent = fork_result == ForkResult::Parent;
if let (Some(recv), true) = (response_recv, is_parent) {
listen_for_daemon_response(recv);
}
is_parent
} else {
true
}
}
// make sure that there isn't already a Eww daemon running.
opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => {
eprintln!("Eww server already running.");
true
}
opts::Action::Daemon => {
log::info!("Initializing Eww server. ({})", paths.get_ipc_socket_file().display());
let _ = std::fs::remove_file(paths.get_ipc_socket_file());
if !opts.show_logs {
println!("Run `{} logs` to see any errors while editing your configuration.", eww_binary_name);
}
let fork_result = server::initialize_server(paths.clone(), None)?;
fork_result == ForkResult::Parent
}
};
if would_show_logs && opts.show_logs {
client::handle_client_only_action(&paths, opts::ActionClientOnly::Logs)?;
}
};
@ -100,11 +114,43 @@ fn main() {
}
}
fn listen_for_daemon_response(mut recv: app::DaemonResponseReceiver) {
let rt = tokio::runtime::Builder::new_current_thread().enable_time().build().expect("Failed to initialize tokio runtime");
rt.block_on(async {
if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), recv.recv()).await {
println!("{}", response);
}
})
}
fn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, connect_attempts: usize) -> Result<()> {
log::info!("Trying to find server process at socket {}", paths.get_ipc_socket_file().display());
let mut stream = attempt_connect(&paths.get_ipc_socket_file(), connect_attempts).context("Failed to connect to daemon")?;
log::info!("Connected to Eww server ({}).", &paths.get_ipc_socket_file().display());
let response = client::do_server_call(&mut stream, action).context("Error while forwarding command to server")?;
if let Some(response) = response {
println!("{}", response);
}
Ok(())
}
fn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Option<net::UnixStream> {
for _ in 0..attempts {
if let Ok(mut con) = net::UnixStream::connect(&socket_path) {
if client::do_server_call(&mut con, &opts::ActionWithServer::Ping).is_ok() {
return net::UnixStream::connect(&socket_path).ok();
}
}
std::thread::sleep(Duration::from_millis(200));
}
None
}
/// Check if a eww server is currently running by trying to send a ping message to it.
fn check_server_running(socket_path: impl AsRef<Path>) -> bool {
let response = net::UnixStream::connect(socket_path)
.ok()
.and_then(|stream| client::do_server_call(stream, opts::ActionWithServer::Ping).ok());
.and_then(|mut stream| client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok());
response.is_some()
}

View file

@ -11,6 +11,7 @@ use crate::app;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Opt {
pub log_debug: bool,
pub show_logs: bool,
pub config_path: Option<std::path::PathBuf>,
pub action: Action,
}
@ -25,6 +26,10 @@ struct RawOpt {
#[structopt(short, long, global = true)]
config: Option<std::path::PathBuf>,
/// Watch the log output after executing the command
#[structopt(long = "logs", global = true)]
show_logs: bool,
#[structopt(subcommand)]
action: Action,
}
@ -136,8 +141,8 @@ impl Opt {
impl From<RawOpt> for Opt {
fn from(other: RawOpt) -> Self {
let RawOpt { action, log_debug, config } = other;
Opt { action, log_debug, config_path: config }
let RawOpt { action, log_debug, show_logs, config } = other;
Opt { action, log_debug, show_logs, config_path: config }
}
}

View file

@ -1,4 +1,9 @@
use crate::{app, config, error_handling_ctx, eww_state::*, ipc_server, script_var_handler, util, EwwPaths};
use crate::{
app::{self, DaemonCommand},
config, error_handling_ctx,
eww_state::*,
ipc_server, script_var_handler, util, EwwPaths,
};
use anyhow::*;
use std::{
@ -9,7 +14,7 @@ use std::{
};
use tokio::sync::mpsc::*;
pub fn initialize_server(paths: EwwPaths) -> Result<()> {
pub fn initialize_server(paths: EwwPaths, action: Option<DaemonCommand>) -> Result<ForkResult> {
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
std::env::set_current_dir(&paths.get_config_dir())
@ -31,7 +36,11 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> {
}
};
do_detach(&paths.get_log_file())?;
let fork_result = do_detach(&paths.get_log_file())?;
if fork_result == ForkResult::Parent {
return Ok(ForkResult::Parent);
}
println!(
r#"
@ -76,6 +85,9 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> {
init_async_part(app.paths.clone(), ui_send);
glib::MainContext::default().spawn_local(async move {
if let Some(action) = action {
app.handle_command(action);
}
while let Some(event) = ui_recv.recv().await {
app.handle_command(event);
}
@ -84,7 +96,7 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> {
gtk::main();
log::info!("main application thread finished");
Ok(())
Ok(ForkResult::Child)
}
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) {
@ -160,7 +172,7 @@ async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<
tokio::spawn(async move {
match daemon_resp_response.recv().await {
Some(app::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
Some(app::DaemonResponse::Failure(e)) => log::error!("Failed to reload config: {}", e),
Some(app::DaemonResponse::Failure(e)) => eprintln!("{}", e),
None => log::error!("No response to reload configuration-reload request"),
}
});
@ -171,14 +183,20 @@ async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<
return Ok(());
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ForkResult {
Parent,
Child,
}
/// detach the process from the terminal, also redirecting stdout and stderr to LOG_FILE
fn do_detach(log_file_path: impl AsRef<Path>) -> Result<()> {
fn do_detach(log_file_path: impl AsRef<Path>) -> Result<ForkResult> {
// detach from terminal
match unsafe { nix::unistd::fork()? } {
nix::unistd::ForkResult::Parent { .. } => {
std::process::exit(0);
}
nix::unistd::ForkResult::Child => {}
nix::unistd::ForkResult::Parent { .. } => {
return Ok(ForkResult::Parent);
}
}
let file = std::fs::OpenOptions::new()
@ -195,5 +213,5 @@ fn do_detach(log_file_path: impl AsRef<Path>) -> Result<()> {
nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?;
}
Ok(())
Ok(ForkResult::Child)
}