next_swc_napi/
lockfile.rs1use std::{
2 fs::{File, OpenOptions},
3 mem::ManuallyDrop,
4 sync::Mutex,
5};
6
7use anyhow::Context;
8use napi::bindgen_prelude::External;
9use napi_derive::napi;
10
11type JsLockfile = Mutex<ManuallyDrop<Option<LockfileInner>>>;
19
20pub struct LockfileInner {
21 file: File,
22 #[cfg(not(windows))]
23 path: std::path::PathBuf,
24}
25
26#[napi(ts_return_type = "{ __napiType: \"Lockfile\" } | null")]
27pub fn lockfile_try_acquire_sync(path: String) -> napi::Result<Option<External<JsLockfile>>> {
28 let mut open_options = OpenOptions::new();
29 open_options.write(true).create(true);
30
31 #[cfg(windows)]
40 return {
41 use std::os::windows::fs::OpenOptionsExt;
42
43 use windows_sys::Win32::{Foundation, Storage::FileSystem};
44
45 open_options
46 .share_mode(FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_DELETE)
47 .custom_flags(FileSystem::FILE_FLAG_DELETE_ON_CLOSE);
48 match open_options.open(&path) {
49 Ok(file) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some(
50 LockfileInner { file },
51 )))))),
52 Err(err)
53 if err.raw_os_error()
54 == Some(Foundation::ERROR_SHARING_VIOLATION.try_into().unwrap()) =>
55 {
56 Ok(None)
57 }
58 Err(err) => Err(err.into()),
59 }
60 };
61
62 #[cfg(not(windows))]
63 return {
64 use std::fs::TryLockError;
65
66 let file = open_options.open(&path)?;
67 match file.try_lock() {
68 Ok(_) => Ok(Some(External::new(Mutex::new(ManuallyDrop::new(Some(
69 LockfileInner {
70 file,
71 path: path.into(),
72 },
73 )))))),
74 Err(TryLockError::WouldBlock) => Ok(None),
75 Err(TryLockError::Error(err)) => Err(err.into()),
76 }
77 };
78}
79
80#[napi(ts_return_type = "Promise<{ __napiType: \"Lockfile\" } | null>")]
81pub async fn lockfile_try_acquire(path: String) -> napi::Result<Option<External<JsLockfile>>> {
82 tokio::task::spawn_blocking(move || lockfile_try_acquire_sync(path))
83 .await
84 .context("panicked while attempting to acquire lockfile")?
85}
86
87#[napi]
88pub fn lockfile_unlock_sync(
89 #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>,
90) {
91 let Some(inner): Option<LockfileInner> = lockfile
94 .lock()
95 .expect("poisoned: another thread panicked during `lockfile_unlock_sync`?")
96 .take()
97 else {
98 return;
99 };
100
101 #[cfg(not(windows))]
107 let _ = std::fs::remove_file(inner.path);
108
109 drop(inner.file);
110}
111
112#[napi]
113pub async fn lockfile_unlock(
114 #[napi(ts_arg_type = "{ __napiType: \"Lockfile\" }")] lockfile: External<JsLockfile>,
115) -> napi::Result<()> {
116 Ok(
117 tokio::task::spawn_blocking(move || lockfile_unlock_sync(lockfile))
118 .await
119 .context("panicked while attempting to unlock lockfile")?,
120 )
121}