Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions nmrs-gui/src/file_lock.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use fs2::FileExt;
use std::fs;
use std::fs::File;

pub fn acquire_app_lock() -> Result<File, String> {
let mut lock_path = dirs::data_local_dir().unwrap_or(std::env::temp_dir());
lock_path.push("my_app.lock");
// Prefer XDG_RUNTIME_DIR for ephemeral lock files (should be /run/usr/<USR_ID>). Fall back to local data dir.
let mut lock_dir = dirs::runtime_dir()
.or_else(dirs::data_local_dir)
.unwrap_or(std::env::temp_dir());

lock_dir.push("nmrs");

fs::create_dir_all(&lock_dir).map_err(|e| format!("Failed to create lock directory: {e}"))?;

let lock_path = lock_dir.join("app.lock");

let file = File::create(&lock_path).map_err(|e| format!("Failed to create lock file: {e}"))?;

Expand All @@ -13,3 +22,41 @@ pub fn acquire_app_lock() -> Result<File, String> {

Ok(file)
}

// Used so each test can be run independently.
// Without that, with tests being run in parallel, the second test can cause the first one to fail.
#[cfg(test)]
use std::sync::Mutex;

#[cfg(test)]
static TEST_LOCK: Mutex<()> = Mutex::new(());

#[test]
fn test_lock_file_is_created() {
// Test if the lock file is properly created.
let _guard = TEST_LOCK.lock().unwrap();
let file = acquire_app_lock();
assert!(file.is_ok(), "Failed to acquire lock: {:?}", file.err());

let lock_path = dirs::runtime_dir()
.or_else(dirs::data_local_dir)
.unwrap_or(std::env::temp_dir())
.join("nmrs")
.join("app.lock");

assert!(
lock_path.exists(),
"Lock file was not created at {:?}",
lock_path
);
}

#[test]
fn test_second_instance_is_rejected() {
// Test if acquire_app_lock fails as it should when called twice.
let _guard = TEST_LOCK.lock().unwrap();
let _first = acquire_app_lock().expect("First lock should succeed");
let second = acquire_app_lock();
assert!(second.is_err(), "Second instance should have been rejected");
assert_eq!(second.unwrap_err(), "Another instance is already running");
}
Loading