feat: add pomodoro timer
This commit is contained in:
parent
dfd0b52d50
commit
103c6d779e
5 changed files with 182 additions and 17 deletions
10
src/cli.rs
10
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<UnixStream> {
|
||||
|
|
|
@ -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<str>, Duration),
|
||||
Remove(Box<str>),
|
||||
List,
|
||||
Pomodoro {
|
||||
work: Duration,
|
||||
pause: Duration,
|
||||
long_pause: Duration,
|
||||
pauses_till_long: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Answer {
|
||||
Ok,
|
||||
Timers(Vec<Timer>),
|
||||
Timers(Vec<Timer>, Option<Pomodoro>),
|
||||
}
|
||||
|
||||
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<String> =
|
||||
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<Timer>,
|
||||
pomodoro: Option<Pomodoro>,
|
||||
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<Answer, AnswerErr> {
|
||||
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<()> {
|
||||
|
|
13
src/main.rs
13
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)
|
||||
}
|
||||
|
|
90
src/pomodoro.rs
Normal file
90
src/pomodoro.rs
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
12
src/timer.rs
12
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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue