yepzon-locationer/src/main.rs

148 lines
4.6 KiB
Rust

mod api;
mod cmd;
mod config;
mod errors;
use api::API;
use chrono::offset::Local;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
use cmd::*;
use config::Config;
use errors::{GenericError, MessagedError};
use std::fs::File;
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::Since(opt) => {
let mut file = File::open(&opt.config).with_msg(format!(
"Could not find {}",
opt.config.to_str().unwrap_or("")
))?;
let mut string = String::new();
file.read_to_string(&mut string)
.with_msg("config file is not valid UTF-8")?;
let config: Config =
toml::from_str(&string).with_msg("given config file is not a valid config file")?;
let since = Some(try_get_datetime(opt.since, &config)?);
let until = match opt.until {
Some(until) => Some(try_get_datetime(until, &config)?),
None => None,
};
run(&config, since, until)?;
Ok(())
}
Subcommand::Init(_) => {
let config = Config::default();
let mut file = File::create("config.toml").unwrap();
file.write_all(&toml::to_vec(&config).unwrap())
.with_msg("Could not write config.toml, make sure you have correct permissions.")?;
Ok(())
}
}
}
fn run(
config: &Config,
from: Option<NaiveDateTime>,
to: Option<NaiveDateTime>,
) -> Result<(), GenericError> {
let mut api = API::new(config.clone());
let tags = api.get_tags()?;
let first_tag = tags[0].id.clone().unwrap();
let state_list = api.get_states(&first_tag)?;
let states = API::get_between(&state_list, from, to, true, &config);
let mut locations = Vec::new();
for (_, state) in states.iter() {
api.queue_location(&first_tag, 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) => {
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();
match loc {
Ok(mut loc) => 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);
dbg!(
&locs.iter().map(|loc| loc.0).collect::<Vec<NaiveDateTime>>(),
locs.len(),
locations.len()
);
Ok(())
}
fn get_opt<A, B>(option: Option<Result<A, B>>) -> Result<Option<A>, B> {
if let Some(res) = option {
match res {
Ok(a) => Ok(Some(a)),
Err(b) => Err(b),
}
} else {
Ok(None)
}
}
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),
},
},
}
}