use std::{
fs::{File, OpenOptions, TryLockError},
io,
path::PathBuf,
};
use thiserror::Error;
use tracing::debug;
use crate::paths::{self, PathsError};
pub struct InstanceGuard {
_handle: File,
}
#[derive(Debug, Error)]
pub enum InstanceError {
#[error("could not resolve lock path")]
Path(#[from] PathsError),
#[error("could not open lock file at {path}")]
Open {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("another instance already holds the lock at {path}")]
AlreadyRunning {
path: PathBuf,
},
#[error("lock attempt at {path} failed")]
LockFailed {
path: PathBuf,
#[source]
source: io::Error,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role {
Agent,
Gui,
}
impl Role {
const fn lock_file(self) -> &'static str {
match self {
Self::Agent => "agent.lock",
Self::Gui => "openlogi.lock",
}
}
}
pub fn acquire(role: Role) -> Result<InstanceGuard, InstanceError> {
let path = paths::config_dir()?.join(role.lock_file());
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|source| InstanceError::Open {
path: path.clone(),
source,
})?;
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)
.map_err(|source| InstanceError::Open {
path: path.clone(),
source,
})?;
match file.try_lock() {
Ok(()) => {
debug!(path = %path.display(), "single-instance lock acquired");
Ok(InstanceGuard { _handle: file })
}
Err(TryLockError::WouldBlock) => Err(InstanceError::AlreadyRunning { path }),
Err(TryLockError::Error(source)) => Err(InstanceError::LockFailed { path, source }),
}
}