Compare commits

..

2 commits

6 changed files with 73 additions and 26 deletions

16
Cargo.lock generated
View file

@ -345,15 +345,6 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "daemonize"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "derivative" name = "derivative"
version = "2.2.0" version = "2.2.0"
@ -621,9 +612,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.146" version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -1132,11 +1123,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"daemonize",
"humantime", "humantime",
"libc",
"notify-rust", "notify-rust",
"serde", "serde",
"serde_cbor", "serde_cbor",
"signal-hook",
"thiserror", "thiserror",
] ]

View file

@ -8,9 +8,10 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.71" anyhow = "1.0.71"
clap = { version = "4.3.4", features = ["derive"] } clap = { version = "4.3.4", features = ["derive"] }
daemonize = "0.5.0"
humantime = "2.1.0" humantime = "2.1.0"
libc = "0.2.147"
notify-rust = "4.8.0" notify-rust = "4.8.0"
serde = { version = "1.0.164", features = ["derive"] } serde = { version = "1.0.164", features = ["derive"] }
serde_cbor = "0.11.2" serde_cbor = "0.11.2"
signal-hook = "0.3.17"
thiserror = "1.0.44" thiserror = "1.0.44"

View file

@ -1,4 +1,5 @@
use crate::daemon::{Answer, AnswerErr, Command as OtherCommand}; use crate::daemon::{Answer, AnswerErr, Command as OtherCommand};
use crate::run_path;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::net::Shutdown; use std::net::Shutdown;
@ -12,7 +13,7 @@ pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Command,
#[arg(short, long)] #[arg(short, long)]
#[clap(default_value = "/tmp/timers.socket")] #[clap(default_value_t = format!("{}/timers.socket", run_path()))]
pub socket: String, pub socket: String,
} }
@ -24,6 +25,9 @@ pub enum Command {
/// do not send notifications /// do not send notifications
#[arg(short, long)] #[arg(short, long)]
no_notify: bool, no_notify: bool,
#[arg(short, long)]
#[clap(default_value_t = format!("{}/timers.pid", run_path()))]
pid_file: String,
}, },
/// Add a timer /// Add a timer
#[clap(visible_alias = "a")] #[clap(visible_alias = "a")]

View file

@ -4,6 +4,10 @@ pub use crate::timer::Timer;
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::fs::File;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{ use std::{
io::Write, io::Write,
os::unix::net::{UnixListener, UnixStream}, os::unix::net::{UnixListener, UnixStream},
@ -63,22 +67,41 @@ pub enum AnswerErr {
} }
pub struct Daemon { pub struct Daemon {
socket_path: Box<Path>,
pid_file_path: Box<Path>,
listener: UnixListener, listener: UnixListener,
timers: Vec<Timer>, timers: Vec<Timer>,
pomodoro: Option<Pomodoro>, pomodoro: Option<Pomodoro>,
notify: bool, notify: bool,
} }
impl Daemon { #[derive(Debug, thiserror::Error)]
pub fn new(socket_path: String, no_notify: bool) -> anyhow::Result<Self> { pub enum DaemonErr {
let path = std::path::Path::new(&socket_path); #[error("Daemon already running!")]
if path.exists() { AlreadyRunning,
std::fs::remove_file(path)
.with_context(|| format!("Could not remove previous socket {}!", socket_path))?;
} }
let listener = UnixListener::bind(&socket_path)
.context(format!("Cannot bind to socket {}!", socket_path))?; impl Daemon {
pub fn new(socket: String, pid_file: String, no_notify: bool) -> anyhow::Result<Self> {
let pid_file_path = std::path::Path::new(&pid_file);
if pid_file_path.exists() {
return Err(DaemonErr::AlreadyRunning)?;
};
let mut pid_file = File::create(pid_file_path)
.with_context(|| format!("Could not create pid file at {}!", pid_file))?;
write!(pid_file, "{}", std::process::id()).context("Could not write pid to file!")?;
let socket_path = std::path::Path::new(&socket);
if socket_path.exists() {
std::fs::remove_file(socket_path)
.with_context(|| format!("Could not remove previous socket {}!", socket))?;
}
let listener =
UnixListener::bind(&socket).context(format!("Cannot bind to socket {}!", socket))?;
Ok(Self { Ok(Self {
socket_path: socket_path.into(),
pid_file_path: pid_file_path.into(),
listener, listener,
timers: Vec::new(), timers: Vec::new(),
pomodoro: None, pomodoro: None,
@ -168,7 +191,12 @@ impl Daemon {
self.listener self.listener
.set_nonblocking(true) .set_nonblocking(true)
.context("Could not set listener to non blocking!")?; .context("Could not set listener to non blocking!")?;
loop {
let term = Arc::new(AtomicBool::new(false));
for sig in signal_hook::consts::TERM_SIGNALS {
signal_hook::flag::register(*sig, Arc::clone(&term))?;
}
while !term.load(Ordering::Relaxed) {
while let Ok((stream, _)) = self.listener.accept() { while let Ok((stream, _)) = self.listener.accept() {
if let Err(e) = self.handle_stream(&stream) { if let Err(e) = self.handle_stream(&stream) {
println!("Error while handling stream: {}", e) println!("Error while handling stream: {}", e)
@ -176,6 +204,20 @@ impl Daemon {
} }
self.check_timers(); self.check_timers();
sleep(Duration::from_millis(100)); sleep(Duration::from_millis(100));
};
Ok(())
} }
} }
impl Drop for Daemon {
fn drop(&mut self) {
println!("Stopping...");
if let Err(err) = std::fs::remove_file(self.socket_path.as_ref()) {
eprintln!("Error removing socket: {}", err)
};
if let Err(err) = std::fs::remove_file(self.pid_file_path.as_ref()) {
eprintln!("Error removing pid file: {}", err)
};
println!("Stopped successfully!");
}
} }

7
src/helper.rs Normal file
View file

@ -0,0 +1,7 @@
pub fn getuid() -> u32 {
unsafe { libc::getuid() }
}
pub fn run_path() -> String {
format!("/run/user/{}", getuid())
}

View file

@ -1,20 +1,21 @@
mod cli; mod cli;
mod daemon; mod daemon;
mod helper;
mod notification; mod notification;
mod pomodoro; mod pomodoro;
mod timer; mod timer;
use crate::cli::{send_command, Cli, Command as CliCommand}; use crate::cli::{send_command, Cli, Command as CliCommand};
use crate::daemon::{Command as DaemonCommand, Daemon}; use crate::daemon::{Command as DaemonCommand, Daemon};
use crate::helper::run_path;
use clap::Parser; use clap::Parser;
use cli::PomodoroCommand; use cli::PomodoroCommand;
fn main() -> Result<(), anyhow::Error> { fn main() -> Result<(), anyhow::Error> {
let args = Cli::parse(); let args = Cli::parse();
let daemon_command = match args.command { let daemon_command = match args.command {
CliCommand::Daemon { no_notify } => { CliCommand::Daemon { no_notify, pid_file } => {
daemonize::Daemonize::new().start()?; return Daemon::new(args.socket, pid_file, no_notify)?.run();
return Daemon::new(args.socket, no_notify)?.run();
} }
CliCommand::Add { name, duration } => { CliCommand::Add { name, duration } => {
DaemonCommand::Add(name.into_boxed_str(), duration.into()) DaemonCommand::Add(name.into_boxed_str(), duration.into())