2020-08-31 05:18:39 +00:00
|
|
|
use super::Alert;
|
2020-08-31 03:15:51 +00:00
|
|
|
use std::error::Error;
|
2020-08-24 03:27:49 +00:00
|
|
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
2020-08-31 01:22:27 +00:00
|
|
|
use std::io::{self, Write};
|
|
|
|
use std::thread;
|
2020-06-21 21:44:54 +00:00
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
#[derive(Debug, Copy, Clone, Default)]
|
|
|
|
pub struct Pomodoro {
|
|
|
|
state: State,
|
|
|
|
count: u64,
|
|
|
|
wait_start: Option<Instant>,
|
|
|
|
paused: PausedState,
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 03:15:51 +00:00
|
|
|
static POMODORO_CYCLES: u64 = 5;
|
2020-08-31 01:22:27 +00:00
|
|
|
static WORK_TIME: u64 = 2;
|
|
|
|
static SBREAK_TIME: u64 = 2;
|
|
|
|
static LBREAK_TIME: u64 = 2;
|
|
|
|
|
2020-08-31 03:15:51 +00:00
|
|
|
static POLLING_RATE: Duration = Duration::from_millis(300);
|
2020-08-31 01:22:27 +00:00
|
|
|
impl Pomodoro {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 05:18:39 +00:00
|
|
|
pub fn start_with_alert(&mut self, alert: Alert) {
|
|
|
|
loop {
|
|
|
|
if let Status::Complete = self.poll(Some(&alert)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::sleep();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
pub fn start(&mut self) {
|
|
|
|
loop {
|
2020-08-31 05:18:39 +00:00
|
|
|
if let Status::Complete = self.poll(None) {
|
2020-08-31 01:22:27 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-08-31 05:18:39 +00:00
|
|
|
|
|
|
|
Self::sleep()
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 05:18:39 +00:00
|
|
|
fn sleep() {
|
|
|
|
thread::sleep(POLLING_RATE);
|
|
|
|
}
|
|
|
|
|
2020-08-31 03:15:51 +00:00
|
|
|
fn notify_complete(&self) -> Result<(), Box<dyn Error>> {
|
|
|
|
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<dyn Error>> {
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2020-08-31 05:18:39 +00:00
|
|
|
fn poll(&mut self, maybe_alert: Option<&Alert>) -> Status {
|
2020-08-31 01:22:27 +00:00
|
|
|
let status = self.check();
|
2020-08-24 03:27:49 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
match status {
|
|
|
|
Status::Paused => {
|
|
|
|
assert!(self.paused.value);
|
|
|
|
let now = Instant::now();
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
self.paused.duration += match self.paused.previous {
|
|
|
|
Some(earlier) => now - earlier,
|
|
|
|
None => Default::default(),
|
|
|
|
};
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
self.paused.previous = Some(now);
|
|
|
|
}
|
|
|
|
Status::Active => self.display_time(),
|
|
|
|
Status::NextState => {
|
|
|
|
let (update_count, new_state) = self.next();
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
if let Count::Increase = update_count {
|
|
|
|
self.count += 1;
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
self.state = new_state;
|
|
|
|
self.wait_start = match new_state {
|
|
|
|
State::Inactive => None,
|
|
|
|
State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()),
|
|
|
|
};
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 05:18:39 +00:00
|
|
|
if let Some(alert) = maybe_alert {
|
|
|
|
let _ = alert.play();
|
|
|
|
}
|
|
|
|
|
2020-08-31 03:15:51 +00:00
|
|
|
let _ = self.notify();
|
2020-08-31 01:22:27 +00:00
|
|
|
self.display_time();
|
|
|
|
}
|
|
|
|
Status::Complete => {
|
2020-08-31 03:15:51 +00:00
|
|
|
println!("\rPomodoro cycle complete!");
|
|
|
|
|
|
|
|
let _ = self.notify_complete();
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
2020-08-31 01:22:27 +00:00
|
|
|
Status::Inactive => {}
|
|
|
|
};
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
status
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
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));
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
fn print(text: &str) -> io::Result<()> {
|
|
|
|
let out = io::stdout();
|
|
|
|
let mut handle = out.lock();
|
2020-08-31 03:15:51 +00:00
|
|
|
|
|
|
|
// Empty String so that we can clear line before writing
|
|
|
|
// from the mostleft side of the terminal again.
|
|
|
|
handle.write_all(b"\r ")?;
|
2020-08-31 01:22:27 +00:00
|
|
|
handle.write_all(text.as_bytes())?;
|
|
|
|
handle.flush()?;
|
|
|
|
Ok(())
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
fn next(&self) -> (Count, State) {
|
2020-06-21 21:44:54 +00:00
|
|
|
match self.state {
|
|
|
|
State::Work => {
|
2020-08-31 01:22:27 +00:00
|
|
|
let state: State = if (self.count + 1) % 4 == 0 {
|
|
|
|
State::LongBreak
|
2020-06-21 21:44:54 +00:00
|
|
|
} else {
|
2020-08-31 01:22:27 +00:00
|
|
|
State::ShortBreak
|
|
|
|
};
|
|
|
|
|
|
|
|
(Count::Increase, state)
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
2020-08-31 01:22:27 +00:00
|
|
|
State::ShortBreak | State::Inactive => (Count::Remain, State::Work),
|
|
|
|
State::LongBreak => (Count::Remain, State::Inactive),
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
fn check(&self) -> Status {
|
|
|
|
if self.paused.value {
|
|
|
|
return Status::Paused;
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
match self.wait_start {
|
|
|
|
Some(earlier) => {
|
|
|
|
let diff: Duration = (Instant::now() - self.paused.duration) - earlier;
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
if diff > Self::wait_times(self.state) {
|
|
|
|
Status::NextState
|
|
|
|
} else {
|
|
|
|
Status::Active
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
2020-08-31 03:15:51 +00:00
|
|
|
if self.count / 4 == POMODORO_CYCLES {
|
2020-08-31 01:22:27 +00:00
|
|
|
Status::Complete
|
2020-08-31 03:15:51 +00:00
|
|
|
} else {
|
|
|
|
Status::NextState
|
2020-08-31 01:22:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
fn wait_times(state: State) -> Duration {
|
2020-06-21 21:44:54 +00:00
|
|
|
match state {
|
2020-08-31 01:22:27 +00:00
|
|
|
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.")
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
struct Clock {
|
2020-08-24 03:27:49 +00:00
|
|
|
hours: u64,
|
|
|
|
minutes: u64,
|
2020-08-31 01:22:27 +00:00
|
|
|
seconds: u64,
|
2020-08-24 03:27:49 +00:00
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
impl From<Duration> for Clock {
|
|
|
|
fn from(dur: Duration) -> Self {
|
|
|
|
let dur = dur.as_secs();
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
let hours = dur / 3600;
|
|
|
|
let minutes = (dur - (hours * 3600)) / 60;
|
|
|
|
let seconds = dur - (hours * 3600) - (minutes * 60);
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
Self {
|
|
|
|
hours,
|
|
|
|
minutes,
|
|
|
|
seconds,
|
|
|
|
}
|
2020-08-24 03:27:49 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-21 21:44:54 +00:00
|
|
|
|
2020-08-31 01:22:27 +00:00
|
|
|
impl Display for Clock {
|
2020-08-24 03:27:49 +00:00
|
|
|
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)?;
|
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
|
|
|
}
|
|
|
|
}
|
2020-08-31 01:22:27 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, Default)]
|
|
|
|
struct PausedState {
|
|
|
|
value: bool,
|
|
|
|
duration: Duration,
|
|
|
|
previous: Option<Instant>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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: "),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|