| | use std::{ |
| | fs::{File, OpenOptions}, |
| | mem::ManuallyDrop, |
| | sync::Mutex, |
| | }; |
| |
|
| | use anyhow::Context; |
| | use napi::bindgen_prelude::External; |
| | use napi_derive::napi; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type JsLockfile = Mutex<ManuallyDrop<Option<LockfileInner>>>; |
| |
|
| | pub struct LockfileInner { |
| | file: File, |
| | #[cfg(not(windows))] |
| | path: std::path::PathBuf, |
| | } |
| |
|
| | #[napi(ts_return_type = "{ __napiType: \"Lockfile\" } | null")] |
| | pub fn lockfile_try_acquire_sync(path: String) -> napi::Result<Option<External<JsLockfile>>> { |
| | let mut open_options = OpenOptions::new(); |
| | open_options.write(true).create(true); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #[cfg(windows)] |
| | return { |
| | use std::os::windows::fs::OpenOptionsExt; |
| |
|
| | use windows_sys::Win32::{Foundation, Storage::FileSystem}; |
| |
|
| | open_options |
| | .share_mode(FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_DELETE) |
| | .custom_flags(FileSystem::FILE_FLAG_DELETE_ON_CLOSE); |
| | match open_options.open(&path) { |
| | Ok(file) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some( |
| | LockfileInner { file }, |
| | )))))), |
| | Err(err) |
| | if err.raw_os_error() |
| | == Some(Foundation::ERROR_SHARING_VIOLATION.try_into().unwrap()) => |
| | { |
| | Ok(None) |
| | } |
| | Err(err) => Err(err.into()), |
| | } |
| | }; |
| |
|
| | #[cfg(not(windows))] |
| | return { |
| | use std::fs::TryLockError; |
| |
|
| | let file = open_options.open(&path)?; |
| | match file.try_lock() { |
| | Ok(_) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some( |
| | LockfileInner { |
| | file, |
| | path: path.into(), |
| | }, |
| | )))))), |
| | Err(TryLockError::WouldBlock) => Ok(None), |
| | Err(TryLockError::Error(err)) => Err(err.into()), |
| | } |
| | }; |
| | } |
| |
|
| | #[napi(ts_return_type = "Promise<{ __napiType: \"Lockfile\" } | null>")] |
| | pub async fn lockfile_try_acquire(path: String) -> napi::Result<Option<External<JsLockfile>>> { |
| | tokio::task::spawn_blocking(move || lockfile_try_acquire_sync(path)) |
| | .await |
| | .context("panicked while attempting to acquire lockfile")? |
| | } |
| |
|
| | #[napi] |
| | pub fn lockfile_unlock_sync( |
| | #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>, |
| | ) { |
| | |
| | |
| | let Some(inner): Option<LockfileInner> = lockfile |
| | .lock() |
| | .expect("poisoned: another thread panicked during `lockfile_unlock_sync`?") |
| | .take() |
| | else { |
| | return; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | #[cfg(not(windows))] |
| | let _ = std::fs::remove_file(inner.path); |
| |
|
| | drop(inner.file); |
| | } |
| |
|
| | #[napi] |
| | pub async fn lockfile_unlock( |
| | #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>, |
| | ) -> napi::Result<()> { |
| | Ok( |
| | tokio::task::spawn_blocking(move || lockfile_unlock_sync(lockfile)) |
| | .await |
| | .context("panicked while attempting to unlock lockfile")?, |
| | ) |
| | } |
| |
|