leta-config 0.11.1

This is an internal component crate of leta
Documentation
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;

pub fn get_config_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
    PathBuf::from(home).join(".config").join("leta")
}

pub fn get_config_path() -> PathBuf {
    get_config_dir().join("config.toml")
}

pub fn get_cache_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
    PathBuf::from(home).join(".cache").join("leta")
}

pub fn get_log_dir() -> PathBuf {
    get_cache_dir().join("log")
}

pub fn get_socket_path() -> PathBuf {
    get_cache_dir().join("daemon.sock")
}

pub fn get_pid_path() -> PathBuf {
    get_cache_dir().join("daemon.pid")
}

pub fn write_pid(path: &std::path::Path, pid: u32) -> std::io::Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, pid.to_string())
}

pub fn remove_pid(path: &std::path::Path) {
    let _ = std::fs::remove_file(path);
}

pub fn read_pid(path: &std::path::Path) -> Option<u32> {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|s| s.trim().parse().ok())
}

pub fn is_daemon_running() -> bool {
    let pid_path = get_pid_path();
    if let Some(pid) = read_pid(&pid_path) {
        is_process_running(pid)
    } else {
        false
    }
}

#[cfg(unix)]
fn is_process_running(pid: u32) -> bool {
    unsafe { libc::kill(pid as i32, 0) == 0 }
}

#[cfg(not(unix))]
fn is_process_running(_pid: u32) -> bool {
    false
}

pub fn detect_workspace_root(path: &std::path::Path) -> Option<PathBuf> {
    let markers = [
        ".git",
        "pyproject.toml",
        "setup.py",
        "package.json",
        "Cargo.toml",
        "go.mod",
        "pom.xml",
        "build.gradle",
        "Gemfile",
        "composer.json",
        "mix.exs",
        "dune-project",
    ];

    let mut current = path.to_path_buf();
    loop {
        for marker in &markers {
            if current.join(marker).exists() {
                return Some(current);
            }
        }
        if !current.pop() {
            break;
        }
    }
    None
}

pub struct DaemonLock {
    _file: File,
}

impl DaemonLock {
    pub fn acquire() -> Option<DaemonLock> {
        let lock_path = get_pid_path().with_extension("lock");
        if let Some(parent) = lock_path.parent() {
            let _ = std::fs::create_dir_all(parent);
        }

        let file = match File::options()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&lock_path)
        {
            Ok(f) => f,
            Err(_) => return None,
        };

        let fd = file.as_raw_fd();
        let result = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
        if result == 0 {
            Some(DaemonLock { _file: file })
        } else {
            None
        }
    }
}