From 103c6d779e434fce22e7f1a9e217878a44b2f974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Sat, 29 Jul 2023 12:15:19 +0200 Subject: [PATCH] feat: add pomodoro timer --- src/cli.rs | 10 ++++++ src/daemon.rs | 74 ++++++++++++++++++++++++++++++---------- src/main.rs | 13 +++++++ src/pomodoro.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ src/timer.rs | 12 +++++++ 5 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 src/pomodoro.rs diff --git a/src/cli.rs b/src/cli.rs index 2271f98..3e61c18 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -30,6 +30,16 @@ pub enum Command { Remove { name: String, }, + Pomodoro { + #[clap(default_value_t = 25)] + work_minutes: u64, + #[clap(default_value_t = 5)] + pause_minutes: u64, + #[clap(default_value_t = 10)] + long_pause_minutes: u64, + #[clap(default_value_t = 3)] + pauses_till_long: u64, + } } fn get_stream(socket_path: &String) -> Result { diff --git a/src/daemon.rs b/src/daemon.rs index 41d45d0..c35ee21 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,3 +1,4 @@ +use crate::pomodoro::Pomodoro; pub use crate::timer::Timer; use anyhow::Context; use notify_rust::Notification; @@ -15,25 +16,36 @@ pub enum Command { Add(Box, Duration), Remove(Box), List, + Pomodoro { + work: Duration, + pause: Duration, + long_pause: Duration, + pauses_till_long: u64, + }, } #[derive(Debug, Serialize, Deserialize)] pub enum Answer { Ok, - Timers(Vec), + Timers(Vec, Option), } impl Display for Answer { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Answer::Ok => write!(f, "Ok"), - Answer::Timers(timers) => { + Answer::Timers(timers, pomodoro) => { if timers.is_empty() { - write!(f, "No timers running.") + writeln!(f, "No timers running.")?; } else { let strings: Vec = timers.iter().map(|timer| timer.to_string()).collect(); - write!(f, "{}", strings.join("\n")) + writeln!(f, "{}", strings.join("\n"))?; + }; + + match pomodoro { + Some(p) => write!(f, "{}", p), + None => write!(f, "No pomodoro running."), } } } @@ -51,6 +63,7 @@ pub enum AnswerErr { pub struct Daemon { listener: UnixListener, timers: Vec, + pomodoro: Option, notify: bool, } @@ -66,6 +79,7 @@ impl Daemon { Ok(Self { listener, timers: Vec::new(), + pomodoro: None, notify, }) } @@ -77,11 +91,23 @@ impl Daemon { fn handle_command(&mut self, command: Command) -> Result { println!("Received command {:?}", command); match command { - Command::List => Ok(Answer::Timers(self.timers.clone())), + Command::List => Ok(Answer::Timers(self.timers.clone(), self.pomodoro.clone())), Command::Add(name, duration) => { if self.has_timer(&name) { return Err(AnswerErr::TimerAlreadyExist(name)); } + + if self.notify { + match Notification::new() + .summary("󰀠 Timers") + .body(format!("Started timer {} for {:?}", &name, duration).as_str()) + .show() + { + Ok(_) => println!("Sent notification sucessfully."), + Err(_) => println!("Failed to send notification."), + }; + } + let timer = Timer::new(name, duration); self.timers.push(timer); Ok(Answer::Ok) @@ -94,6 +120,23 @@ impl Daemon { .retain(|other| other.name.as_ref() != name.as_ref()); Ok(Answer::Ok) } + Command::Pomodoro { + work, + pause, + long_pause, + pauses_till_long, + } => { + match Notification::new() + .summary("󰀠 Timers") + .body("Started pomodoro.") + .show() + { + Ok(_) => println!("Sent notification sucessfully."), + Err(_) => println!("Failed to send notification."), + }; + self.pomodoro = Some(Pomodoro::new(work, pause, long_pause, pauses_till_long)); + Ok(Answer::Ok) + } } } @@ -107,21 +150,18 @@ impl Daemon { fn check_timers(&mut self) { self.timers.retain(|timer| { - let expired = timer.is_expired(); - - if expired { - let msg = format!("Timer {} has expired!", timer.name); - println!("{}", &msg); - if self.notify { - match Notification::new().summary("󰀠 Timers").body(&msg).show() { - Ok(_) => println!("Send notification sucessfully."), - Err(_) => println!("Failed to send notification."), - } - } + if timer.is_expired() { + timer.handle_expiration(self.notify); } - !expired + !timer.is_expired() }); + + if let Some(pomodoro) = &mut self.pomodoro { + if pomodoro.is_expired() { + pomodoro.handle_expiration(self.notify); + } + } } pub fn run(&mut self) -> anyhow::Result<()> { diff --git a/src/main.rs b/src/main.rs index 32f32b2..7890f75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ pub mod cli; pub mod daemon; +pub mod pomodoro; pub mod timer; use std::time::Duration; @@ -19,6 +20,18 @@ fn main() -> Result<()> { } => DaemonCommand::Add(name.into_boxed_str(), Duration::from_secs(duration_seconds)), CliCommand::List => DaemonCommand::List, CliCommand::Remove { name } => DaemonCommand::Remove(name.into_boxed_str()), + CliCommand::Pomodoro { + work_minutes, + pause_minutes, + long_pause_minutes, + pauses_till_long, + + } => DaemonCommand::Pomodoro{ + work: Duration::from_secs(work_minutes * 60), + pause: Duration::from_secs(pause_minutes * 60), + long_pause: Duration::from_secs(long_pause_minutes * 60), + pauses_till_long + }, }; send_command(&args.socket, daemon_command) } diff --git a/src/pomodoro.rs b/src/pomodoro.rs new file mode 100644 index 0000000..7344ddf --- /dev/null +++ b/src/pomodoro.rs @@ -0,0 +1,90 @@ +use std::{fmt::Display, time::Duration}; + +use serde::{Deserialize, Serialize}; + +use crate::daemon::Timer; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Pomodoro { + work: Duration, + pause: Duration, + long_pause: Duration, + pauses_till_long: u64, + pauses: u64, + status: Status, + pub timer: Timer, +} + +impl Display for Pomodoro { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Pomodoro ({:?}, {:?}, {:?}) currently {} with {:?} remaining.", + self.work, + self.pause, + self.long_pause, + self.status, + self.timer.remaining() + ) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +enum Status { + Working, + Pausing, +} + +impl Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Status::Working => write!(f, "working"), + Status::Pausing => write!(f, "pausing"), + } + } +} + +impl Pomodoro { + pub fn new( + work: Duration, + pause: Duration, + long_pause: Duration, + pauses_till_long: u64, + ) -> Self { + Pomodoro { + work, + pause, + long_pause, + pauses_till_long, + pauses: 0, + status: Status::Working, + timer: Timer::new(Status::Working.to_string().into_boxed_str(), work), + } + } + + pub fn handle_expiration(&mut self, notify: bool) { + self.timer.handle_expiration(notify); + let duration = match self.status { + Status::Pausing => self.work, + Status::Working => { + if self.pauses == self.pauses_till_long { + self.long_pause + } else { + self.pause + } + } + }; + self.status = match self.status { + Status::Working => { + self.pauses += 1; + Status::Pausing + } + Status::Pausing => Status::Working, + }; + 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 291fe5e..9c59d53 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,3 +1,4 @@ +use notify_rust::Notification; use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Formatter}, @@ -66,4 +67,15 @@ impl Timer { pub fn remaining(&self) -> Duration { self.duration - (Instant::now() - self.start) } + + pub fn handle_expiration(&self, notify: bool) { + let msg = format!("Timer {} has expired!", self.name); + println!("{}", &msg); + if notify { + match Notification::new().summary("󰀠 Timers").body(&msg).show() { + Ok(_) => println!("Sent notification sucessfully."), + Err(_) => println!("Failed to send notification."), + } + } + } }