use super::Config; use crossbeam::channel::Sender; use rodio::{Decoder, Device, Source}; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fs::File; use std::io::BufReader; use std::path::Path; use std::time::{Duration, Instant}; pub struct Alert<'a> { path: &'a Path, device: &'a Device, } impl<'a> Alert<'a> { pub fn new>(path: &'a P, device: &'a Device) -> Alert<'a> { Alert { path: path.as_ref(), device, } } pub fn play(&self) { if self.path.exists() { let file = File::open(self.path).unwrap(); let source = Decoder::new(BufReader::new(file)).unwrap(); rodio::play_raw(&self.device, source.convert_samples()); } } pub fn load>(mut self, new_path: &'a P) -> Self { self.path = new_path.as_ref(); self } } #[derive(Copy, Clone, Debug)] pub enum State { Work, ShortBreak, LongBreak, Inactive, } impl std::fmt::Display for State { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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: "), } } } #[derive(Copy, Clone, Debug)] pub struct Pomodoro { count: u64, state: State, } impl Default for Pomodoro { fn default() -> Self { Pomodoro { count: 0, state: State::Inactive, } } } impl<'a> Pomodoro { pub fn start(&mut self, config: Config, tx: Sender, maybe_alert: Option<&Alert<'a>>) { loop { self.next(); if let Some(alert) = maybe_alert { alert.play(); } let _ = Self::notify(&self.state); match self.state { State::Work => { Self::send_to_clock(&tx, self.state, config.work_time); Self::wait(config.work_time); } State::ShortBreak => { Self::send_to_clock(&tx, self.state, config.short_break); Self::wait(config.short_break); } State::LongBreak => { Self::send_to_clock(&tx, self.state, config.long_break); Self::wait(config.long_break); } State::Inactive => { println!("Pomodoro Cycle is complete"); break; } } } } } impl Pomodoro { pub fn new() -> Pomodoro { Pomodoro { count: 0, state: State::Inactive, } } fn next(&mut self) { match self.state { State::Work => { self.count += 1; if (self.count % 4) == 0 { self.state = State::LongBreak; } else { self.state = State::ShortBreak; } } State::LongBreak => self.state = State::Inactive, State::ShortBreak => self.state = State::Work, State::Inactive => self.state = State::Work, } } fn wait(duration: Duration) { std::thread::sleep(duration); } fn send_to_clock(tx: &Sender, state: State, length: Duration) { let status = Status { start: Instant::now(), length, state, }; tx.send(status).unwrap(); } fn notify(state: &State) -> Result<(), anyhow::Error> { let mut toast = notify_rust::Notification::new(); match state { State::Work => toast .summary("Time to Work!") .body("Remember to stay focused!") .show()?, State::ShortBreak | State::LongBreak => toast .summary("Break Time!") .body("Enjoy your well deserved rest!") .show()?, State::Inactive => toast .summary("Pomodoro Cycle Complete.") .body("Now waiting for user input....") .show()?, }; Ok(()) } } #[derive(Copy, Clone, Debug)] pub struct Status { pub start: Instant, pub length: Duration, pub state: State, } pub struct Remaining { state: State, remaining: u64, seconds: u64, hours: u64, minutes: u64, } impl Remaining { pub fn from_status(status: Status) -> Option { let now = Instant::now(); let maybe_elapsed = now.checked_duration_since(status.start); match maybe_elapsed { Some(duration) => { let remaining = status.length.as_secs() - duration.as_secs(); let hours = remaining / 3600; let minutes = (remaining - (hours * 3600)) / 60; let seconds = remaining - (hours * 3600) - (minutes * 60); Some(Self { state: status.state, remaining, hours, minutes, seconds, }) } None => None, } } pub fn remaining_secs(&self) -> u64 { self.remaining } pub fn polling_interval() -> Duration { Duration::from_millis(500) } } impl Display for Remaining { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.write_str("\r")?; write!(f, "{}", self.state)?; 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) } } }