use clap::{App, Arg, ArgMatches, SubCommand}; use crossbeam::channel::{unbounded, Receiver, Sender}; use crossterm::{ event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode}, }; use domasi::pomodoro::{Alert, Remaining, Status}; use domasi::{Config, Pomodoro}; use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::thread; fn main() { let matches = App::new("domasi") .version("0.1.0") .author("Rekai Musuka ") .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_config_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) = unbounded(); // UI Thread thread::spawn(move || loop { if let Ok(status) = rx.recv() { loop { let (text, remain) = match Remaining::from_status(status) { Some(seconds) => (seconds.to_string(), seconds.remaining_secs()), None => ("??? Status: ??:??".to_string(), 0), }; let out = io::stdout(); let mut handle = out.lock(); handle.write_all(text.as_bytes()).unwrap(); handle.flush().unwrap(); // TODO: Make sure this isn't an issue. if remain < 1 { break; } thread::sleep(Remaining::polling_interval()); } } }); // User Input Thread thread::spawn(|| setup_user_input().unwrap()); 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)); } None => pomodoro.start(config, tx, None), } } fn get_audio_file(items: std::fs::ReadDir) -> Option { for maybe_entry in items { if let Ok(entry) = maybe_entry { if let Some(ext) = entry.path().extension() { if let Some("mp3") | Some("wav") | Some("ogg") | Some("flac") = ext.to_str() { return Some(entry.path()); } } } } None } fn setup_user_input() -> crossterm::Result<()> { enable_raw_mode()?; get_user_input()?; disable_raw_mode() } fn get_user_input() -> crossterm::Result<()> { loop { if poll(Remaining::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); }