Music, but without the subscription.
at main 117 lines 3.4 kB view raw
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}