228 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use super::Config;
 | |
| use crossbeam::channel::Sender;
 | |
| use rodio::{Decoder, Device, Source};
 | |
| 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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<Status>, maybe_alert: Option<&Alert<'a>>) {
 | |
|         loop {
 | |
|             self.next();
 | |
| 
 | |
|             if let Some(alert) = maybe_alert {
 | |
|                 alert.play();
 | |
|             }
 | |
| 
 | |
|             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<Status>, state: State, length: Duration) {
 | |
|         let status = Status {
 | |
|             start: Instant::now(),
 | |
|             length,
 | |
|             state,
 | |
|         };
 | |
| 
 | |
|         tx.send(status).unwrap();
 | |
|     }
 | |
| 
 | |
|     fn notify(state: &State) {
 | |
|         let mut toast = notify_rust::Notification::new();
 | |
| 
 | |
|         match state {
 | |
|             State::Work => {
 | |
|                 toast
 | |
|                     .summary("Time to Work!")
 | |
|                     .body("Remember to stay focused!")
 | |
|                     .show()
 | |
|                     .unwrap();
 | |
|             }
 | |
|             State::ShortBreak | State::LongBreak => {
 | |
|                 toast
 | |
|                     .summary("Break Time!")
 | |
|                     .body("Enjoy your well deserved rest!")
 | |
|                     .show()
 | |
|                     .unwrap();
 | |
|             }
 | |
|             State::Inactive => {
 | |
|                 toast
 | |
|                     .summary("Pomodoro Cycle Complete.")
 | |
|                     .body("Now waiting for user input....")
 | |
|                     .show()
 | |
|                     .unwrap();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Copy, Clone, Debug)]
 | |
| pub struct Status {
 | |
|     pub start: Instant,
 | |
|     pub length: Duration,
 | |
|     pub state: State,
 | |
| }
 | |
| 
 | |
| pub struct Clock;
 | |
| 
 | |
| impl Clock {
 | |
|     pub fn get_formatted_string(status: Status) -> (u64, String) {
 | |
|         let now = Instant::now();
 | |
|         let elapsed = now.checked_duration_since(status.start);
 | |
| 
 | |
|         match elapsed {
 | |
|             Some(duration) => {
 | |
|                 let remaining = status.length.as_secs() - duration.as_secs();
 | |
| 
 | |
|                 let seconds = remaining;
 | |
|                 let hours = seconds / 3600;
 | |
|                 let minutes = (seconds - (hours * 3600)) / 60;
 | |
|                 let seconds = seconds - (hours * 3600) - (minutes * 60);
 | |
| 
 | |
|                 let mut clock = "\r".to_string();
 | |
| 
 | |
|                 clock.push_str(&format!("{}", status.state));
 | |
| 
 | |
|                 if hours > 0 {
 | |
|                     // We don't want o bother with the hours part if there is none
 | |
|                     if hours < 10 {
 | |
|                         clock.push_str(&format!("0{}:", hours));
 | |
|                     } else {
 | |
|                         clock.push_str(&format!("{}:", hours));
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if minutes < 10 {
 | |
|                     clock.push_str(&format!("0{}:", minutes));
 | |
|                 } else {
 | |
|                     clock.push_str(&format!("{}:", minutes));
 | |
|                 }
 | |
| 
 | |
|                 if seconds < 10 {
 | |
|                     clock.push_str(&format!("0{}", seconds));
 | |
|                 } else {
 | |
|                     clock.push_str(&format!("{}", seconds));
 | |
|                 }
 | |
| 
 | |
|                 (remaining, clock)
 | |
|             }
 | |
|             None => (0, "??:??:??".to_string()), // This will break the loop
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn get_polling_interval() -> Duration {
 | |
|         Duration::from_millis(500)
 | |
|     }
 | |
| }
 |