use std::io;
use std::path::Path;
use anyhow::Result;
#[cfg(all(unix, not(feature = "tcp-transport")))]
mod imp {
use std::io::{self, ErrorKind};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::paths;
pub type Listener = UnixListener;
pub type Stream = UnixStream;
fn endpoint(root: &Path) -> PathBuf {
paths::state_dir(root).join("daemon.sock")
}
pub fn connect(root: &Path) -> io::Result<Option<Stream>> {
match UnixStream::connect(endpoint(root)) {
Ok(s) => Ok(Some(s)),
Err(e)
if e.kind() == ErrorKind::NotFound || e.kind() == ErrorKind::ConnectionRefused =>
{
Ok(None)
}
Err(e) => Err(e),
}
}
pub fn bind(root: &Path) -> Result<Option<Listener>> {
let sock = endpoint(root);
match UnixListener::bind(&sock) {
Ok(l) => Ok(Some(l)),
Err(e) if e.kind() == ErrorKind::AddrInUse => {
if UnixStream::connect(&sock).is_ok() {
Ok(None)
} else {
std::fs::remove_file(&sock).ok();
Ok(Some(UnixListener::bind(&sock)?))
}
}
Err(e) => Err(e.into()),
}
}
pub fn accept(listener: &Listener) -> io::Result<Stream> {
listener.accept().map(|(s, _)| s)
}
pub fn cleanup(root: &Path) {
let _ = std::fs::remove_file(endpoint(root));
}
}
#[cfg(any(windows, feature = "tcp-transport"))]
mod imp {
use std::io::{self, ErrorKind};
use std::net::{Ipv4Addr, TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::paths;
pub type Listener = TcpListener;
pub type Stream = TcpStream;
fn port_file(root: &Path) -> PathBuf {
paths::state_dir(root).join("daemon.port")
}
fn read_port(root: &Path) -> Option<u16> {
std::fs::read_to_string(port_file(root))
.ok()?
.trim()
.parse()
.ok()
}
pub fn connect(root: &Path) -> io::Result<Option<Stream>> {
let Some(port) = read_port(root) else {
return Ok(None);
};
match TcpStream::connect((Ipv4Addr::LOCALHOST, port)) {
Ok(s) => {
s.set_nodelay(true).ok();
Ok(Some(s))
}
Err(e) if e.kind() == ErrorKind::ConnectionRefused => Ok(None),
Err(e) => Err(e),
}
}
pub fn bind(root: &Path) -> Result<Option<Listener>> {
if connect(root)?.is_some() {
return Ok(None);
}
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))?;
let port = listener.local_addr()?.port();
let dir = paths::state_dir(root);
std::fs::create_dir_all(&dir)?;
let tmp = dir.join(format!("daemon.port.{}", std::process::id()));
std::fs::write(&tmp, port.to_string())?;
std::fs::rename(&tmp, port_file(root))?;
Ok(Some(listener))
}
pub fn accept(listener: &Listener) -> io::Result<Stream> {
let (s, _) = listener.accept()?;
s.set_nodelay(true).ok();
Ok(s)
}
pub fn cleanup(root: &Path) {
let _ = std::fs::remove_file(port_file(root));
}
}
pub use imp::{Listener, Stream};
pub fn connect(root: &Path) -> io::Result<Option<Stream>> {
imp::connect(root)
}
pub fn bind(root: &Path) -> Result<Option<Listener>> {
imp::bind(root)
}
pub fn accept(listener: &Listener) -> io::Result<Stream> {
imp::accept(listener)
}
pub fn cleanup(root: &Path) {
imp::cleanup(root)
}