diff --git a/src/cli.rs b/src/cli.rs index a7f5a8d..5490a22 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -44,7 +44,7 @@ pub enum Command { #[clap(visible_alias = "t")] Toggle { /// name of the timer to toggle - name: String + name: String, }, /// Remove a timer #[clap(visible_alias = "r")] @@ -91,16 +91,12 @@ pub enum PomodoroCommand { List, /// Toggle pomodoro #[clap(visible_alias = "t")] - Toggle -} - -fn get_stream(socket_path: &String) -> Result { - UnixStream::connect(socket_path) - .context(format!("Could not connect to socket {}!", socket_path)) + Toggle, } pub fn send_command(socket_path: &String, command: OtherCommand) -> Result { - let stream = get_stream(socket_path)?; + let stream = UnixStream::connect(socket_path) + .context(format!("Could not connect to socket {}!", socket_path))?; serde_cbor::to_writer(&stream, &command).context("Could not write command!")?; stream .shutdown(Shutdown::Write) diff --git a/src/daemon.rs b/src/daemon.rs index 42e6ad3..40fb853 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,4 +1,4 @@ -use crate::notification::send_notifictation; +use crate::helper::send_notifictation; use crate::pomodoro::Pomodoro; pub use crate::timer::Timer; use anyhow::Context; @@ -39,27 +39,6 @@ pub enum Answer { Pomodoro(Option), } -impl Display for Answer { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - Answer::Ok => write!(f, ""), - Answer::Timers(timers) => { - if timers.is_empty() { - writeln!(f, "No timers running.") - } else { - let strings: Vec = - timers.iter().map(|timer| timer.to_string()).collect(); - writeln!(f, "{}", strings.join("\n")) - } - } - Answer::Pomodoro(pomodoro) => match pomodoro { - Some(p) => write!(f, "{}", p), - None => write!(f, "No pomodoro running."), - }, - } - } -} - #[derive(Debug, thiserror::Error, Serialize, Deserialize)] pub enum AnswerErr { #[error("Timer with name '{}' already exists", .0)] @@ -70,6 +49,12 @@ pub enum AnswerErr { NoPomodoro, } +#[derive(Debug, thiserror::Error)] +pub enum DaemonErr { + #[error("Daemon already running!")] + AlreadyRunning, +} + pub struct Daemon { socket_path: Box, pid_file_path: Box, @@ -79,12 +64,6 @@ pub struct Daemon { notify: bool, } -#[derive(Debug, thiserror::Error)] -pub enum DaemonErr { - #[error("Daemon already running!")] - AlreadyRunning, -} - impl Daemon { pub fn new(socket: String, pid_file: String, no_notify: bool) -> anyhow::Result { let pid_file_path = std::path::Path::new(&pid_file); @@ -179,10 +158,9 @@ impl Daemon { Some(ref mut pomodoro) => { pomodoro.timer.toggle(); Ok(Answer::Ok) - }, + } None => Err(AnswerErr::NoPomodoro), - } - + }, } } @@ -195,18 +173,10 @@ impl Daemon { } fn check_timers(&mut self) { - self.timers.retain(|timer| { - if timer.is_expired() { - timer.handle_expiration(self.notify); - } - - !timer.is_expired() - }); + self.timers.retain(|timer| !timer.is_expired()); if let Some(pomodoro) = &mut self.pomodoro { - if pomodoro.is_expired() { - pomodoro.handle_expiration(self.notify); - } + pomodoro.update(); } } @@ -219,11 +189,13 @@ impl Daemon { for sig in signal_hook::consts::TERM_SIGNALS { signal_hook::flag::register(*sig, Arc::clone(&term))?; } + self.main_loop(term) + } + + fn main_loop(&mut self, term: Arc) -> anyhow::Result<()> { while !term.load(Ordering::Relaxed) { - while let Ok((stream, _)) = self.listener.accept() { - if let Err(e) = self.handle_stream(&stream) { - println!("Error while handling stream: {}", e) - } + if let Ok((stream, _)) = self.listener.accept() { + self.handle_stream(&stream)?; } self.check_timers(); sleep(Duration::from_millis(100)); @@ -244,3 +216,24 @@ impl Drop for Daemon { println!("Stopped successfully!"); } } + +impl Display for Answer { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Answer::Ok => write!(f, ""), + Answer::Timers(timers) => { + if timers.is_empty() { + writeln!(f, "No timers running.") + } else { + let strings: Vec = + timers.iter().map(|timer| timer.to_string()).collect(); + writeln!(f, "{}", strings.join("\n")) + } + } + Answer::Pomodoro(pomodoro) => match pomodoro { + Some(p) => write!(f, "{}", p), + None => write!(f, "No pomodoro running."), + }, + } + } +} diff --git a/src/helper.rs b/src/helper.rs index 164b5e9..ecd9b90 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,3 +1,5 @@ +use notify_rust::Notification; + pub fn getuid() -> u32 { unsafe { libc::getuid() } } @@ -5,3 +7,10 @@ pub fn getuid() -> u32 { pub fn run_path() -> String { format!("/run/user/{}", getuid()) } + +pub fn send_notifictation(msg: &str) { + match Notification::new().summary("󰀠 Timers").body(msg).show() { + Ok(_) => println!("Sent notification sucessfully."), + Err(_) => println!("Failed to send notification."), + }; +} diff --git a/src/notification.rs b/src/notification.rs index 28be7e5..e69de29 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -1,8 +0,0 @@ -use notify_rust::Notification; - -pub fn send_notifictation(msg: &str) { - match Notification::new().summary("󰀠 Timers").body(msg).show() { - Ok(_) => println!("Sent notification sucessfully."), - Err(_) => println!("Failed to send notification."), - }; -} diff --git a/src/pomodoro.rs b/src/pomodoro.rs index 1bc2929..8cd5103 100644 --- a/src/pomodoro.rs +++ b/src/pomodoro.rs @@ -64,8 +64,13 @@ impl Pomodoro { } } - pub fn handle_expiration(&mut self, notify: bool) { - self.timer.handle_expiration(notify); + pub fn update(&mut self) { + if self.timer.is_expired() { + self.switch(); + }; + } + + fn switch(&mut self) { let duration = match self.status { Status::Working => { if self.pauses == self.pauses_till_long { @@ -90,8 +95,4 @@ impl Pomodoro { }; self.timer = Timer::new(self.status.to_string().into_boxed_str(), duration); } - - pub fn is_expired(&self) -> bool { - self.timer.is_expired() - } } diff --git a/src/timer.rs b/src/timer.rs index a613fe0..c8226fa 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,10 +1,86 @@ -use crate::notification::send_notifictation; +use crate::helper::send_notifictation; use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Formatter}, time::{Duration, Instant}, }; +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct Timer { + pub name: Box, + #[serde(with = "approx_instant")] + start: Instant, + duration: Duration, + state: State, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum State { + Running, + Paused, +} + +impl Timer { + /// Create a new [`Timer`] with the supplied name and duration + /// The timer is instantly started. + pub fn new(name: Box, duration: Duration) -> Timer { + Timer { + name, + start: Instant::now(), + duration, + state: State::Running, + } + } + + /// Returns `true` if this [`Timer`] has expired + pub fn is_expired(&self) -> bool { + let expired = Instant::now() - self.start > self.duration; + if expired { + self.handle_expiration() + }; + expired + } + + /// Returns the remaining duration rounded to seconds of this [`Timer`]. + pub fn remaining(&self) -> Duration { + if self.state == State::Paused { + return self.duration; + }; + let exact = self.duration - (Instant::now() - self.start); + Duration::from_secs(exact.as_secs()) + } + + /// Handles the expiration of this [`Timer`] + fn handle_expiration(&self) { + let msg = format!("Timer {} has expired!", self.name); + println!("{}", &msg); + send_notifictation(msg.as_str()); + } + + /// Toggle [`State`] of this [`Timer`] + /// from [`State::Running`] to [`State::Paused`] and vice versa + pub fn toggle(&mut self) { + if self.state != State::Paused { + self.duration = self.remaining(); + self.state = State::Paused; + } else { + self.start = Instant::now(); + self.state = State::Running; + } + } +} + +impl Display for Timer { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "{} has {} remaining.", + self.name, + humantime::format_duration(self.remaining()) + ) + } +} + mod approx_instant { use std::time::{Duration, Instant}; @@ -32,72 +108,3 @@ mod approx_instant { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct Timer { - pub name: Box, - #[serde(with = "approx_instant")] - start: Instant, - duration: Duration, - state: State, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] -pub enum State { - Running, - Paused, -} - -impl Display for Timer { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - f, - "{} has {} remaining.", - self.name, - humantime::format_duration(self.remaining()) - ) - } -} - -impl Timer { - pub fn new(name: Box, duration: Duration) -> Timer { - Timer { - name, - start: Instant::now(), - duration, - state: State::Running, - } - } - - pub fn is_expired(&self) -> bool { - Instant::now() - self.start > self.duration - } - - /// Returns the remaining duration rounded to seconds of this [`Timer`]. - pub fn remaining(&self) -> Duration { - if self.state == State::Paused { - return self.duration; - }; - let exact = self.duration - (Instant::now() - self.start); - Duration::from_secs(exact.as_secs()) - } - - /// Logs or notifies timer expiration - pub fn handle_expiration(&self, notify: bool) { - let msg = format!("Timer {} has expired!", self.name); - println!("{}", &msg); - if notify { - send_notifictation(msg.as_str()); - } - } - - /// Toggles the timers state - pub fn toggle(&mut self) { - if self.state != State::Paused { - self.duration = self.remaining(); - self.state = State::Paused; - } else { - self.start = Instant::now(); - self.state = State::Running; - } - } -}