client_core/sync/
lockfile.rs1use fs2::FileExt;
2use serde::Serialize;
3use std::fs::{File, OpenOptions};
4use std::io::Write;
5use std::path::Path;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Copy, Serialize)]
9pub enum LockKind {
10 Desktop,
11 Cli,
12}
13
14#[derive(Serialize)]
15struct LockBody<'a> {
16 pid: u32,
17 kind: &'a str,
18 started_at: String,
19}
20
21pub struct Lockfile {
22 file: File, }
24
25impl Lockfile {
26 pub fn try_acquire(path: &Path, kind: LockKind) -> std::io::Result<Option<Self>> {
30 if let Some(dir) = path.parent() {
31 std::fs::create_dir_all(dir)?;
32 }
33 let file = OpenOptions::new()
34 .create(true)
35 .read(true)
36 .write(true)
37 .truncate(false)
38 .open(path)?;
39 match file.try_lock_exclusive() {
40 Ok(()) => {
41 let body = LockBody {
42 pid: std::process::id(),
43 kind: match kind {
44 LockKind::Desktop => "desktop",
45 LockKind::Cli => "cli",
46 },
47 started_at: chrono::Utc::now().to_rfc3339(),
48 };
49 let mut f = &file;
50 let _ = f.set_len(0);
51 let _ = writeln!(f, "{}", serde_json::to_string(&body).unwrap_or_default());
52 Ok(Some(Self { file }))
53 }
54 Err(e)
55 if e.kind() == std::io::ErrorKind::WouldBlock
56 || matches!(e.raw_os_error(), Some(11) | Some(35)) =>
57 {
58 Ok(None)
59 }
60 Err(e) => Err(e),
61 }
62 }
63
64 pub fn is_held_by_other(path: &Path) -> std::io::Result<bool> {
67 if let Some(dir) = path.parent() {
68 std::fs::create_dir_all(dir)?;
69 }
70 let f = OpenOptions::new()
71 .create(true)
72 .read(true)
73 .write(true)
74 .truncate(false)
75 .open(path)?;
76 match f.try_lock_exclusive() {
77 Ok(()) => {
78 let _ = FileExt::unlock(&f);
81 Ok(false)
82 }
83 Err(_) => Ok(true),
84 }
85 }
86}
87
88impl Drop for Lockfile {
89 fn drop(&mut self) {
90 let _ = FileExt::unlock(&self.file);
91 }
92}
93
94pub async fn acquire_blocking(
96 path: &Path,
97 kind: LockKind,
98 poll: Duration,
99 deadline: std::time::Instant,
100) -> std::io::Result<Option<Lockfile>> {
101 loop {
102 match Lockfile::try_acquire(path, kind)? {
103 Some(l) => return Ok(Some(l)),
104 None if std::time::Instant::now() >= deadline => return Ok(None),
105 None => tokio::time::sleep(poll).await,
106 }
107 }
108}