diff --git a/src/cli.rs b/src/cli.rs index 5490a22..3926105 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -40,12 +40,6 @@ pub enum Command { /// List timers #[clap(visible_alias = "l")] List, - /// Toggle timer - #[clap(visible_alias = "t")] - Toggle { - /// name of the timer to toggle - name: String, - }, /// Remove a timer #[clap(visible_alias = "r")] Remove { @@ -89,14 +83,15 @@ pub enum PomodoroCommand { /// List the pomodoro settings and remaining duration #[clap(visible_alias = "l")] 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)) } pub fn send_command(socket_path: &String, command: OtherCommand) -> Result { - let stream = UnixStream::connect(socket_path) - .context(format!("Could not connect to socket {}!", socket_path))?; + let stream = get_stream(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 40fb853..e9cecbb 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,4 +1,4 @@ -use crate::helper::send_notifictation; +use crate::notification::send_notifictation; use crate::pomodoro::Pomodoro; pub use crate::timer::Timer; use anyhow::Context; @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::fs::File; use std::path::Path; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ io::Write, os::unix::net::{UnixListener, UnixStream}, @@ -18,7 +18,6 @@ use std::{ #[derive(Debug, Serialize, Deserialize)] pub enum Command { Add(Box, Duration), - Toggle(Box), Remove(Box), List, PomodoroStart { @@ -29,7 +28,6 @@ pub enum Command { }, PomodoroRemove, PomodoroList, - PomodoroToggle, } #[derive(Debug, Serialize, Deserialize)] @@ -39,20 +37,33 @@ 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)] TimerAlreadyExist(Box), #[error("No timer with the name '{}' exists", .0)] NoSuchTimer(Box), - #[error("No pomodoro running")] - NoPomodoro, -} - -#[derive(Debug, thiserror::Error)] -pub enum DaemonErr { - #[error("Daemon already running!")] - AlreadyRunning, } pub struct Daemon { @@ -64,6 +75,12 @@ 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); @@ -93,11 +110,7 @@ impl Daemon { } fn has_timer(&mut self, name: &str) -> bool { - self.get_timer(name).is_some() - } - - fn get_timer(&mut self, name: &str) -> Option<&mut Timer> { - self.timers.iter_mut().find(|t| t.name.as_ref() == name) + self.timers.iter().any(|other| other.name.as_ref() == name) } fn handle_command(&mut self, command: Command) -> Result { @@ -124,13 +137,6 @@ impl Daemon { self.timers.push(timer); Ok(Answer::Ok) } - Command::Toggle(name) => match self.get_timer(&name) { - Some(timer) => { - timer.toggle(); - Ok(Answer::Ok) - } - None => Err(AnswerErr::NoSuchTimer(name)), - }, Command::Remove(name) => { if !self.has_timer(&name) { return Err(AnswerErr::NoSuchTimer(name)); @@ -154,13 +160,6 @@ impl Daemon { Ok(Answer::Ok) } Command::PomodoroList => Ok(Answer::Pomodoro(self.pomodoro.clone())), - Command::PomodoroToggle => match &mut self.pomodoro { - Some(ref mut pomodoro) => { - pomodoro.timer.toggle(); - Ok(Answer::Ok) - } - None => Err(AnswerErr::NoPomodoro), - }, } } @@ -173,10 +172,18 @@ impl Daemon { } fn check_timers(&mut self) { - self.timers.retain(|timer| !timer.is_expired()); + self.timers.retain(|timer| { + if timer.is_expired() { + timer.handle_expiration(self.notify); + } + + !timer.is_expired() + }); if let Some(pomodoro) = &mut self.pomodoro { - pomodoro.update(); + if pomodoro.is_expired() { + pomodoro.handle_expiration(self.notify); + } } } @@ -189,17 +196,15 @@ 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) { - if let Ok((stream, _)) = self.listener.accept() { - self.handle_stream(&stream)?; + while let Ok((stream, _)) = self.listener.accept() { + if let Err(e) = self.handle_stream(&stream) { + println!("Error while handling stream: {}", e) + } } self.check_timers(); sleep(Duration::from_millis(100)); - } + }; Ok(()) } } @@ -216,24 +221,3 @@ 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 ecd9b90..164b5e9 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,5 +1,3 @@ -use notify_rust::Notification; - pub fn getuid() -> u32 { unsafe { libc::getuid() } } @@ -7,10 +5,3 @@ 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/main.rs b/src/main.rs index 5d6286d..5b118eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,6 @@ fn main() -> Result<(), anyhow::Error> { } CliCommand::List => DaemonCommand::List, CliCommand::Remove { name } => DaemonCommand::Remove(name.into_boxed_str()), - CliCommand::Toggle { name } => DaemonCommand::Toggle(name.into_boxed_str()), CliCommand::Pomodoro(pomodoro) => match pomodoro { PomodoroCommand::Start { work, @@ -37,7 +36,6 @@ fn main() -> Result<(), anyhow::Error> { }, PomodoroCommand::Remove => DaemonCommand::PomodoroRemove, PomodoroCommand::List => DaemonCommand::PomodoroList, - PomodoroCommand::Toggle => DaemonCommand::PomodoroToggle, }, }; let answer = send_command(&args.socket, daemon_command)?; diff --git a/src/notification.rs b/src/notification.rs index e69de29..28be7e5 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -0,0 +1,8 @@ +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 8cd5103..1bc2929 100644 --- a/src/pomodoro.rs +++ b/src/pomodoro.rs @@ -64,13 +64,8 @@ impl Pomodoro { } } - pub fn update(&mut self) { - if self.timer.is_expired() { - self.switch(); - }; - } - - fn switch(&mut self) { + pub fn handle_expiration(&mut self, notify: bool) { + self.timer.handle_expiration(notify); let duration = match self.status { Status::Working => { if self.pauses == self.pauses_till_long { @@ -95,4 +90,8 @@ 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 c8226fa..44c929c 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,86 +1,10 @@ -use crate::helper::send_notifictation; +use crate::notification::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}; @@ -108,3 +32,49 @@ mod approx_instant { } } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct Timer { + pub name: Box, + #[serde(with = "approx_instant")] + start: Instant, + duration: Duration, +} + +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, + } + } + + 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 { + let exact = self.duration - (Instant::now() - self.start); + Duration::from_secs(exact.as_secs()) + } + + pub fn handle_expiration(&self, notify: bool) { + let msg = format!("Timer {} has expired!", self.name); + println!("{}", &msg); + if notify { + send_notifictation(msg.as_str()); + } + } +}