yepzon-locationer/src/main.rs

206 lines
6.7 KiB
Rust

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::<Vec<NaiveDateTime>>(),
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<NaiveDateTime>,
to: Option<NaiveDateTime>,
) -> 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<NaiveDateTime, ParseError> {
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<TagModel>,
config: &Config,
) -> Result<TagModel, GenericError> {
let mut tag = None;
if let Ok(num) = tag_str.parse::<i32>() {
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,
)),
}
}