rmux 0.7.0

A local terminal multiplexer with a tmux-style CLI, daemon runtime, Rust SDK, and ratatui integration.
#![cfg(windows)]

use std::fs::{remove_file, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::{Duration, Instant, SystemTime};

const LOCK_STALE_AFTER: Duration = Duration::from_secs(10 * 60);
const LOCK_WAIT_TIMEOUT: Duration = Duration::from_secs(120);

pub(crate) struct WindowsCliSerialGuard {
    path: PathBuf,
}

pub(crate) fn acquire(label: &str) -> io::Result<WindowsCliSerialGuard> {
    let path = std::env::temp_dir().join("rmux-windows-cli-integration.lock");
    let deadline = Instant::now() + LOCK_WAIT_TIMEOUT;
    loop {
        match OpenOptions::new().write(true).create_new(true).open(&path) {
            Ok(file) => {
                use std::io::Write as _;
                writeln!(&file, "pid={} label={label}", std::process::id())?;
                return Ok(WindowsCliSerialGuard { path });
            }
            Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {
                remove_stale_lock(&path);
                if Instant::now() >= deadline {
                    return Err(io::Error::new(
                        io::ErrorKind::TimedOut,
                        format!(
                            "timed out waiting for Windows CLI integration lock '{}'",
                            path.display()
                        ),
                    ));
                }
                thread::sleep(Duration::from_millis(50));
            }
            Err(error) => return Err(error),
        }
    }
}

fn remove_stale_lock(path: &Path) {
    let Ok(metadata) = std::fs::metadata(path) else {
        return;
    };
    let Ok(modified) = metadata.modified() else {
        return;
    };
    let Ok(age) = SystemTime::now().duration_since(modified) else {
        return;
    };
    if age >= LOCK_STALE_AFTER {
        let _ = remove_file(path);
    }
}

impl Drop for WindowsCliSerialGuard {
    fn drop(&mut self) {
        let _ = remove_file(&self.path);
    }
}