use std::io;
use std::net::TcpStream;
use std::path::PathBuf;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use super::DEFAULT_DAEMON_PORT;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonInfo {
pub pid: u32,
pub hyperd_endpoint: String,
pub health_port: u16,
pub started_at: String,
pub version: String,
}
pub fn state_dir() -> io::Result<PathBuf> {
if let Some(dir) = std::env::var_os("HYPERDB_STATE_DIR") {
return Ok(PathBuf::from(dir));
}
let home = home_dir().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "cannot determine home directory")
})?;
Ok(home.join(".hyperdb"))
}
pub fn discovery_file_path() -> io::Result<PathBuf> {
Ok(state_dir()?.join("daemon.json"))
}
pub fn write_discovery_file(info: &DaemonInfo) -> io::Result<()> {
let dir = state_dir()?;
std::fs::create_dir_all(&dir)?;
let path = dir.join("daemon.json");
let tmp_path = dir.join("daemon.json.tmp");
let json = serde_json::to_string_pretty(info).map_err(|e| io::Error::other(e.to_string()))?;
std::fs::write(&tmp_path, json.as_bytes())?;
let _ = std::fs::remove_file(&path);
std::fs::rename(&tmp_path, &path)?;
Ok(())
}
pub fn discover() -> Option<DaemonInfo> {
let path = discovery_file_path().ok()?;
let contents = std::fs::read_to_string(&path).ok()?;
let info: DaemonInfo = serde_json::from_str(&contents).ok()?;
if is_daemon_alive(info.health_port) {
Some(info)
} else {
let _ = std::fs::remove_file(&path);
None
}
}
pub fn remove_discovery_file() {
if let Ok(path) = discovery_file_path() {
let _ = std::fs::remove_file(&path);
}
}
fn is_daemon_alive(port: u16) -> bool {
TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_secs(2),
)
.is_ok()
}
pub fn resolve_port() -> u16 {
std::env::var(super::ENV_DAEMON_PORT)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_DAEMON_PORT)
}
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
}