mod api; mod cmd; mod config; mod errors; mod gpx; use api::{LocationModel, TagModel, API}; use chrono::offset::Local; use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError}; use cmd::*; use config::Config; use errors::{GenericError, MessagedError}; use std::io::prelude::*; use std::sync::mpsc::TryRecvError; use std::time::Duration; fn main() { let env: EnvOpt = argh::from_env(); if let Err(e) = from_env(env) { eprintln!("Critical Error: {}", e); std::process::exit(1); } } fn from_env(env: EnvOpt) -> Result<(), GenericError> { match env.subcommand { Subcommand::Between(opt) => { let config = Config::from_path(&env.config)?; let since = Some(try_get_datetime(opt.since, &config).with_msg("Failed to parse since")?); let until = match opt.until { Some(until) => { Some(try_get_datetime(until, &config).with_msg("Failed to parse until")?) } None => None, }; let (tag, locs) = run(&config, opt.device, since, until)?; dbg!( &locs.iter().map(|loc| loc.0).collect::>(), locs.len(), ); let gpx = gpx::generate_gpx(tag, locs, &config)?; gpx::write_gpx(&gpx)?; Ok(()) } Subcommand::Init(opt) => { let mut config = Config::default(); if let Some(api_key) = opt.api_key { config.api_key = api_key; } config.write_to(&env.config)?; Ok(()) } Subcommand::Nick(opt) => { let mut config = Config::from_path(&env.config)?; let mut api = API::new(config.clone()); let mut tags = api.get_tags()?; tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id)); match opt.subcommand { NickSub::List(_) => { for (idx, tag) in tags.iter().enumerate() { println!( "{}. {}\n{}", idx + 1, tag.get_nick(&config) .unwrap_or("No current nick".to_owned()), tag ); } Ok(()) } NickSub::Set(opt) => { if let Some(tag) = tags.get((opt.index - 1).max(0) as usize) { if let Some(id) = &tag.id { config.nicknames.insert(id.clone(), opt.nickname); config.write_to(&env.config)?; Ok(()) } else { Err(GenericError::MessagedError( format!("Device with index {} does not have an id", opt.index), None, )) } } else { Err(GenericError::MessagedError( format!("Could not find device with index {}", opt.index), None, )) } } } } } } fn run( config: &Config, tag_str: String, from: Option, to: Option, ) -> Result<(TagModel, Vec<(NaiveDateTime, LocationModel)>), GenericError> { let mut api = API::new(config.clone()); let tags = api.get_tags()?; let tag = find_tag(tag_str, &tags, config)?; let tag_id = tag.get_id()?; print!("Preparing..\r"); std::io::stdout().lock().flush().ok(); let state_list = api.get_states(&tag_id)?; let states = API::get_between(&state_list, from, to, true, &config); let mut locations = Vec::new(); for (_, state) in states.iter() { api.queue_location(&tag_id, state.id.as_ref().unwrap()); } let receiver = api.begin_location_fetch(); let mut counter = 0; loop { match receiver.try_recv() { Ok(res) => match res { Ok(loc) => match loc { Ok(mut loc) => { counter += 1; let remaining = exp_time(&api, states.len() as u64 - counter as u64); print!( "Done: {:<5.2}% Remaining: {:<5.2} seconds\r", counter as f32 / states.len() as f32 * 100., remaining.as_secs_f32() ); std::io::stdout().lock().flush().ok(); locations.append(&mut loc); } Err(e) => eprintln!( "Error fetching location data: {}", e.message.unwrap_or(String::new()) ), }, Err(e) => eprintln!("{}", e), }, Err(e) => { if let TryRecvError::Disconnected = e { break; } } } } println!("{:<100}", "Done!"); locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp)); let locs = API::get_between(&locations, from, to, false, &config); Ok((tag, locs)) } fn exp_time(api: &API, reqs_left: u64) -> Duration { let interval = 1_000 / api.config.throttle as u64; Duration::from_millis(interval * reqs_left) } fn try_get_datetime(parsed: String, config: &Config) -> Result { match NaiveDateTime::parse_from_str(&parsed, &config.timedate_format) { Ok(timedate) => Ok(timedate), Err(_) => match NaiveDate::parse_from_str(&parsed, &config.date_format) { Ok(date) => Ok(date.and_hms(0, 0, 0)), Err(_) => match NaiveTime::parse_from_str(&parsed, &config.time_format) { Ok(time) => Ok(Local::today().naive_local().and_time(time)), Err(e) => Err(e), }, }, } } fn find_tag( tag_str: String, tags: &Vec, config: &Config, ) -> Result { let mut tag = None; if let Ok(num) = tag_str.parse::() { if let Some(found) = tags.get((num - 1).max(0) as usize) { tag = Some(found.clone()); } } for curr in tags.iter() { if let Some(nick) = curr.get_nick(config) { if nick == tag_str { tag = Some(curr.clone()); } } } match tag { Some(tag) => Ok(tag), None => Err(GenericError::MessagedError( format!("Could not find device {}", tag_str), None, )), } }