use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
const FILE: &str = "daemon.json";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rendezvous {
pub version: String,
pub pid: u32,
pub http_address: std::net::IpAddr,
pub http_port: u16,
pub workspace_root: PathBuf,
pub started_at: u64,
}
impl Rendezvous {
pub fn path_in(state_dir: &Path) -> PathBuf {
state_dir.join(FILE)
}
pub fn read(state_dir: &Path) -> Result<Option<Self>, String> {
let path = Self::path_in(state_dir);
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(format!("cannot read {}: {e}", path.display())),
};
serde_json::from_slice(&bytes)
.map(Some)
.map_err(|e| format!("{} is not a daemon rendezvous file: {e}", path.display()))
}
pub fn write(&self, state_dir: &Path) -> Result<(), String> {
std::fs::create_dir_all(state_dir)
.map_err(|e| format!("cannot create {}: {e}", state_dir.display()))?;
let path = Self::path_in(state_dir);
let staged = state_dir.join(format!(".{FILE}.{}", std::process::id()));
let encoded = serde_json::to_vec_pretty(self)
.map_err(|e| format!("cannot encode the daemon rendezvous: {e}"))?;
let outcome = (|| -> Result<(), String> {
create_private(&staged)?;
std::fs::write(&staged, &encoded)
.map_err(|e| format!("cannot write {}: {e}", staged.display()))?;
std::fs::rename(&staged, &path)
.map_err(|e| format!("cannot publish {}: {e}", path.display()))
})();
if outcome.is_err() {
let _ = std::fs::remove_file(&staged);
}
outcome
}
pub fn remove(state_dir: &Path) -> Result<(), String> {
let path = Self::path_in(state_dir);
match std::fs::remove_file(&path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(format!("cannot remove {}: {e}", path.display())),
}
}
}
#[cfg(unix)]
pub(super) fn create_private(path: &Path) -> Result<(), String> {
use std::os::unix::fs::OpenOptionsExt;
let _ = std::fs::remove_file(path);
std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(path)
.map(|_| ())
.map_err(|e| format!("cannot create {}: {e}", path.display()))
}
#[cfg(windows)]
pub(super) fn create_private(path: &Path) -> Result<(), String> {
let _ = std::fs::remove_file(path);
std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.map(|_| ())
.map_err(|e| format!("cannot create {}: {e}", path.display()))
}
#[cfg(test)]
#[path = "rendezvous_tests.rs"]
mod tests;