devpane 0.3.0

A terminal-first multi-process workspace and development dashboard
use super::{Connection, ReadWrite};

use std::fs;
use std::io;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
use std::thread;
use std::time::Duration;

const SOCKET_PATH: &str = "/tmp/devpane.sock";

impl ReadWrite for UnixStream {
    fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
        UnixStream::set_read_timeout(self, timeout)
    }
}

pub struct Listener(UnixListener);

impl Listener {
    pub fn bind() -> io::Result<Self> {
        let listener = UnixListener::bind(SOCKET_PATH)?;
        listener.set_nonblocking(true)?;
        Ok(Self(listener))
    }

    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
        self.0.set_nonblocking(nonblocking)
    }

    pub fn accept(&self) -> io::Result<Option<Connection>> {
        match self.0.accept() {
            Ok((stream, _)) => Ok(Some(Connection::new(stream))),
            Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(None),
            Err(error) => Err(error),
        }
    }
}

pub fn connect() -> io::Result<Connection> {
    UnixStream::connect(SOCKET_PATH).map(Connection::new)
}

pub fn connect_with_retry(retries: u32, delay_ms: u64) -> io::Result<Connection> {
    for attempt in 0..retries {
        match connect() {
            Ok(connection) => return Ok(connection),
            Err(error) if attempt + 1 == retries => {
                return Err(error);
            }
            Err(_) => {
                thread::sleep(Duration::from_millis(delay_ms));
            }
        }
    }

    Err(io::Error::new(
        io::ErrorKind::NotConnected,
        "No connection attempts were made",
    ))
}

pub fn prepare_listener() -> io::Result<Listener> {
    if Path::new(SOCKET_PATH).exists() {
        match connect() {
            Ok(_) => {
                return Err(io::Error::new(
                    io::ErrorKind::AddrInUse,
                    "An instance is already listening",
                ));
            }
            Err(_) => {
                fs::remove_file(SOCKET_PATH)?;
            }
        }
    }

    Listener::bind()
}

pub fn remove_socket() -> io::Result<()> {
    let path = Path::new(SOCKET_PATH);

    if path.exists() {
        fs::remove_file(path)?;
    }

    Ok(())
}