Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions client/src/lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::env;
use std::fmt;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

const LOCK_FILE_NAME: &str = "centerpiece.lock";
const XDG_RUNTIME_DIR_ENV: &str = "XDG_RUNTIME_DIR";

#[derive(Debug)]
pub enum LockFileError {
AlreadyLocked(PathBuf),
IoError(std::io::Error),
UnlockError(PathBuf, std::io::Error),
}

type LockFileResult<T> = Result<T, LockFileError>;

impl fmt::Display for LockFileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let display_path = |path: &PathBuf| path.to_string_lossy().to_string();
match self {
LockFileError::AlreadyLocked(ref path) => {
write!(
f,
"Could not hold an exclusive lock on the lockfile in {}\n\
If you think this is an error, please remove the lockfile manually",
display_path(path)
)
}
LockFileError::IoError(ref err) => write!(f, "IoError: {}", err),
LockFileError::UnlockError(ref path, ref err) => {
write!(
f,
"Failed to remove the lockfile: {} - IoError: {}",
display_path(path),
err
)
}
}
}
}

impl From<std::io::Error> for LockFileError {
fn from(err: std::io::Error) -> Self {
LockFileError::IoError(err)
}
}

/// Queries the environment for the $XDG_RUNTIME_DIR
fn get_xdg_runtime_dir() -> Option<String> {
env::var(XDG_RUNTIME_DIR_ENV).ok()
}

#[derive(Debug)]
pub struct LockFile(PathBuf);

impl LockFile {
fn get_or_init() -> &'static Option<Self> {
static LOCK_FILE: OnceLock<Option<LockFile>> = OnceLock::new();
LOCK_FILE.get_or_init(Self::init)
}

fn init() -> Option<Self> {
Some(Self(Self::get_lock_file_path()?))
}

fn path(&self) -> &Path {
self.0.as_path()
}

fn path_buf(&self) -> PathBuf {
self.0.as_path().to_path_buf()
}

fn get_lock_file_path() -> Option<PathBuf> {
let xdg_runtime_dir = get_xdg_runtime_dir()?;
Some(Path::new(&xdg_runtime_dir).join(LOCK_FILE_NAME))
}

fn try_lock(&self) -> LockFileResult<()> {
if self.path().is_file() {
return Err(LockFileError::AlreadyLocked(self.path_buf()));
} else {
let _ = File::create(self.path())?;
};
Ok(())
}

fn remove_lockfile() -> LockFileResult<()> {
if let Some(lock_file) = Self::get_or_init() {
std::fs::remove_file(lock_file.path())
.map_err(|err| LockFileError::UnlockError(lock_file.path_buf(), err))?
}
Ok(())
}

/// Attempts to remove the lock file - logs errors.
/// *MUST* be run on every shutdown of `centerpiece`.
pub fn unlock() {
if let Err(err) = Self::remove_lockfile() {
log::error!("{err}");
}
}
/// Attempts to hold an exclusive lock in the runtime dir.
/// If we can't find the XDG_RUNTIME_DIR, we don't hold a lock.
/// If the lock is not successful, then exit `centerpiece`.
pub fn run_exclusive() {
Self::get_or_init().as_ref().map(|lock_file| {
if let Err(err) = lock_file.try_lock() {
log::error!("{err}");
log::error!("Stopping centerpiece.");
std::process::exit(1);
}
});
}
}
9 changes: 8 additions & 1 deletion client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use iced::Application;

mod cli;
mod component;
mod lock;
mod model;
mod plugin;
mod settings;

pub fn main() -> iced::Result {
let args = crate::cli::CliArgs::parse();
simple_logger::init_with_level(log::Level::Info).unwrap();
lock::LockFile::run_exclusive();
Centerpiece::run(Centerpiece::settings(args))
}

Expand Down Expand Up @@ -42,6 +44,7 @@ impl Application for Centerpiece {
fn new(flags: crate::cli::CliArgs) -> (Self, iced::Command<Message>) {
let settings = crate::settings::Settings::try_from(flags).unwrap_or_else(|_| {
eprintln!("There is an issue with the settings, please check the configuration file.");
lock::LockFile::unlock();
std::process::exit(0);
});

Expand Down Expand Up @@ -105,6 +108,7 @@ impl Application for Centerpiece {
}
iced::keyboard::Event::KeyReleased { key, .. } => {
if key == iced::keyboard::Key::Named(iced::keyboard::key::Named::Escape) {
lock::LockFile::unlock();
return iced::window::close(iced::window::Id::MAIN);
}
iced::Command::none()
Expand All @@ -126,7 +130,10 @@ impl Application for Centerpiece {

Message::UpdateEntries(plugin_id, entries) => self.update_entries(plugin_id, entries),

Message::Exit => iced::window::close(iced::window::Id::MAIN),
Message::Exit => {
lock::LockFile::unlock();
iced::window::close(iced::window::Id::MAIN)
}
}
}

Expand Down