use super::Config; use chrono::format::ParseError; use chrono::NaiveDateTime; use minreq::{Error, Response}; use serde::Deserialize; use std::thread::sleep; use std::time::{Duration, Instant}; pub trait Timestamped { fn timestamp(&self) -> Option; } #[derive(Deserialize, Debug)] pub struct TagModel { pub id: Option, pub imei: Option, #[serde(rename(deserialize = "batteryValue"))] pub battery_value: Option, #[serde(rename(deserialize = "btId"))] pub bt_id: Option, #[serde(rename(deserialize = "nfcId"))] pub nfc_id: Option, } #[derive(Deserialize, Debug, Clone)] pub struct StateModel { pub id: Option, pub timestamp: Option, #[serde(skip_deserializing)] pub state: Option, #[serde(rename(deserialize = "realState"))] pub real_state: Option, } impl Timestamped for StateModel { fn timestamp(&self) -> Option { self.timestamp.clone() } } #[derive(Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] pub struct LocationModel { pub timestamp: Option, #[serde(rename(deserialize = "type"))] pub loc_type: Option, #[serde(rename(deserialize = "coordinateLat"))] pub lat: Option, #[serde(rename(deserialize = "coordinateLng"))] pub lon: Option, pub accuracy: Option, } impl Timestamped for LocationModel { fn timestamp(&self) -> Option { self.timestamp.clone() } } pub struct API { config: Config, pub last_response_ping: u32, } impl API { pub fn new(config: Config) -> API { API { last_response_ping: config.throttle, config: config, } } pub fn get_tags(&mut self) -> Result, Error> { let response = self.request(self.config.tags_url.clone())?; response.json() } pub fn get_states(&mut self, tag_id: &String) -> Result, Error> { let response = self.request(str::replace(&self.config.states_url, "{tag}", &tag_id))?; response.json() } pub fn get_locations( &mut self, tag_id: &String, state_id: &String, ) -> Result, Error> { let url = str::replace(&self.config.locations_url, "{tag}", &tag_id); let url = str::replace(&url, "{state}", &state_id); let response = self.request(url)?; response.json() } pub fn get_between( list: Vec, from: Option, to: Option, inclusive_from: bool, config: &Config, ) -> Vec<(NaiveDateTime, T)> { let mut timestamped = Vec::new(); for item in list { if let Some(res) = API::parse_timestamp(&item.timestamp(), config) { match res { Ok(t) => { if let Some(to) = to { if t > to { continue; } } let mut too_old = false; if let Some(from) = from { if t < from { too_old = true; } } if too_old { if inclusive_from { timestamped.push((t.clone(), item)); } break; } timestamped.push((t.clone(), item)); } Err(e) => panic!(e), } } else { println!("Skipped item, did not have timestmap"); } } timestamped } fn request(&mut self, url: String) -> Result { let before = Instant::now(); let response = minreq::get(url) .with_header("content-type", "application/json") .with_header("x-api-key", &self.config.api_key) .send(); let after = Instant::now(); let min_time = Duration::new(0, self.config.throttle * 1_000_000); let duration = after.duration_since(before); self.last_response_ping = (duration.as_millis() as u32).max(self.config.throttle); if min_time > duration { sleep(min_time - duration); } response } fn parse_timestamp( timestamp: &Option, config: &Config, ) -> Option> { if let Some(timestamp) = ×tamp { Some(NaiveDateTime::parse_from_str( ×tamp, &config.timestamp_format, )) } else { None } } }