use single_instance::SingleInstance;
use thiserror::Error;
const INSTANCE_ID: &str = "mindfork-rs-single-instance";
pub struct InstanceGuard {
_inner: SingleInstance,
}
#[derive(Debug, Error)]
pub enum InstanceError {
#[error("application is already running")]
AlreadyRunning,
#[error("failed to initialize the single-instance lock: {0}")]
Init(String),
}
pub fn acquire() -> Result<InstanceGuard, InstanceError> {
acquire_named(INSTANCE_ID)
}
fn acquire_named(name: &str) -> Result<InstanceGuard, InstanceError> {
let inner =
SingleInstance::new(&lock_name(name)).map_err(|e| InstanceError::Init(e.to_string()))?;
if !inner.is_single() {
return Err(InstanceError::AlreadyRunning);
}
Ok(InstanceGuard { _inner: inner })
}
fn lock_name(name: &str) -> String {
if cfg!(target_os = "macos") {
std::env::temp_dir()
.join(name)
.to_string_lossy()
.into_owned()
} else {
name.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn second_acquire_reports_already_running() {
let name = "mindfork-rs-test-second-acquire-reports-already-running";
let first = acquire_named(name).expect("first acquire should succeed");
match acquire_named(name) {
Err(InstanceError::AlreadyRunning) => {}
Err(other) => panic!("expected AlreadyRunning, got: {other:?}"),
Ok(_) => panic!("second acquire should not succeed while the first is alive"),
}
drop(first);
let _again = acquire_named(name).expect("after drop, acquiring again is possible");
}
#[test]
fn the_lock_leaves_the_working_directory_alone() {
let name = "mindfork-rs-test-the-lock-leaves-the-working-directory-alone";
let _guard = acquire_named(name).expect("acquire");
assert!(
!std::path::Path::new(name).exists(),
"a lock file in the cwd"
);
if cfg!(target_os = "macos") {
assert!(std::env::temp_dir().join(name).is_file());
} else {
assert_eq!(lock_name(name), name);
}
}
}