Add working clock, and improve appearance of the output
This commit is contained in:
		
							
								
								
									
										161
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								src/lib.rs
									
									
									
									
									
								
							@@ -6,6 +6,8 @@ pub mod pomodoro {
 | 
			
		||||
    use std::fs::{self, File};
 | 
			
		||||
    use std::io::BufReader;
 | 
			
		||||
    use std::path::{Path, PathBuf};
 | 
			
		||||
    use std::sync::mpsc::Sender;
 | 
			
		||||
    use std::time::{Duration, Instant};
 | 
			
		||||
 | 
			
		||||
    #[derive(Copy, Clone)]
 | 
			
		||||
    pub struct Alert<'a> {
 | 
			
		||||
@@ -21,10 +23,6 @@ pub mod pomodoro {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn load<'a, 'b, P: AsRef<Path>>(&'a mut self, _path: &'b P) {
 | 
			
		||||
            unimplemented!()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn play(&self) {
 | 
			
		||||
            let file = File::open(self.path).unwrap();
 | 
			
		||||
            let source = Decoder::new(BufReader::new(file)).unwrap();
 | 
			
		||||
@@ -33,7 +31,7 @@ pub mod pomodoro {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Eq)]
 | 
			
		||||
    #[derive(Copy, Clone, Debug)]
 | 
			
		||||
    pub enum State {
 | 
			
		||||
        Work,
 | 
			
		||||
        ShortBreak,
 | 
			
		||||
@@ -41,32 +39,58 @@ pub mod pomodoro {
 | 
			
		||||
        Inactive,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub struct Config {
 | 
			
		||||
        pub work_time: u64,
 | 
			
		||||
        pub short_break: u64,
 | 
			
		||||
        pub long_break: u64,
 | 
			
		||||
        pub data_directory: PathBuf,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Config {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            let dirs = ProjectDirs::from("moe", "paoda", "domasi").unwrap();
 | 
			
		||||
 | 
			
		||||
            let data_directory = dirs.data_dir().to_owned();
 | 
			
		||||
 | 
			
		||||
            if !data_directory.exists() {
 | 
			
		||||
                fs::create_dir_all(&data_directory).unwrap();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Config {
 | 
			
		||||
                work_time: 1,
 | 
			
		||||
                short_break: 1,
 | 
			
		||||
                long_break: 1,
 | 
			
		||||
                data_directory,
 | 
			
		||||
    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: "),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub struct Config {
 | 
			
		||||
        pub work_time: Duration,
 | 
			
		||||
        pub short_break: Duration,
 | 
			
		||||
        pub long_break: Duration,
 | 
			
		||||
        pub data_directory: PathBuf,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Config {
 | 
			
		||||
        pub fn new(work: u64, short_break: u64, long_break: u64) -> Config {
 | 
			
		||||
            let data_directory = Self::get_data_directory();
 | 
			
		||||
            if !data_directory.exists() {
 | 
			
		||||
                fs::create_dir_all(&data_directory).unwrap();
 | 
			
		||||
            }
 | 
			
		||||
            let seconds_in_minutes = 60;
 | 
			
		||||
 | 
			
		||||
            Config {
 | 
			
		||||
                work_time: Duration::from_secs(work * seconds_in_minutes),
 | 
			
		||||
                short_break: Duration::from_secs(short_break * seconds_in_minutes),
 | 
			
		||||
                long_break: Duration::from_secs(long_break * seconds_in_minutes),
 | 
			
		||||
                data_directory,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn with_data_directory<P: AsRef<Path>>(mut self, path: &P) -> Config {
 | 
			
		||||
            self.data_directory = path.as_ref().to_owned();
 | 
			
		||||
            self
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fn get_data_directory() -> PathBuf {
 | 
			
		||||
            let dirs = ProjectDirs::from("moe", "paoda", "domasi").unwrap();
 | 
			
		||||
            dirs.data_dir().to_owned()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Config {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Config::new(20, 5, 10)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Copy, Clone)]
 | 
			
		||||
    pub struct Pomodoro<'a> {
 | 
			
		||||
        count: u64,
 | 
			
		||||
        state: State,
 | 
			
		||||
@@ -99,30 +123,26 @@ pub mod pomodoro {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async fn wait(minutes: u64) {
 | 
			
		||||
            use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
            let duration = Duration::from_secs(minutes * 5);
 | 
			
		||||
        async fn wait(duration: Duration) {
 | 
			
		||||
            async_std::task::sleep(duration).await;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub async fn start(&mut self, config: Config) {
 | 
			
		||||
        pub async fn start(&mut self, config: Config, tx: Sender<Status>) {
 | 
			
		||||
            loop {
 | 
			
		||||
                self.next();
 | 
			
		||||
 | 
			
		||||
                self.alert.play();
 | 
			
		||||
 | 
			
		||||
                match self.state {
 | 
			
		||||
                    State::Work => {
 | 
			
		||||
                        println!("Start Work.");
 | 
			
		||||
                        Self::send_to_clock(&tx, self.state, config.work_time);
 | 
			
		||||
                        Self::wait(config.work_time).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    State::ShortBreak => {
 | 
			
		||||
                        println!("Start Short Break.");
 | 
			
		||||
                        Self::send_to_clock(&tx, self.state, config.short_break);
 | 
			
		||||
                        Self::wait(config.short_break).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    State::LongBreak => {
 | 
			
		||||
                        println!("Start Long Break.");
 | 
			
		||||
                        Self::send_to_clock(&tx, self.state, config.long_break);
 | 
			
		||||
                        Self::wait(config.long_break).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    State::Inactive => {
 | 
			
		||||
@@ -133,8 +153,73 @@ pub mod pomodoro {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn completed(&self) -> u64 {
 | 
			
		||||
            self.count / 4
 | 
			
		||||
        fn send_to_clock(tx: &Sender<Status>, state: State, length: Duration) {
 | 
			
		||||
            let status = Status {
 | 
			
		||||
                start: Instant::now(),
 | 
			
		||||
                length,
 | 
			
		||||
                state,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            tx.send(status).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() * 60) - 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 = String::new();
 | 
			
		||||
 | 
			
		||||
                    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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,7 +1,12 @@
 | 
			
		||||
use async_std::task;
 | 
			
		||||
use clap::{App, ArgMatches, SubCommand};
 | 
			
		||||
use domasi::pomodoro::{Alert, Config};
 | 
			
		||||
use crossterm::{cursor, terminal::Clear, terminal::ClearType, QueueableCommand};
 | 
			
		||||
use domasi::pomodoro::{Alert, Clock, Config, Status};
 | 
			
		||||
use domasi::Pomodoro;
 | 
			
		||||
use std::io::{stdout, Write};
 | 
			
		||||
 | 
			
		||||
use std::sync::mpsc::{channel, Receiver, Sender};
 | 
			
		||||
use std::thread;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let matches = App::new("Domasi")
 | 
			
		||||
@@ -11,9 +16,13 @@ fn main() {
 | 
			
		||||
        .subcommand(SubCommand::with_name("start").about("Start the Pomodoro Timer"))
 | 
			
		||||
        .get_matches();
 | 
			
		||||
 | 
			
		||||
    match matches.subcommand() {
 | 
			
		||||
        ("start", Some(sub_matches)) => start(sub_matches),
 | 
			
		||||
        _ => {}
 | 
			
		||||
    // match matches.subcommand() {
 | 
			
		||||
    //     ("start", Some(sub_matches)) => start(sub_matches),
 | 
			
		||||
    //     _ => {}
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    if let ("start", Some(sub_matches)) = matches.subcommand() {
 | 
			
		||||
        start(sub_matches);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +35,34 @@ pub fn start(_args: &ArgMatches) {
 | 
			
		||||
    let mut pomodoro = Pomodoro::new(&alert);
 | 
			
		||||
 | 
			
		||||
    task::block_on(async {
 | 
			
		||||
        pomodoro.start(config).await;
 | 
			
		||||
        let (tx, rx): (Sender<Status>, Receiver<Status>) = channel();
 | 
			
		||||
 | 
			
		||||
        thread::spawn(move || loop {
 | 
			
		||||
            if let Ok(status) = rx.recv() {
 | 
			
		||||
                loop {
 | 
			
		||||
                    let (remaining, string) = Clock::get_formatted_string(status);
 | 
			
		||||
                    print_overwrite(&string);
 | 
			
		||||
 | 
			
		||||
                    // Super fun race condition that you gotta handle better :)
 | 
			
		||||
                    if remaining < 1 {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    thread::sleep(Clock::get_polling_interval());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        pomodoro.start(config, tx).await;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn print_overwrite(text: &str) {
 | 
			
		||||
    let mut stdout = stdout();
 | 
			
		||||
 | 
			
		||||
    stdout.queue(Clear(ClearType::CurrentLine)).unwrap();
 | 
			
		||||
    stdout.queue(cursor::SavePosition).unwrap();
 | 
			
		||||
    stdout.write_all(text.as_bytes()).unwrap();
 | 
			
		||||
    stdout.queue(cursor::RestorePosition).unwrap();
 | 
			
		||||
    stdout.flush().unwrap();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user