use super::Alert; use std::error::Error; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::{self, Write}; use std::thread; use std::time::{Duration, Instant}; #[derive(Debug, Copy, Clone, Default)] pub struct Pomodoro { state: State, count: u64, wait_start: Option, paused: PausedState, } static POMODORO_CYCLES: u64 = 5; static WORK_TIME: u64 = 2; static SBREAK_TIME: u64 = 2; static LBREAK_TIME: u64 = 2; static POLLING_RATE: Duration = Duration::from_millis(300); impl Pomodoro { pub fn new() -> Self { Self::default() } pub fn start_with_alert(&mut self, alert: Alert) { loop { if let Status::Complete = self.poll(Some(&alert)) { break; } Self::sleep(); } } pub fn start(&mut self) { loop { if let Status::Complete = self.poll(None) { break; } Self::sleep() } } fn sleep() { thread::sleep(POLLING_RATE); } fn notify_complete(&self) -> Result<(), Box> { let mut toast = notify_rust::Notification::new(); toast.summary("Pomodoro cycle complete!"); toast.body("domasi will now exit."); toast.show()?; Ok(()) } fn notify(&self) -> Result<(), Box> { fn pmdr_cycle_count(i: u64) -> String { let i = i / 4; match i { 1 => "After this, you will have completed 1 cycle.".to_string(), _ => format!("After this, you will have completed {} cycles.", i), } } fn work_sesh_num(i: u64) -> &'static str { match i % 4 { 0 => "Time for your 1st work session.", 1 => "Time for your 2nd work session.", 2 => "Time for your 3rd work session.", 3 => "Time for your 4th work session.", _ => unreachable!(), } } let mut toast = notify_rust::Notification::new(); match self.state { State::Inactive => {} State::Work => { toast .summary(work_sesh_num(self.count)) .body("Remember to stay focused.") .show()?; } State::ShortBreak => { toast .summary("Time for a quick break.") .body("Sit back and relax.") .show()?; } State::LongBreak => { toast .summary("Enjoy your long break!") .body(&pmdr_cycle_count(self.count)) .show()?; } }; Ok(()) } fn poll(&mut self, maybe_alert: Option<&Alert>) -> Status { let status = self.check(); match status { Status::Paused => { assert!(self.paused.value); let now = Instant::now(); self.paused.duration += match self.paused.previous { Some(earlier) => now - earlier, None => Default::default(), }; self.paused.previous = Some(now); } Status::Active => self.display_time(), Status::NextState => { let (update_count, new_state) = self.next(); if let Count::Increase = update_count { self.count += 1; } self.state = new_state; self.wait_start = match new_state { State::Inactive => None, State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()), }; if let Some(alert) = maybe_alert { let _ = alert.play(); } let _ = self.notify(); self.display_time(); } Status::Complete => { println!("\rPomodoro cycle complete!"); let _ = self.notify_complete(); } Status::Inactive => {} }; status } fn display_time(&self) { if let Some(earlier) = self.wait_start { let wait = Instant::now() - earlier; let left: Clock = (Self::wait_times(self.state) - wait).into(); let _ = Self::print(&format!("\r{} {}", self.state, left)); } } fn print(text: &str) -> io::Result<()> { let out = io::stdout(); let mut handle = out.lock(); // Empty String so that we can clear line before writing // from the mostleft side of the terminal again. handle.write_all(b"\r ")?; handle.write_all(text.as_bytes())?; handle.flush()?; Ok(()) } fn next(&self) -> (Count, State) { match self.state { State::Work => { let state: State = if (self.count + 1) % 4 == 0 { State::LongBreak } else { State::ShortBreak }; (Count::Increase, state) } State::ShortBreak | State::Inactive => (Count::Remain, State::Work), State::LongBreak => (Count::Remain, State::Inactive), } } fn check(&self) -> Status { if self.paused.value { return Status::Paused; } match self.wait_start { Some(earlier) => { let diff: Duration = (Instant::now() - self.paused.duration) - earlier; if diff > Self::wait_times(self.state) { Status::NextState } else { Status::Active } } None => { if self.count / 4 == POMODORO_CYCLES { Status::Complete } else { Status::NextState } } } } fn wait_times(state: State) -> Duration { match state { State::Work => Duration::from_secs(WORK_TIME), State::ShortBreak => Duration::from_secs(SBREAK_TIME), State::LongBreak => Duration::from_secs(LBREAK_TIME), _ => unreachable!("Can not have Pomodoro state = State::Inactive and wait_start = Some(...) at the same time.") } } } struct Clock { hours: u64, minutes: u64, seconds: u64, } impl From for Clock { fn from(dur: Duration) -> Self { let dur = dur.as_secs(); let hours = dur / 3600; let minutes = (dur - (hours * 3600)) / 60; let seconds = dur - (hours * 3600) - (minutes * 60); Self { hours, minutes, seconds, } } } impl Display for Clock { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if self.hours > 0 { // No need to print the hours if there are none. if self.hours < 10 { write!(f, "0{}:", self.hours)?; } else { write!(f, "{}:", self.hours)?; } } if self.minutes < 10 { write!(f, "0{}:", self.minutes)?; } else { write!(f, "{}:", self.minutes)?; } if self.seconds < 10 { write!(f, "0{}", self.seconds) } else { write!(f, "{}", self.seconds) } } } #[derive(Debug, Copy, Clone, Default)] struct PausedState { value: bool, duration: Duration, previous: Option, } #[derive(Debug, Copy, Clone)] enum Count { Increase, Remain, } #[derive(Debug, Copy, Clone)] enum Status { Paused, Active, NextState, Inactive, Complete, } #[derive(Debug, Copy, Clone)] enum State { Inactive, Work, ShortBreak, LongBreak, } impl Default for State { fn default() -> Self { State::Inactive } } impl Display for State { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match *self { State::Work => f.write_str("Working: "), State::ShortBreak => f.write_str("Resting: "), State::LongBreak => f.write_str("REALLY Resting: "), State::Inactive => f.write_str("Inactive: "), } } }