use async_std::task; use clap::{App, Arg, ArgMatches, SubCommand}; use crossterm::{ cursor, event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, QueueableCommand, }; use domasi::pomodoro::{Alert, Clock, Status}; use domasi::{Config, Pomodoro}; use std::fs; use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; fn main() { let matches = App::new("Domasi") .version("0.1.0") .author("paoda ") .about("Yet another pomodoro timer.") .arg( Arg::with_name("create-config") .short("C") .long("create-config") .help("Creates a Settings.toml and an alert directory"), ) .subcommand( SubCommand::with_name("start") .about("Start the Pomodoro Timer") .arg( Arg::with_name("alert") .short("a") .long("alert") .value_name("FILE") .takes_value(true) .help("Aloud Sound. (Supports WAV, MP3, Vorbis, FLAC)"), ), ) .get_matches(); // match matches.subcommand() { // ("start", Some(sub_matches)) => start(sub_matches), // _ => {} // } if matches.is_present("create-config") { create_config() } if let ("start", Some(sub_matches)) = matches.subcommand() { start(sub_matches); } } pub fn create_config() { let config_dir = Config::get_data_directory(); let data_dir = Config::get_data_directory().join("alert"); if !config_dir.exists() { fs::create_dir_all(&config_dir).unwrap_or_else(|err| { panic!("Failed to create {}: {}", config_dir.to_string_lossy(), err) }) } if !data_dir.exists() { fs::create_dir_all(&data_dir).unwrap_or_else(|err| { panic!("Failed to create {}: {}", data_dir.to_string_lossy(), err) }); } let config: Config = Default::default(); Config::save(&config).unwrap_or_else(|err| { let cfg_path = config_dir.to_string_lossy(); panic!("Error while writing settings.toml to {}: {}", cfg_path, err); }); let data_path = data_dir.to_string_lossy(); let settings_path = config_dir.join("settings.toml"); let settings_path = settings_path.to_string_lossy(); println!( "Successfully created \"{}\" and \"{}\"", settings_path, data_path ); } pub fn start(args: &ArgMatches) { let mut pomodoro = Pomodoro::new(); let (tx, rx): (Sender, Receiver) = channel(); // UI Thread thread::spawn(move || loop { if let Ok(status) = rx.recv() { loop { let (remain, text) = Clock::get_formatted_string(status); print_overwrite(&text); // Make this check better pls if remain < 1 { break; } thread::sleep(Clock::get_polling_interval()); } } }); // User Input Thread thread::spawn(|| setup_user_input().unwrap()); // Async Pomodoro task::block_on(async { let config = { match Config::load() { Some(cfg) => cfg, None => Default::default(), } }; let maybe_audio: Option; match args.value_of("alert") { Some(path) => maybe_audio = Some(Path::new(path).to_path_buf()), None => { match &config.sound_file { Some(path) => maybe_audio = Some(path.clone()), None => { // Look in the default locations // check for .mp3, .wav, .ogg, .flac let data_dir = Config::get_data_directory().join("alert"); let data_dir_str = data_dir.to_string_lossy(); let items = fs::read_dir(&data_dir).unwrap_or_else(|_err| { panic!("Unable to read the contents of {}", data_dir_str); }); maybe_audio = get_audio_file(items); } } } } match maybe_audio { Some(audio_path) => { let default_device = rodio::default_output_device().unwrap(); let alert = Alert::new(&audio_path, &default_device); pomodoro.start(config, tx, Some(&alert)).await; } None => pomodoro.start(config, tx, None).await, } }); } fn get_audio_file(items: std::fs::ReadDir) -> Option { let mut result: Option = None; for maybe_entry in items { match maybe_entry { Ok(entry) => match entry.path().extension() { Some(ext) => match ext.to_str() { Some("mp3") | Some("wav") | Some("ogg") | Some("flac") => { result = Some(entry.path()); break; } _ => continue, }, None => continue, }, Err(_err) => continue, } } result } fn setup_user_input() -> crossterm::Result<()> { enable_raw_mode()?; get_user_input()?; disable_raw_mode() } 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(); } fn get_user_input() -> crossterm::Result<()> { loop { if poll(Clock::get_polling_interval())? { if let Event::Key(event) = read()? { handle_key_event(event); } } } } fn handle_key_event(event: KeyEvent) { match event.code { KeyCode::Char('c') => { if let KeyModifiers::CONTROL = event.modifiers { exit_domasi(0) } } KeyCode::Esc | KeyCode::Char('q') => exit_domasi(0), _ => {} } } fn exit_domasi(code: i32) { disable_raw_mode().unwrap(); std::process::exit(code); }