diff --git a/Cargo.lock b/Cargo.lock
index eda3076..9ab99c9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -311,6 +311,7 @@ dependencies = [
"espanso-path",
"espanso-render",
"espanso-ui",
+ "fs2",
"html2text",
"lazy_static",
"log",
@@ -484,6 +485,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index 9c0762c..3850c00 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -37,4 +37,5 @@ serde = { version = "1.0.123", features = ["derive"] }
serde_json = "1.0.62"
markdown = "0.3.0"
html2text = "0.2.1"
-log-panics = "2.0.0"
\ No newline at end of file
+log-panics = "2.0.0"
+fs2 = "0.4.3"
\ No newline at end of file
diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs
index bb024a4..cc83226 100644
--- a/espanso/src/cli/daemon/mod.rs
+++ b/espanso/src/cli/daemon/mod.rs
@@ -19,7 +19,9 @@
use std::process::Command;
-use log::info;
+use log::{error, info};
+
+use crate::lock::acquire_daemon_lock;
use super::{CliModule, CliModuleArgs};
@@ -41,16 +43,20 @@ const VERSION: &str = env!("CARGO_PKG_VERSION");
fn daemon_main(args: CliModuleArgs) {
let paths = args.paths.expect("missing paths in daemon main");
+ // Make sure only one instance of the daemon is running
+ let lock_file = acquire_daemon_lock(&paths.runtime);
+ if lock_file.is_none() {
+ error!("daemon is already running!");
+ std::process::exit(1);
+ }
+
info!("espanso version: {}", VERSION);
// TODO: print os system and version? (with os_info crate)
- // TODO: check daemon lock file to avoid duplicates
// TODO: check worker lock file, if taken stop the worker process through IPC
- // TODO: start IPC server
-
- // TODO: start file watcher thread
+ // TODO: register signals to terminate the worker if the daemon terminates
let espanso_exe_path =
std::env::current_exe().expect("unable to obtain espanso executable location");
@@ -71,4 +77,12 @@ fn daemon_main(args: CliModuleArgs) {
}
command.spawn().expect("unable to spawn worker process");
+
+ // TODO: start IPC server
+
+ // TODO: start file watcher thread
+
+ loop {
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+ }
}
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index 8ad11d0..ce39055 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -17,6 +17,10 @@
* along with espanso. If not, see .
*/
+use log::error;
+
+use crate::lock::acquire_worker_lock;
+
use self::ui::util::convert_icon_paths_to_tray_vec;
use super::{CliModule, CliModuleArgs};
@@ -39,6 +43,15 @@ pub fn new() -> CliModule {
}
fn worker_main(args: CliModuleArgs) {
+ let paths = args.paths.expect("missing paths in worker main");
+
+ // Avoid running multiple worker instances
+ let lock_file = acquire_worker_lock(&paths.runtime);
+ if lock_file.is_none() {
+ error!("worker is already running!");
+ std::process::exit(1);
+ }
+
let config_store = args
.config_store
.expect("missing config store in worker main");
@@ -46,8 +59,6 @@ fn worker_main(args: CliModuleArgs) {
.match_store
.expect("missing match store in worker main");
- let paths = args.paths.expect("missing paths in worker main");
-
let icon_paths =
self::ui::icon::load_icon_paths(&paths.runtime).expect("unable to initialize icons");
diff --git a/espanso/src/lock.rs b/espanso/src/lock.rs
new file mode 100644
index 0000000..ea85613
--- /dev/null
+++ b/espanso/src/lock.rs
@@ -0,0 +1,72 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 Federico Terzi
+ *
+ * espanso is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * espanso is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with espanso. If not, see .
+ */
+
+use anyhow::Result;
+use fs2::FileExt;
+use std::{
+ fs::{File, OpenOptions},
+ path::Path,
+};
+
+pub struct Lock {
+ lock_file: File,
+}
+
+impl Lock {
+ pub fn release(self) -> Result<()> {
+ self.lock_file.unlock()?;
+ Ok(())
+ }
+
+ fn acquire(runtime_dir: &Path, name: &str) -> Option {
+ let lock_file_path = runtime_dir.join(format!("{}.lock", name));
+ let lock_file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&lock_file_path)
+ .expect(&format!(
+ "unable to create reference to lock file: {:?}",
+ lock_file_path
+ ));
+
+ if lock_file.try_lock_exclusive().is_ok() {
+ Some(Lock { lock_file })
+ } else {
+ None
+ }
+ }
+}
+
+impl Drop for Lock {
+ fn drop(&mut self) {
+ self
+ .lock_file
+ .unlock()
+ .expect(&format!("unable to unlock lock_file: {:?}", self.lock_file));
+ }
+}
+
+pub fn acquire_daemon_lock(runtime_dir: &Path) -> Option {
+ Lock::acquire(runtime_dir, "espanso-daemon.lock")
+}
+
+pub fn acquire_worker_lock(runtime_dir: &Path) -> Option {
+ Lock::acquire(runtime_dir, "espanso-worker.lock")
+}
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index d09c168..2e9a25d 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -35,6 +35,7 @@ use crate::cli::LogMode;
mod cli;
mod engine;
mod gui;
+mod lock;
mod logging;
mod util;
@@ -253,7 +254,7 @@ fn main() {
if handler.enable_logs {
let config = ConfigBuilder::new()
.set_time_to_local(true)
- .set_time_format(format!("%H:%M:%S [{}]", handler.subcommand))
+ .set_time_format(format!("%H:%M:%S [{}({})]", handler.subcommand, std::process::id()))
.set_location_level(LevelFilter::Off)
.add_filter_ignore_str("html5ever")
.build();