diff --git a/src/alert.rs b/src/alert.rs index 0f355e9..b908831 100644 --- a/src/alert.rs +++ b/src/alert.rs @@ -1,8 +1,9 @@ -use rodio::{Decoder, OutputStream, Sink}; +use rodio::{decoder::DecoderError, Decoder, OutputStream, PlayError, Sink, StreamError}; use std::fs::File; use std::io::BufReader; use std::path::PathBuf; use std::thread; +use std::{error::Error, fmt::Display, fmt::Formatter, fmt::Result as FmtResult}; #[derive(Debug, Clone)] pub struct Alert { @@ -18,15 +19,38 @@ impl Alert { self.path = path; } - pub fn play(&self) { + // pub fn play(&self) -> Result<(), Box> { + // let file = File::open(&self.path).unwrap(); + + // thread::Builder::new() + // .name("Audio Thread".to_string()) + // .spawn(|| -> Result<(), Error> { + // let (_stream, handle) = OutputStream::try_default()?; + // let source = Decoder::new(BufReader::new(file))?; + // let sink = Sink::try_new(&handle)?; + + // sink.append(source); + + // loop { + // if sink.len() == 0 { + // break; + // } + // } + + // Ok(()) + // })?; + // Ok(()) + // } + + pub fn play(&self) -> Result<(), Box> { let file = File::open(&self.path).unwrap(); thread::Builder::new() - .name("Audio Thread".to_string()) - .spawn(move || { - let (_stream, handle) = OutputStream::try_default().unwrap(); - let source = Decoder::new(BufReader::new(file)).unwrap(); - let sink = Sink::try_new(&handle).unwrap(); + .name("Alert Playback Thread".to_string()) + .spawn(move || -> Result<(), PlaybackError> { + let (_stream, handle) = OutputStream::try_default()?; + let source = Decoder::new(BufReader::new(file))?; + let sink = Sink::try_new(&handle)?; sink.append(source); @@ -35,7 +59,55 @@ impl Alert { break; } } - }) - .unwrap(); + + Ok(()) + })?; + + Ok(()) + } +} + +#[derive(Debug)] +enum PlaybackError { + StreamError(StreamError), + DecoderError(DecoderError), + SinkError(PlayError), +} + +impl From for PlaybackError { + fn from(err: DecoderError) -> Self { + Self::DecoderError(err) + } +} + +impl From for PlaybackError { + fn from(err: StreamError) -> Self { + Self::StreamError(err) + } +} + +impl From for PlaybackError { + fn from(err: PlayError) -> Self { + Self::SinkError(err) + } +} + +impl Error for PlaybackError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self { + Self::StreamError(err) => Some(err), + Self::DecoderError(err) => Some(err), + Self::SinkError(err) => Some(err), + } + } +} + +impl Display for PlaybackError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match &self { + Self::StreamError(err) => write!(f, "{}", err), + Self::DecoderError(err) => write!(f, "{}", err), + Self::SinkError(err) => write!(f, "{}", err), + } } } diff --git a/src/domasi.rs b/src/domasi.rs index 6b135f8..b187561 100644 --- a/src/domasi.rs +++ b/src/domasi.rs @@ -11,7 +11,7 @@ static SHORT_BREAK_TIME: u64 = 300; // Default: 300 (5min) static LONG_BREAK_TIME: u64 = 600; // Default: 600 (10min) static POLLING_RATE: Duration = Duration::from_millis(100); -#[derive(Debug, Copy, Clone)] +#[derive(Debug)] pub struct Domasi { state: State, count: u64, @@ -32,7 +32,7 @@ impl Default for Domasi { impl Domasi { pub fn new() -> Self { - Self::default() + Default::default() } pub fn start(&mut self, maybe_alert: Option) -> crossterm::Result<()> { @@ -67,18 +67,11 @@ impl Domasi { self.paused.toggle(); if !self.paused.value { - // Apply the elapsed time - let duration = self.paused.duration; - match self.wait_start { - Some(instant) => self.wait_start = Some(instant + duration), - None => { - unreachable!("Unable to resume from a non-existent timer.") - } - } - + self.apply_paused_time(); self.paused = Default::default(); } } + KeyCode::Char('s') | KeyCode::Char('S') => self.skip(maybe_alert.as_ref()), KeyCode::Char('r') | KeyCode::Char('R') => self.restart(), KeyCode::Char('q') | KeyCode::Char('Q') => break, KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => break, @@ -92,6 +85,44 @@ impl Domasi { Ok(()) } + fn apply_paused_time(&mut self) { + let duration = self.paused.duration; + match self.wait_start { + Some(instant) => self.wait_start = Some(instant + duration), + None => unreachable!("Unable to resume from a non-existent timer."), + } + } + + fn skip(&mut self, maybe_alert: Option<&Alert>) { + if self.paused.value { + self.paused = Default::default(); + } + + self.next(); + + if let Some(alert) = maybe_alert { + let _ = alert.play(); + } + + let _ = self.notify(); + self.display_time() + } + + fn next(&mut self) { + let (do_update_count, next_state) = self.get_next(); + + if let Count::Increase = do_update_count { + self.count += 1; + } + + self.wait_start = match next_state { + State::UserWait => None, + State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()), + State::Start => unreachable!("Domasi#next should never return State::Start"), + }; + self.state = next_state; + } + fn restart(&mut self) { self.wait_start = Some(Instant::now()); self.paused = Default::default(); @@ -169,21 +200,10 @@ impl Domasi { } 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::UserWait => None, - State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()), - State::Start => unreachable!("Domasi#next should never return State::Start"), - }; + self.next(); if let Some(alert) = maybe_alert { - alert.play() + let _ = alert.play(); } let _ = self.notify(); @@ -229,7 +249,7 @@ impl Domasi { Ok(()) } - fn next(&self) -> (Count, State) { + fn get_next(&self) -> (Count, State) { match self.state { State::Work => { let state: State = if (self.count + 1) % 4 == 0 {