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::Config;
|
||||||
use super::GenericError;
|
use super::GenericError;
|
||||||
use chrono::format::ParseError;
|
use chrono::format::ParseError;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use minreq::Response;
|
use minreq::Response;
|
||||||
use serde::Deserialize;
|
use std::fmt::Display;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
pub use structs::*;
|
||||||
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 struct API {
|
pub struct API {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
@ -134,8 +25,16 @@ impl API {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tags(&mut self) -> Result<Vec<TagModel>, GenericError> {
|
#[allow(dead_code)]
|
||||||
let response = self.request(self.config.tags_url.clone())?;
|
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();
|
let tags = response.json();
|
||||||
if let Err(_) = tags {
|
if let Err(_) = tags {
|
||||||
let err: ErrorModel = response.json()?;
|
let err: ErrorModel = response.json()?;
|
||||||
@ -145,26 +44,39 @@ impl API {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_states(&mut self, tag_id: &String) -> Result<Vec<StateModel>, GenericError> {
|
pub fn get_states(&self, tag_id: &String) -> Result<Vec<StateModel>, GenericError> {
|
||||||
let response = self.request(str::replace(&self.config.states_url, "{tag}", &tag_id))?;
|
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()?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_locations(
|
pub fn get_locations(
|
||||||
&mut self,
|
&self,
|
||||||
tag_id: &String,
|
tag_id: &String,
|
||||||
state_id: &String,
|
state_id: &String,
|
||||||
) -> Result<Vec<LocationModel>, GenericError> {
|
) -> Result<Vec<LocationModel>, GenericError> {
|
||||||
let url = str::replace(&self.config.locations_url, "{tag}", &tag_id);
|
let response = self.request(API::api_url(
|
||||||
let url = str::replace(&url, "{state}", &state_id);
|
APIUrl::Locations(tag_id.clone(), state_id.clone()),
|
||||||
let response = self.request(url)?;
|
&self.config,
|
||||||
|
))?;
|
||||||
Ok(response.json()?)
|
Ok(response.json()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_location(&mut self, tag_id: &String, state_id: &String) {
|
pub fn queue_location(&mut self, tag_id: &String, state_id: &String) {
|
||||||
let url = str::replace(&self.config.locations_url, "{tag}", &tag_id);
|
self.location_req_que.push(API::api_url(
|
||||||
let url = str::replace(&url, "{state}", &state_id);
|
APIUrl::Locations(tag_id.clone(), state_id.clone()),
|
||||||
self.location_req_que.push(url);
|
&self.config,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_location_fetch(
|
pub fn begin_location_fetch(
|
||||||
@ -282,6 +194,35 @@ impl API {
|
|||||||
timestamped
|
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(
|
fn add_error_back(
|
||||||
location: String,
|
location: String,
|
||||||
rec: &Result<Result<Vec<LocationModel>, ErrorModel>, GenericError>,
|
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)
|
let response = minreq::get(url)
|
||||||
.with_header("content-type", "application/json")
|
.with_header("content-type", "application/json")
|
||||||
.with_header("x-api-key", &self.config.api_key)
|
.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 {
|
pub enum Subcommand {
|
||||||
Between(BetweenOpt),
|
Between(BetweenOpt),
|
||||||
Init(InitOpt),
|
Init(InitOpt),
|
||||||
|
Api(ApiOpt),
|
||||||
Nick(NickOpt),
|
Nick(NickOpt),
|
||||||
Get(GetOpt),
|
Get(GetOpt),
|
||||||
}
|
}
|
||||||
@ -58,6 +59,14 @@ pub struct InitOpt {
|
|||||||
pub api_key: Option<String>,
|
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)]
|
#[derive(FromArgs)]
|
||||||
#[argh(
|
#[argh(
|
||||||
subcommand,
|
subcommand,
|
||||||
@ -88,11 +97,11 @@ pub struct NickListOpt {}
|
|||||||
#[argh(
|
#[argh(
|
||||||
subcommand,
|
subcommand,
|
||||||
name = "set",
|
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 {
|
pub struct NickSetOpt {
|
||||||
#[argh(positional, description = "device index, see index with nick list")]
|
#[argh(positional, description = "the device index or nickname")]
|
||||||
pub index: i32,
|
pub device: String,
|
||||||
#[argh(positional, description = "the new nickname for the device")]
|
#[argh(positional, description = "the new nickname for the device")]
|
||||||
pub nickname: String,
|
pub nickname: String,
|
||||||
}
|
}
|
||||||
@ -111,10 +120,19 @@ pub struct GetOpt {
|
|||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
#[argh(subcommand)]
|
#[argh(subcommand)]
|
||||||
pub enum GetSub {
|
pub enum GetSub {
|
||||||
|
Tags(GetTagsOpt),
|
||||||
States(GetStatesOpt),
|
States(GetStatesOpt),
|
||||||
Locations(GetLocationsOpt),
|
Locations(GetLocationsOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
#[argh(
|
||||||
|
subcommand,
|
||||||
|
name = "tags",
|
||||||
|
description = "Get all tags connected to this API-key"
|
||||||
|
)]
|
||||||
|
pub struct GetTagsOpt {}
|
||||||
|
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
#[argh(
|
#[argh(
|
||||||
subcommand,
|
subcommand,
|
||||||
@ -136,5 +154,5 @@ pub struct GetLocationsOpt {
|
|||||||
#[argh(positional, description = "the device in question")]
|
#[argh(positional, description = "the device in question")]
|
||||||
pub device: String,
|
pub device: String,
|
||||||
#[argh(positional, description = "the state id")]
|
#[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 tags_url: String,
|
||||||
pub states_url: String,
|
pub states_url: String,
|
||||||
pub locations_url: String,
|
pub locations_url: String,
|
||||||
|
pub current_locations_url: String,
|
||||||
|
|
||||||
pub timestamp_format: String,
|
pub timestamp_format: String,
|
||||||
pub timedate_format: String,
|
pub timedate_format: String,
|
||||||
@ -50,6 +51,7 @@ impl Default for Config {
|
|||||||
tags_url: "https://platform.yepzon.com/tags".to_owned(),
|
tags_url: "https://platform.yepzon.com/tags".to_owned(),
|
||||||
states_url: "https://platform.yepzon.com/tags/{tag}/states".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(),
|
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(),
|
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 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 chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use geo_types::Point;
|
use geo_types::Point;
|
||||||
use gpx::{Gpx, GpxVersion, Metadata, Person, Route, Track, TrackSegment, Waypoint};
|
use gpx::{Gpx, GpxVersion, Metadata, Person, Route, Track, TrackSegment, Waypoint};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
pub fn generate_gpx(
|
pub fn generate_gpx(
|
||||||
tag: TagModel,
|
tag: &TagModel,
|
||||||
locations: Vec<(NaiveDateTime, LocationModel)>,
|
locations: &Vec<(NaiveDateTime, LocationModel)>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Gpx, GenericError> {
|
) -> Result<Gpx, GenericError> {
|
||||||
let route = Route::default();
|
let route = Route::default();
|
||||||
@ -47,14 +47,14 @@ pub fn write_gpx(gpx: &Gpx) -> Result<(), GenericError> {
|
|||||||
Ok(())
|
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 (naive_time, location) = point;
|
||||||
let now = Utc::now();
|
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) {
|
if let (Some(lat), Some(lon)) = (location.lat, location.lon) {
|
||||||
let mut waypoint = Waypoint::new(Point::new(lon, lat));
|
let mut waypoint = Waypoint::new(Point::new(lon, lat));
|
||||||
waypoint.time = Some(time);
|
waypoint.time = Some(time);
|
||||||
waypoint.source = location.loc_type;
|
waypoint.source = location.loc_type.as_ref().cloned();
|
||||||
Ok(waypoint)
|
Ok(waypoint)
|
||||||
} else {
|
} else {
|
||||||
Err(GenericError::MessagedError(
|
Err(GenericError::MessagedError(
|
||||||
|
176
src/main.rs
176
src/main.rs
@ -1,18 +1,16 @@
|
|||||||
mod api;
|
mod api;
|
||||||
mod cmd;
|
mod args;
|
||||||
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod gpx;
|
mod gpx;
|
||||||
|
|
||||||
use api::{LocationModel, TagModel, API};
|
use api::{LocationModel, TagModel, API};
|
||||||
|
use args::*;
|
||||||
use chrono::offset::Local;
|
use chrono::offset::Local;
|
||||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
|
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
|
||||||
use cmd::*;
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use errors::{GenericError, MessagedError};
|
use errors::{GenericError, MessagedError};
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::sync::mpsc::TryRecvError;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let env: EnvOpt = argh::from_env();
|
let env: EnvOpt = argh::from_env();
|
||||||
@ -26,6 +24,7 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
|||||||
match env.subcommand {
|
match env.subcommand {
|
||||||
Subcommand::Between(opt) => {
|
Subcommand::Between(opt) => {
|
||||||
let config = Config::from_path(&env.config)?;
|
let config = Config::from_path(&env.config)?;
|
||||||
|
let mut api = API::new(config.clone());
|
||||||
|
|
||||||
let since =
|
let since =
|
||||||
Some(try_get_datetime(opt.since, &config).with_msg("Failed to parse 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,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tag, locs) = run(&config, opt.device, since, until)?;
|
let (_, locs) = commands::between(&mut api, opt.device, since, until)?;
|
||||||
dbg!(
|
dbg!(
|
||||||
&locs.iter().map(|loc| loc.0).collect::<Vec<NaiveDateTime>>(),
|
&locs.iter().map(|loc| loc.0).collect::<Vec<NaiveDateTime>>(),
|
||||||
locs.len(),
|
locs.len(),
|
||||||
);
|
);
|
||||||
let gpx = gpx::generate_gpx(tag, locs, &config)?;
|
|
||||||
gpx::write_gpx(&gpx)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Subcommand::Init(opt) => {
|
Subcommand::Init(opt) => {
|
||||||
let mut config = Config::default();
|
commands::init(&env.config, opt.api_key)?;
|
||||||
if let Some(api_key) = opt.api_key {
|
|
||||||
config.api_key = api_key;
|
|
||||||
}
|
|
||||||
config.write_to(&env.config)?;
|
|
||||||
Ok(())
|
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) => {
|
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 api = API::new(config.clone());
|
||||||
|
|
||||||
let mut tags = api.get_tags()?;
|
|
||||||
tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id));
|
|
||||||
|
|
||||||
match opt.subcommand {
|
match opt.subcommand {
|
||||||
NickSub::List(_) => {
|
NickSub::List(_) => {
|
||||||
for (idx, tag) in tags.iter().enumerate() {
|
for ((idx, nick), tag) in commands::nick_list(&api)? {
|
||||||
println!(
|
println!("{}. {}\n{}", idx, nick, tag);
|
||||||
"{}. {}\n{}",
|
|
||||||
idx + 1,
|
|
||||||
tag.get_nick(&config)
|
|
||||||
.unwrap_or("No current nick".to_owned()),
|
|
||||||
tag
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
NickSub::Set(opt) => {
|
NickSub::Set(opt) => {
|
||||||
if let Some(tag) = tags.get((opt.index - 1).max(0) as usize) {
|
commands::nick_set(&mut api, &env.config, opt.device, opt.nickname)?;
|
||||||
if let Some(id) = &tag.id {
|
}
|
||||||
config.nicknames.insert(id.clone(), opt.nickname);
|
}
|
||||||
config.write_to(&env.config)?;
|
|
||||||
Ok(())
|
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) => {
|
Subcommand::Get(opt) => {
|
||||||
let config = Config::from_path(&env.config)?;
|
let config = Config::from_path(&env.config)?;
|
||||||
let mut api = API::new(config.clone());
|
let api = API::new(config.clone());
|
||||||
let tags = api.get_tags()?;
|
|
||||||
match opt.subcommand {
|
match opt.subcommand {
|
||||||
|
GetSub::Tags(_) => {
|
||||||
|
let tags = api.get_tags()?;
|
||||||
|
dbg!(&tags);
|
||||||
|
}
|
||||||
GetSub::States(opt) => {
|
GetSub::States(opt) => {
|
||||||
let tag = find_tag(opt.device, &tags, &config)?;
|
API::print_list(commands::get_states(&api, opt.device));
|
||||||
dbg!(api.get_states(&tag.get_id()?));
|
|
||||||
}
|
}
|
||||||
GetSub::Locations(opt) => {
|
GetSub::Locations(opt) => {
|
||||||
let tag = find_tag(opt.device, &tags, &config)?;
|
API::print_list(commands::get_locations(&api, opt.device, opt.state))
|
||||||
dbg!(api.get_locations(&tag.get_id()?, &opt.state));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
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> {
|
fn try_get_datetime(parsed: String, config: &Config) -> Result<NaiveDateTime, ParseError> {
|
||||||
match NaiveDateTime::parse_from_str(&parsed, &config.timedate_format) {
|
match NaiveDateTime::parse_from_str(&parsed, &config.timedate_format) {
|
||||||
Ok(timedate) => Ok(timedate),
|
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