use std::io;
use std::path::PathBuf;
use std::time::{Duration, Instant};
const SPAWN_GRACE: Duration = Duration::from_millis(600);
const STOP_TIMEOUT: Duration = Duration::from_secs(15);
fn data_dir() -> PathBuf {
std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".local/share")))
.or_else(|| std::env::var_os("LOCALAPPDATA").map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from("."))
.join("av1converter")
}
pub fn pid_file() -> PathBuf {
data_dir().join("daemon.pid")
}
pub fn log_file() -> PathBuf {
data_dir().join("daemon.log")
}
pub fn running_pid() -> Option<u32> {
let pid = std::fs::read_to_string(pid_file()).ok()?.trim().parse().ok()?;
alive(pid).then_some(pid)
}
pub fn write_pid_file() -> io::Result<()> {
std::fs::create_dir_all(data_dir())?;
std::fs::write(pid_file(), std::process::id().to_string())
}
pub fn remove_pid_file() {
let _ = std::fs::remove_file(pid_file());
}
#[cfg(unix)]
fn alive(pid: u32) -> bool {
unsafe { libc::kill(pid as libc::pid_t, 0) == 0 }
}
#[cfg(not(unix))]
fn alive(_pid: u32) -> bool {
false
}
#[cfg(unix)]
pub fn spawn_background() -> io::Result<u32> {
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
std::fs::create_dir_all(data_dir())?;
let log = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(log_file())?;
let log_err = log.try_clone()?;
let mut cmd = Command::new(std::env::current_exe()?);
cmd.arg("--daemon-foreground")
.stdin(Stdio::null())
.stdout(log)
.stderr(log_err)
.current_dir("/");
unsafe {
cmd.pre_exec(|| {
if libc::setsid() == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
});
}
let mut child = cmd.spawn()?;
std::thread::sleep(SPAWN_GRACE);
if child.try_wait()?.is_some() {
return Err(io::Error::other("daemon exited during startup"));
}
Ok(child.id())
}
#[cfg(not(unix))]
pub fn spawn_background() -> io::Result<u32> {
Err(io::Error::other(
"background mode is only supported on Unix; use --daemon-foreground",
))
}
#[cfg(unix)]
pub fn stop(pid: u32) -> io::Result<()> {
if unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) } != 0 {
return Err(io::Error::last_os_error());
}
let deadline = Instant::now() + STOP_TIMEOUT;
while Instant::now() < deadline {
if !alive(pid) {
remove_pid_file();
return Ok(());
}
std::thread::sleep(Duration::from_millis(100));
}
Err(io::Error::other("daemon did not exit in time"))
}
#[cfg(not(unix))]
pub fn stop(_pid: u32) -> io::Result<()> {
Err(io::Error::other("--stop is only supported on Unix"))
}