2020-06-21 21:44:54 +00:00
|
|
|
use super::Config;
|
2020-08-20 01:57:54 +00:00
|
|
|
use crossbeam::channel::Sender;
|
2020-06-21 21:44:54 +00:00
|
|
|
use rodio::{Decoder, Device, Source};
|
2020-08-24 03:27:49 +00:00
|
|
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
2020-06-21 21:44:54 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 03:27:49 +00:00
|
|
|
|
2020-06-21 21:44:54 +00:00
|
|
|
#[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 {
|
2020-08-20 01:57:54 +00:00
|
|
|
pub fn start(&mut self, config: Config, tx: Sender<Status>, maybe_alert: Option<&Alert<'a>>) {
|
2020-06-21 21:44:54 +00:00
|
|
|
loop {
|
|
|
|
self.next();
|
|
|
|
|
|
|
|
if let Some(alert) = maybe_alert {
|
|
|
|
alert.play();
|
|
|
|
}
|
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
let _ = Self::notify(&self.state);
|
2020-06-21 21:44:54 +00:00
|
|
|
|
|
|
|
match self.state {
|
|
|
|
State::Work => {
|
|
|
|
Self::send_to_clock(&tx, self.state, config.work_time);
|
2020-08-20 01:57:54 +00:00
|
|
|
Self::wait(config.work_time);
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
State::ShortBreak => {
|
|
|
|
Self::send_to_clock(&tx, self.state, config.short_break);
|
2020-08-20 01:57:54 +00:00
|
|
|
Self::wait(config.short_break);
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
State::LongBreak => {
|
|
|
|
Self::send_to_clock(&tx, self.state, config.long_break);
|
2020-08-20 01:57:54 +00:00
|
|
|
Self::wait(config.long_break);
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-20 01:57:54 +00:00
|
|
|
fn wait(duration: Duration) {
|
|
|
|
std::thread::sleep(duration);
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn send_to_clock(tx: &Sender<Status>, state: State, length: Duration) {
|
|
|
|
let status = Status {
|
|
|
|
start: Instant::now(),
|
|
|
|
length,
|
|
|
|
state,
|
|
|
|
};
|
|
|
|
|
|
|
|
tx.send(status).unwrap();
|
|
|
|
}
|
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
fn notify(state: &State) -> Result<(), anyhow::Error> {
|
2020-06-21 21:44:54 +00:00
|
|
|
let mut toast = notify_rust::Notification::new();
|
|
|
|
match state {
|
2020-08-24 03:27:49 +00:00
|
|
|
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(())
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
pub struct Status {
|
|
|
|
pub start: Instant,
|
|
|
|
pub length: Duration,
|
|
|
|
pub state: State,
|
|
|
|
}
|
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
pub struct Remaining {
|
|
|
|
state: State,
|
|
|
|
remaining: u64,
|
|
|
|
seconds: u64,
|
|
|
|
hours: u64,
|
|
|
|
minutes: u64,
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
impl Remaining {
|
|
|
|
pub fn from_status(status: Status) -> Option<Self> {
|
2020-06-21 21:44:54 +00:00
|
|
|
let now = Instant::now();
|
2020-08-24 03:27:49 +00:00
|
|
|
let maybe_elapsed = now.checked_duration_since(status.start);
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
match maybe_elapsed {
|
2020-06-21 21:44:54 +00:00
|
|
|
Some(duration) => {
|
|
|
|
let remaining = status.length.as_secs() - duration.as_secs();
|
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
pub fn remaining_secs(&self) -> u64 {
|
|
|
|
self.remaining
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
pub fn polling_interval() -> Duration {
|
|
|
|
Duration::from_millis(500)
|
|
|
|
}
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
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)?;
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-24 03:27:49 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|