Separate commands, apistructs and add api command
This commit is contained in:
parent
23cf2c370d
commit
762ea09b56
@ -1,125 +1,16 @@
|
||||
pub mod structs;
|
||||
|
||||
use super::Config;
|
||||
use super::GenericError;
|
||||
use chrono::format::ParseError;
|
||||
use chrono::NaiveDateTime;
|
||||
use minreq::Response;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::Display;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub trait Timestamped {
|
||||
fn timestamp(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ErrorModel {
|
||||
#[serde(rename(deserialize = "Message"))]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct TagModel {
|
||||
pub id: Option<String>,
|
||||
pub imei: Option<String>,
|
||||
#[serde(rename(deserialize = "batteryValue"))]
|
||||
pub battery_value: Option<i32>,
|
||||
#[serde(rename(deserialize = "btId"))]
|
||||
pub bt_id: Option<String>,
|
||||
#[serde(rename(deserialize = "nfcId"))]
|
||||
pub nfc_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TagModel {
|
||||
pub fn get_nick(&self, config: &Config) -> Option<String> {
|
||||
if let Some(id) = &self.id {
|
||||
if let Some(n) = config.nicknames.get(id) {
|
||||
Some(n.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Result<String, GenericError> {
|
||||
match &self.id {
|
||||
Some(id) => Ok(id.to_string()),
|
||||
None => Err(GenericError::MessagedError(
|
||||
"Could not find device id. Error probably on Yetzon server side.".to_owned(),
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagModel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let mut text = String::new();
|
||||
text += &format!(
|
||||
" id = {}",
|
||||
self.id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n imei = {}",
|
||||
self.imei.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n battery value = {}%",
|
||||
&self
|
||||
.battery_value
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or("unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n bluetooth id = {}",
|
||||
&self.bt_id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n NFC id = {}",
|
||||
&self.nfc_id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct StateModel {
|
||||
pub id: Option<String>,
|
||||
pub timestamp: Option<String>,
|
||||
pub state: Option<String>,
|
||||
#[serde(rename(deserialize = "realState"))]
|
||||
pub real_state: Option<String>,
|
||||
}
|
||||
|
||||
impl Timestamped for StateModel {
|
||||
fn timestamp(&self) -> Option<String> {
|
||||
self.timestamp.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LocationModel {
|
||||
pub timestamp: Option<String>,
|
||||
#[serde(rename(deserialize = "type"))]
|
||||
pub loc_type: Option<String>,
|
||||
#[serde(rename(deserialize = "coordinateLat"))]
|
||||
pub lat: Option<f64>,
|
||||
#[serde(rename(deserialize = "coordinateLng"))]
|
||||
pub lon: Option<f64>,
|
||||
pub accuracy: Option<f32>,
|
||||
}
|
||||
|
||||
impl Timestamped for LocationModel {
|
||||
fn timestamp(&self) -> Option<String> {
|
||||
self.timestamp.clone()
|
||||
}
|
||||
}
|
||||
pub use structs::*;
|
||||
|
||||
pub struct API {
|
||||
pub config: Config,
|
||||
@ -134,8 +25,16 @@ impl API {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tags(&mut self) -> Result<Vec<TagModel>, GenericError> {
|
||||
let response = self.request(self.config.tags_url.clone())?;
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_string(&self, url: APIUrl) -> Result<String, GenericError> {
|
||||
Ok(self
|
||||
.request(API::api_url(url, &self.config))?
|
||||
.as_str()?
|
||||
.to_owned())
|
||||
}
|
||||
|
||||
pub fn get_tags(&self) -> Result<Vec<TagModel>, GenericError> {
|
||||
let response = self.request(API::api_url(APIUrl::Tags, &self.config))?;
|
||||
let tags = response.json();
|
||||
if let Err(_) = tags {
|
||||
let err: ErrorModel = response.json()?;
|
||||
@ -145,26 +44,39 @@ impl API {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_states(&mut self, tag_id: &String) -> Result<Vec<StateModel>, GenericError> {
|
||||
let response = self.request(str::replace(&self.config.states_url, "{tag}", &tag_id))?;
|
||||
pub fn get_states(&self, tag_id: &String) -> Result<Vec<StateModel>, GenericError> {
|
||||
let response = self.request(API::api_url(APIUrl::States(tag_id.clone()), &self.config))?;
|
||||
Ok(response.json()?)
|
||||
}
|
||||
|
||||
pub fn get_current_locations(
|
||||
&self,
|
||||
tag_id: &String,
|
||||
) -> Result<Vec<LocationModel>, GenericError> {
|
||||
let response = self.request(API::api_url(
|
||||
APIUrl::CurrLocations(tag_id.clone()),
|
||||
&self.config,
|
||||
))?;
|
||||
Ok(response.json()?)
|
||||
}
|
||||
|
||||
pub fn get_locations(
|
||||
&mut self,
|
||||
&self,
|
||||
tag_id: &String,
|
||||
state_id: &String,
|
||||
) -> Result<Vec<LocationModel>, GenericError> {
|
||||
let url = str::replace(&self.config.locations_url, "{tag}", &tag_id);
|
||||
let url = str::replace(&url, "{state}", &state_id);
|
||||
let response = self.request(url)?;
|
||||
let response = self.request(API::api_url(
|
||||
APIUrl::Locations(tag_id.clone(), state_id.clone()),
|
||||
&self.config,
|
||||
))?;
|
||||
Ok(response.json()?)
|
||||
}
|
||||
|
||||
pub fn queue_location(&mut self, tag_id: &String, state_id: &String) {
|
||||
let url = str::replace(&self.config.locations_url, "{tag}", &tag_id);
|
||||
let url = str::replace(&url, "{state}", &state_id);
|
||||
self.location_req_que.push(url);
|
||||
self.location_req_que.push(API::api_url(
|
||||
APIUrl::Locations(tag_id.clone(), state_id.clone()),
|
||||
&self.config,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn begin_location_fetch(
|
||||
@ -282,6 +194,35 @@ impl API {
|
||||
timestamped
|
||||
}
|
||||
|
||||
pub fn print_list<T: Display + Timestamped>(list: Result<Vec<T>, GenericError>) {
|
||||
match list {
|
||||
Ok(items) => {
|
||||
for item in items {
|
||||
println!(
|
||||
"\n{}\n{}",
|
||||
item.timestamp().unwrap_or("No timestamp".to_owned()),
|
||||
item
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Could not complete request: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_url(url: APIUrl, config: &Config) -> String {
|
||||
match url {
|
||||
APIUrl::CurrLocations(tag) => {
|
||||
str::replace(&config.current_locations_url, "{tag}", &tag)
|
||||
}
|
||||
APIUrl::Locations(tag, state) => {
|
||||
let url = str::replace(&config.locations_url, "{tag}", &tag);
|
||||
str::replace(&url, "{state}", &state)
|
||||
}
|
||||
APIUrl::States(tag) => str::replace(&config.states_url, "{tag}", &tag),
|
||||
APIUrl::Tags => config.tags_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_error_back(
|
||||
location: String,
|
||||
rec: &Result<Result<Vec<LocationModel>, ErrorModel>, GenericError>,
|
||||
@ -299,7 +240,7 @@ impl API {
|
||||
}
|
||||
}
|
||||
|
||||
fn request(&mut self, url: String) -> Result<Response, GenericError> {
|
||||
fn request(&self, url: String) -> Result<Response, GenericError> {
|
||||
let response = minreq::get(url)
|
||||
.with_header("content-type", "application/json")
|
||||
.with_header("x-api-key", &self.config.api_key)
|
||||
@ -321,3 +262,10 @@ impl API {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum APIUrl {
|
||||
CurrLocations(String),
|
||||
Locations(String, String),
|
||||
States(String),
|
||||
Tags,
|
||||
}
|
227
src/api/structs.rs
Normal file
227
src/api/structs.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use super::{Config, GenericError};
|
||||
use serde::Deserialize;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
pub trait Timestamped {
|
||||
fn timestamp(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ErrorModel {
|
||||
#[serde(rename(deserialize = "Message"))]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TagModel {
|
||||
pub id: Option<String>,
|
||||
pub imei: Option<String>,
|
||||
pub battery_value: Option<i32>,
|
||||
pub bt_id: Option<String>,
|
||||
pub nfc_id: Option<String>,
|
||||
pub pair_code: Option<String>,
|
||||
pub location: Option<LocationModel>,
|
||||
pub state: Option<ChangingState>,
|
||||
pub last_contact: Option<String>,
|
||||
pub charging: Option<bool>,
|
||||
pub charger_connected: Option<bool>,
|
||||
pub config: Option<ChangingConfig>,
|
||||
pub capabilities: Option<Capabilities>,
|
||||
}
|
||||
|
||||
impl TagModel {
|
||||
pub fn get_nick(&self, config: &Config) -> Option<String> {
|
||||
if let Some(id) = &self.id {
|
||||
if let Some(n) = config.nicknames.get(id) {
|
||||
Some(n.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Result<String, GenericError> {
|
||||
match &self.id {
|
||||
Some(id) => Ok(id.to_string()),
|
||||
None => Err(GenericError::MessagedError(
|
||||
"Could not find device id. Error probably on Yetzon server side.".to_owned(),
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagModel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let mut text = String::new();
|
||||
text += &format!(
|
||||
" id = {}",
|
||||
self.id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n imei = {}",
|
||||
self.imei.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n battery value = {}%",
|
||||
&self
|
||||
.battery_value
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or("unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n bluetooth id = {}",
|
||||
&self.bt_id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n NFC id = {}",
|
||||
&self.nfc_id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigModel {
|
||||
pub active_interval: Option<i32>,
|
||||
pub sleep_interval: Option<i32>,
|
||||
pub sos_alert_sound_enabled: Option<bool>,
|
||||
pub mode: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ChangingConfig {
|
||||
current: Option<ConfigModel>,
|
||||
pending: Option<ConfigModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Capabilities {
|
||||
rx: Option<bool>,
|
||||
sos: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StateModel {
|
||||
pub id: Option<String>,
|
||||
pub timestamp: Option<String>,
|
||||
pub state: Option<String>,
|
||||
pub real_state: Option<String>,
|
||||
}
|
||||
|
||||
impl StateModel {
|
||||
pub fn get_id(&self) -> Result<String, GenericError> {
|
||||
match &self.id {
|
||||
Some(id) => Ok(id.to_string()),
|
||||
None => Err(GenericError::MessagedError(
|
||||
"Could not find state id. Error probably on Yetzon server side.".to_owned(),
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timestamped for StateModel {
|
||||
fn timestamp(&self) -> Option<String> {
|
||||
self.timestamp.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StateModel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let mut text = String::new();
|
||||
text += &format!(
|
||||
" id = {}",
|
||||
self.id.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n timestamp = {}",
|
||||
self.timestamp.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n state = {}",
|
||||
self.state.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n real state = {}",
|
||||
self.real_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"no real state".to_owned())
|
||||
);
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ChangingState {
|
||||
current: Option<StateModel>,
|
||||
pending: Option<StateModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LocationModel {
|
||||
pub timestamp: Option<String>,
|
||||
#[serde(rename(deserialize = "type"))]
|
||||
pub loc_type: Option<String>,
|
||||
#[serde(rename(deserialize = "coordinateLat"))]
|
||||
#[serde(alias = "latitude")]
|
||||
pub lat: Option<f64>,
|
||||
#[serde(rename(deserialize = "coordinateLng"))]
|
||||
#[serde(alias = "longitude")]
|
||||
pub lon: Option<f64>,
|
||||
pub accuracy: Option<f32>,
|
||||
}
|
||||
|
||||
impl Timestamped for LocationModel {
|
||||
fn timestamp(&self) -> Option<String> {
|
||||
self.timestamp.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LocationModel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let mut text = String::new();
|
||||
text += &format!(
|
||||
" timestamp = {}",
|
||||
self.timestamp().as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n location type = {}",
|
||||
self.loc_type.as_ref().unwrap_or(&"unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n latitude = {}",
|
||||
self.lat
|
||||
.as_ref()
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or("unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n longitude = {}",
|
||||
self.lon
|
||||
.as_ref()
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or("unknown".to_owned())
|
||||
);
|
||||
text += &format!(
|
||||
"\n accuracy = {}",
|
||||
self.accuracy
|
||||
.as_ref()
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or("unknown".to_owned())
|
||||
);
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ pub struct EnvOpt {
|
||||
pub enum Subcommand {
|
||||
Between(BetweenOpt),
|
||||
Init(InitOpt),
|
||||
Api(ApiOpt),
|
||||
Nick(NickOpt),
|
||||
Get(GetOpt),
|
||||
}
|
||||
@ -58,6 +59,14 @@ pub struct InitOpt {
|
||||
pub api_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(
|
||||
subcommand,
|
||||
name = "api",
|
||||
description = "check integrity of API and API-key validity"
|
||||
)]
|
||||
pub struct ApiOpt {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(
|
||||
subcommand,
|
||||
@ -88,11 +97,11 @@ pub struct NickListOpt {}
|
||||
#[argh(
|
||||
subcommand,
|
||||
name = "set",
|
||||
description = "Set nickname for given device index. see index with nick list"
|
||||
description = "Set nickname for given device. Give device index from nick list, or current nickname."
|
||||
)]
|
||||
pub struct NickSetOpt {
|
||||
#[argh(positional, description = "device index, see index with nick list")]
|
||||
pub index: i32,
|
||||
#[argh(positional, description = "the device index or nickname")]
|
||||
pub device: String,
|
||||
#[argh(positional, description = "the new nickname for the device")]
|
||||
pub nickname: String,
|
||||
}
|
||||
@ -111,10 +120,19 @@ pub struct GetOpt {
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
pub enum GetSub {
|
||||
Tags(GetTagsOpt),
|
||||
States(GetStatesOpt),
|
||||
Locations(GetLocationsOpt),
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(
|
||||
subcommand,
|
||||
name = "tags",
|
||||
description = "Get all tags connected to this API-key"
|
||||
)]
|
||||
pub struct GetTagsOpt {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(
|
||||
subcommand,
|
||||
@ -136,5 +154,5 @@ pub struct GetLocationsOpt {
|
||||
#[argh(positional, description = "the device in question")]
|
||||
pub device: String,
|
||||
#[argh(positional, description = "the state id")]
|
||||
pub state: String,
|
||||
pub state: Option<String>,
|
||||
}
|
194
src/commands.rs
Normal file
194
src/commands.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use super::api::{LocationModel, StateModel, TagModel, API};
|
||||
use super::config::Config;
|
||||
use super::errors::GenericError;
|
||||
use super::gpx;
|
||||
use chrono::NaiveDateTime;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn init(path: &PathBuf, api_key: Option<String>) -> Result<(), GenericError> {
|
||||
let mut config = Config::default();
|
||||
if let Some(api_key) = api_key {
|
||||
config.api_key = api_key;
|
||||
}
|
||||
Ok(config.write_to(path)?)
|
||||
}
|
||||
|
||||
pub fn between(
|
||||
api: &mut API,
|
||||
tag_str: String,
|
||||
from: Option<NaiveDateTime>,
|
||||
to: Option<NaiveDateTime>,
|
||||
) -> Result<(TagModel, Vec<(NaiveDateTime, LocationModel)>), GenericError> {
|
||||
let tags = api.get_tags()?;
|
||||
let tag = find_tag(tag_str, &tags, &api.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, &api.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}", "\nDone!");
|
||||
locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp));
|
||||
|
||||
let locs = API::get_between(&locations, from, to, false, &api.config);
|
||||
|
||||
let gpx = gpx::generate_gpx(&tag, &locs, &api.config)?;
|
||||
gpx::write_gpx(&gpx)?;
|
||||
Ok((tag, locs))
|
||||
}
|
||||
|
||||
pub fn check_api(api: &API) -> Result<(), GenericError> {
|
||||
let tags = api.get_tags()?;
|
||||
if let Some(tag) = tags.get(0) {
|
||||
let tag_id = tag.get_id()?;
|
||||
|
||||
api.get_current_locations(&tag_id)?;
|
||||
|
||||
if let Some(state) = api.get_states(&tag_id)?.get(0) {
|
||||
let state_id = state.get_id()?;
|
||||
api.get_locations(&tag_id, &state_id)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GenericError::MessagedError(
|
||||
"Could not find any states for the first device found.".to_owned(),
|
||||
None,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(GenericError::MessagedError("Could not find any devices. Please make sure this API-key is paired with a device first.".to_owned(), None))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nick_list(api: &API) -> Result<Vec<((usize, String), TagModel)>, GenericError> {
|
||||
let mut tags = api.get_tags()?;
|
||||
tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id));
|
||||
let list = tags
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, tag)| {
|
||||
(
|
||||
(
|
||||
idx + 1,
|
||||
tag.get_nick(&api.config)
|
||||
.unwrap_or("No current nick".to_owned()),
|
||||
),
|
||||
tag.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub fn nick_set(
|
||||
api: &mut API,
|
||||
config_path: &PathBuf,
|
||||
tag_str: String,
|
||||
nickname: String,
|
||||
) -> Result<(), GenericError> {
|
||||
let mut tags = api.get_tags()?;
|
||||
tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id));
|
||||
let tag = find_tag(tag_str.clone(), &tags, &api.config)?;
|
||||
if let Some(id) = &tag.id {
|
||||
api.config.nicknames.insert(id.clone(), nickname);
|
||||
api.config.write_to(config_path)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GenericError::MessagedError(
|
||||
format!("Device {} does not have an id", tag_str),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_locations(
|
||||
api: &API,
|
||||
tag: String,
|
||||
state: Option<String>,
|
||||
) -> Result<Vec<LocationModel>, GenericError> {
|
||||
let tags = api.get_tags()?;
|
||||
let tag = find_tag(tag, &tags, &api.config)?;
|
||||
if let Some(state) = state {
|
||||
Ok(api.get_locations(&tag.get_id()?, &state)?)
|
||||
} else {
|
||||
Ok(api.get_current_locations(&tag.get_id()?)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_states(api: &API, tag: String) -> Result<Vec<StateModel>, GenericError> {
|
||||
let tags = api.get_tags()?;
|
||||
let tag = find_tag(tag, &tags, &api.config)?;
|
||||
Ok(api.get_states(&tag.get_id()?)?)
|
||||
}
|
||||
|
||||
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 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,
|
||||
)),
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ pub struct Config {
|
||||
pub tags_url: String,
|
||||
pub states_url: String,
|
||||
pub locations_url: String,
|
||||
pub current_locations_url: String,
|
||||
|
||||
pub timestamp_format: String,
|
||||
pub timedate_format: String,
|
||||
@ -50,6 +51,7 @@ impl Default for Config {
|
||||
tags_url: "https://platform.yepzon.com/tags".to_owned(),
|
||||
states_url: "https://platform.yepzon.com/tags/{tag}/states".to_owned(),
|
||||
locations_url: "https://platform.yepzon.com/tags/{tag}/locations/{state}".to_owned(),
|
||||
current_locations_url: "https://platform.yepzon.com/tags/{tag}/locations".to_owned(),
|
||||
|
||||
timestamp_format: "%Y-%m-%dT%H:%M:%S%.fZ".to_owned(),
|
||||
|
||||
|
12
src/gpx.rs
12
src/gpx.rs
@ -1,13 +1,13 @@
|
||||
use super::{Config, GenericError, LocationModel, TagModel};
|
||||
use chrono::offset::{LocalResult, TimeZone, Utc as UTC_ZONE};
|
||||
use chrono::offset::Utc as UTC_ZONE;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use geo_types::Point;
|
||||
use gpx::{Gpx, GpxVersion, Metadata, Person, Route, Track, TrackSegment, Waypoint};
|
||||
use std::fs::File;
|
||||
|
||||
pub fn generate_gpx(
|
||||
tag: TagModel,
|
||||
locations: Vec<(NaiveDateTime, LocationModel)>,
|
||||
tag: &TagModel,
|
||||
locations: &Vec<(NaiveDateTime, LocationModel)>,
|
||||
config: &Config,
|
||||
) -> Result<Gpx, GenericError> {
|
||||
let route = Route::default();
|
||||
@ -47,14 +47,14 @@ pub fn write_gpx(gpx: &Gpx) -> Result<(), GenericError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_waypoint(point: (NaiveDateTime, LocationModel)) -> Result<Waypoint, GenericError> {
|
||||
fn get_waypoint(point: &(NaiveDateTime, LocationModel)) -> Result<Waypoint, GenericError> {
|
||||
let (naive_time, location) = point;
|
||||
let now = Utc::now();
|
||||
let time: DateTime<UTC_ZONE> = DateTime::from_utc(naive_time, now.offset().clone());
|
||||
let time: DateTime<UTC_ZONE> = DateTime::from_utc(*naive_time, now.offset().clone());
|
||||
if let (Some(lat), Some(lon)) = (location.lat, location.lon) {
|
||||
let mut waypoint = Waypoint::new(Point::new(lon, lat));
|
||||
waypoint.time = Some(time);
|
||||
waypoint.source = location.loc_type;
|
||||
waypoint.source = location.loc_type.as_ref().cloned();
|
||||
Ok(waypoint)
|
||||
} else {
|
||||
Err(GenericError::MessagedError(
|
||||
|
176
src/main.rs
176
src/main.rs
@ -1,18 +1,16 @@
|
||||
mod api;
|
||||
mod cmd;
|
||||
mod args;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod errors;
|
||||
mod gpx;
|
||||
|
||||
use api::{LocationModel, TagModel, API};
|
||||
use args::*;
|
||||
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();
|
||||
@ -26,6 +24,7 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
||||
match env.subcommand {
|
||||
Subcommand::Between(opt) => {
|
||||
let config = Config::from_path(&env.config)?;
|
||||
let mut api = API::new(config.clone());
|
||||
|
||||
let since =
|
||||
Some(try_get_datetime(opt.since, &config).with_msg("Failed to parse since")?);
|
||||
@ -37,76 +36,60 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let (tag, locs) = run(&config, opt.device, since, until)?;
|
||||
let (_, locs) = commands::between(&mut api, 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)?;
|
||||
commands::init(&env.config, opt.api_key)?;
|
||||
Ok(())
|
||||
}
|
||||
Subcommand::Api(_) => {
|
||||
let config = Config::from_path(&env.config)?;
|
||||
let api = API::new(config.clone());
|
||||
match commands::check_api(&api) {
|
||||
Ok(_) => {
|
||||
println!("API verified, no issues");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(GenericError::MessagedError(
|
||||
"API integrity failed, or API-key is not valid. ".to_owned(),
|
||||
Some(Box::new(e)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Subcommand::Nick(opt) => {
|
||||
let mut config = Config::from_path(&env.config)?;
|
||||
let 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
|
||||
);
|
||||
for ((idx, nick), tag) in commands::nick_list(&api)? {
|
||||
println!("{}. {}\n{}", idx, nick, 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)?;
|
||||
commands::nick_set(&mut api, &env.config, opt.device, opt.nickname)?;
|
||||
}
|
||||
}
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Subcommand::Get(opt) => {
|
||||
let config = Config::from_path(&env.config)?;
|
||||
let mut api = API::new(config.clone());
|
||||
let tags = api.get_tags()?;
|
||||
let api = API::new(config.clone());
|
||||
match opt.subcommand {
|
||||
GetSub::Tags(_) => {
|
||||
let tags = api.get_tags()?;
|
||||
dbg!(&tags);
|
||||
}
|
||||
GetSub::States(opt) => {
|
||||
let tag = find_tag(opt.device, &tags, &config)?;
|
||||
dbg!(api.get_states(&tag.get_id()?));
|
||||
API::print_list(commands::get_states(&api, opt.device));
|
||||
}
|
||||
GetSub::Locations(opt) => {
|
||||
let tag = find_tag(opt.device, &tags, &config)?;
|
||||
dbg!(api.get_locations(&tag.get_id()?, &opt.state));
|
||||
API::print_list(commands::get_locations(&api, opt.device, opt.state))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -114,72 +97,6 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
||||
}
|
||||
}
|
||||
|
||||
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}", "\nDone!");
|
||||
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),
|
||||
@ -192,30 +109,3 @@ fn try_get_datetime(parsed: String, config: &Config) -> Result<NaiveDateTime, Pa
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user