From e56e9d5f0852bcd228c9e32d2e1395cae494352e Mon Sep 17 00:00:00 2001 From: Brayo Date: Sun, 13 Apr 2025 12:27:42 +0300 Subject: [PATCH 1/2] chore(client): add deps fs4,log,thiserror,dirs --- Cargo.lock | 74 +++++++++++++++++++---- aw-client-rust/Cargo.toml | 5 ++ aw-client-rust/src/lib.rs | 8 +++ aw-client-rust/src/single_instance.rs | 84 +++++++++++++++++++++++++++ aw-webui | 2 +- 5 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 aw-client-rust/src/single_instance.rs diff --git a/Cargo.lock b/Cargo.lock index c8a3866b..a3f50284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,11 +205,16 @@ dependencies = [ "aw-models", "aw-server", "chrono", + "dirs 4.0.0", + "fs4", "gethostname", + "libc", + "log", "reqwest", "rocket", "serde", "serde_json", + "thiserror", "tokio", "tokio-test", ] @@ -299,7 +304,7 @@ dependencies = [ "chrono", "clap", "ctrlc", - "dirs", + "dirs 5.0.1", "gethostname", "log", "openssl", @@ -735,13 +740,33 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", ] [[package]] @@ -795,12 +820,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -885,6 +910,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.0.5", + "windows-sys 0.59.0", +] + [[package]] name = "futures" version = "0.3.30" @@ -1305,9 +1340,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libredox" @@ -1336,6 +1371,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.12" @@ -2113,10 +2154,23 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2322,7 +2376,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" dependencies = [ - "dirs", + "dirs 5.0.1", ] [[package]] @@ -2442,7 +2496,7 @@ dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix", + "rustix 0.38.34", "windows-sys 0.59.0", ] diff --git a/aw-client-rust/Cargo.toml b/aw-client-rust/Cargo.toml index f41c3706..a3e2d068 100644 --- a/aw-client-rust/Cargo.toml +++ b/aw-client-rust/Cargo.toml @@ -12,6 +12,11 @@ serde_json = "1.0" chrono = { version = "0.4", features = ["serde"] } aw-models = { path = "../aw-models" } tokio = { version = "1.28.2", features = ["rt"] } +libc = "0.2" +log = "0.4" +thiserror = "1.0" +dirs = "4.0" +fs4 = { version = "0.13", features = ["sync"] } [dev-dependencies] aw-datastore = { path = "../aw-datastore" } diff --git a/aw-client-rust/src/lib.rs b/aw-client-rust/src/lib.rs index f57d1422..93377984 100644 --- a/aw-client-rust/src/lib.rs +++ b/aw-client-rust/src/lib.rs @@ -6,11 +6,13 @@ extern crate serde_json; extern crate tokio; pub mod blocking; +pub mod single_instance; use std::{collections::HashMap, error::Error}; use chrono::{DateTime, Utc}; use serde_json::{json, Map}; +use single_instance::SingleInstance; use std::net::TcpStream; use std::time::Duration; @@ -18,6 +20,8 @@ pub use aw_models::{Bucket, BucketMetadata, Event}; pub struct AwClient { client: reqwest::Client, + #[allow(dead_code)] + single_instance: SingleInstance, pub baseurl: reqwest::Url, pub name: String, pub hostname: String, @@ -40,9 +44,13 @@ impl AwClient { let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(120)) .build()?; + //TODO: change localhost string to 127.0.0.1 for feature parity + let single_instance_name = format!("{}-at-{}-on-{}", name, host, port); + let single_instance = single_instance::SingleInstance::new(single_instance_name.as_str())?; Ok(AwClient { client, + single_instance, baseurl, name: name.to_string(), hostname, diff --git a/aw-client-rust/src/single_instance.rs b/aw-client-rust/src/single_instance.rs new file mode 100644 index 00000000..745fa2b2 --- /dev/null +++ b/aw-client-rust/src/single_instance.rs @@ -0,0 +1,84 @@ +use dirs::cache_dir; +use fs4::fs_std::FileExt; +use log::{debug, error}; +use std::fs::{File, OpenOptions}; +use std::io; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +#[derive(Debug)] +pub struct SingleInstance { + file: Option, + locked: Arc, +} + +#[derive(Debug, thiserror::Error)] +pub enum SingleInstanceError { + #[error("Another instance is already running")] + AlreadyRunning, + #[error("IO error: {0}")] + Io(#[from] io::Error), + #[error("Failed to create lock directory")] + LockDirCreation, +} + +impl SingleInstance { + pub fn new(client_name: &str) -> Result { + let cache_dir = cache_dir().ok_or(SingleInstanceError::LockDirCreation)?; + let lock_dir = cache_dir.join("activitywatch").join("client_locks"); + std::fs::create_dir_all(&lock_dir).map_err(|_| SingleInstanceError::LockDirCreation)?; + + let lockfile = lock_dir.join(client_name); + debug!("SingleInstance lockfile: {:?}", lockfile); + + #[cfg(windows)] + { + // On Windows, try to create an exclusive file + // Remove existing file if it exists (in case of previous crash) + let _ = std::fs::remove_file(&lockfile); + + match OpenOptions::new() + .write(true) + .create(true) + .create_new(true) + .open(&lockfile) + { + Ok(file) => Ok(SingleInstance { + file: Some(file), + locked: Arc::new(AtomicBool::new(true)), + }), + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => { + error!("Another instance is already running"); + Err(SingleInstanceError::AlreadyRunning) + } + Err(e) => Err(SingleInstanceError::Io(e)), + } + } + + #[cfg(unix)] + { + // On Unix-like systems, use flock + match OpenOptions::new().write(true).create(true).open(&lockfile) { + Ok(file) => match file.try_lock_exclusive() { + Ok(true) => Ok(SingleInstance { + file: Some(file), + locked: Arc::new(AtomicBool::new(true)), + }), + Ok(false) => Err(SingleInstanceError::AlreadyRunning), + Err(e) => Err(SingleInstanceError::Io(e)), + }, + Err(e) => Err(SingleInstanceError::Io(e)), + } + } + } +} + +impl Drop for SingleInstance { + fn drop(&mut self) { + if self.locked.load(Ordering::SeqCst) { + //drop the file handle and lock on Unix and Windows + self.file.take(); + self.locked.store(false, Ordering::SeqCst); + } + } +} diff --git a/aw-webui b/aw-webui index 291da6f2..0cf78317 160000 --- a/aw-webui +++ b/aw-webui @@ -1 +1 @@ -Subproject commit 291da6f2c5e7a6b896f23a4eec5ffed9874321ba +Subproject commit 0cf7831771d9cad9e25954eaed99c77d5a7c6e10 From 7d30abe03e37f204f72842ff610695c41fef0533 Mon Sep 17 00:00:00 2001 From: Brayo Date: Sun, 13 Apr 2025 12:28:28 +0300 Subject: [PATCH 2/2] feat(client):support single instance