Music, but without the subscription.
1use anyhow::Context;
2use std::fs::{self, OpenOptions};
3use std::path::PathBuf;
4
5pub enum AcquireResult {
6 Acquired(SingleInstanceGuard),
7 AlreadyRunning,
8}
9
10pub struct SingleInstanceGuard {
11 #[cfg(unix)]
12 _lock_file: std::fs::File,
13 #[cfg(not(any(unix, windows)))]
14 _lock_file: std::fs::File,
15 #[cfg(windows)]
16 handle: windows::Win32::Foundation::HANDLE,
17}
18
19#[cfg(windows)]
20impl Drop for SingleInstanceGuard {
21 fn drop(&mut self) {
22 let _ = unsafe { windows::Win32::Foundation::CloseHandle(self.handle) };
23 }
24}
25
26fn lock_file_path() -> PathBuf {
27 dirs::data_local_dir()
28 .or_else(dirs::data_dir)
29 .unwrap_or_else(std::env::temp_dir)
30 .join("vleer")
31 .join("instance.lock")
32}
33
34#[cfg(unix)]
35pub fn try_acquire() -> anyhow::Result<AcquireResult> {
36 use std::io::ErrorKind;
37 use std::os::fd::AsRawFd;
38 use std::os::raw::c_int;
39
40 const LOCK_EX: c_int = 2;
41 const LOCK_NB: c_int = 4;
42
43 unsafe extern "C" {
44 fn flock(fd: c_int, operation: c_int) -> c_int;
45 }
46
47 let lock_path = lock_file_path();
48 if let Some(parent) = lock_path.parent() {
49 fs::create_dir_all(parent)
50 .with_context(|| format!("failed to create lock directory '{}'", parent.display()))?;
51 }
52
53 let lock_file = OpenOptions::new()
54 .read(true)
55 .write(true)
56 .create(true)
57 .truncate(false)
58 .open(&lock_path)
59 .with_context(|| format!("failed to open lock file '{}'", lock_path.display()))?;
60
61 let result = unsafe { flock(lock_file.as_raw_fd(), LOCK_EX | LOCK_NB) };
62 if result == 0 {
63 return Ok(AcquireResult::Acquired(SingleInstanceGuard {
64 _lock_file: lock_file,
65 }));
66 }
67
68 let err = std::io::Error::last_os_error();
69 if err.kind() == ErrorKind::WouldBlock {
70 return Ok(AcquireResult::AlreadyRunning);
71 }
72
73 Err(err).with_context(|| format!("failed to acquire lock file '{}'", lock_path.display()))
74}
75
76#[cfg(windows)]
77pub fn try_acquire() -> anyhow::Result<AcquireResult> {
78 use anyhow::anyhow;
79 use windows::Win32::Foundation::{CloseHandle, ERROR_ALREADY_EXISTS, GetLastError};
80 use windows::Win32::System::Threading::CreateMutexW;
81 use windows::core::w;
82
83 let handle = unsafe { CreateMutexW(None, false, w!("Global\\VleerSingleInstance"))? };
84 let last_error = unsafe { GetLastError() };
85 if last_error == ERROR_ALREADY_EXISTS {
86 let _ = unsafe { CloseHandle(handle) };
87 return Ok(AcquireResult::AlreadyRunning);
88 }
89
90 Ok(AcquireResult::Acquired(SingleInstanceGuard { handle }))
91}
92
93#[cfg(not(any(unix, windows)))]
94pub fn try_acquire() -> anyhow::Result<AcquireResult> {
95 use std::io::ErrorKind;
96
97 let lock_path = lock_file_path();
98 if let Some(parent) = lock_path.parent() {
99 fs::create_dir_all(parent)
100 .with_context(|| format!("failed to create lock directory '{}'", parent.display()))?;
101 }
102
103 let lock_file = OpenOptions::new()
104 .read(true)
105 .write(true)
106 .create_new(true)
107 .open(&lock_path);
108
109 match lock_file {
110 Ok(file) => Ok(AcquireResult::Acquired(SingleInstanceGuard {
111 _lock_file: file,
112 })),
113 Err(err) if err.kind() == ErrorKind::AlreadyExists => Ok(AcquireResult::AlreadyRunning),
114 Err(err) => Err(err)
115 .with_context(|| format!("failed to create lock file '{}'", lock_path.display())),
116 }
117}