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::sync::mpsc; use std::sync::mpsc::Receiver; use std::thread; use std::time::{Duration, Instant}; pub trait Timestamped { fn timestamp(&self) -> Option; } #[derive(Deserialize, Debug)] pub struct ErrorModel { #[serde(rename(deserialize = "Message"))] pub message: Option, } #[derive(Deserialize, Debug, Clone)] 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, } impl TagModel { pub fn get_nick(&self, config: &Config) -> Option { 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 { 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)] 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 { pub config: Config, location_req_que: Vec, } impl API { pub fn new(config: Config) -> API { API { config: config, location_req_que: Vec::new(), } } pub fn get_tags(&mut self) -> Result, GenericError> { let response = self.request(self.config.tags_url.clone())?; let tags = response.json(); if let Err(_) = tags { let err: ErrorModel = response.json()?; Err(GenericError::from(err)) } else { tags.map_err(GenericError::from) } } pub fn get_states(&mut self, tag_id: &String) -> Result, GenericError> { let response = self.request(str::replace(&self.config.states_url, "{tag}", &tag_id))?; 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); } pub fn begin_location_fetch( &mut self, ) -> Receiver, ErrorModel>, GenericError>> { let (sender, receiver) = mpsc::channel(); let mut locations = self.location_req_que.clone(); let config = self.config.clone(); // sleep for a second to make sure no overlaps with previous requests. thread::sleep(Duration::from_millis(1000)); thread::spawn(move || { let (i_sender, i_receiver) = mpsc::channel(); let mut idx = 0; let mut awaiting = 0; let mut timer = 0; let mut last_time = Instant::now(); let interval = 1_000_000_000 / config.throttle as u128; loop { let sender = sender.clone(); let config = config.clone(); let i_sender = i_sender.clone(); let now = Instant::now(); let delta = (now - last_time).as_nanos(); if timer > 0 { timer = timer - delta.min(timer); } last_time = now; if awaiting <= config.throttle && timer < (interval * (config.throttle as u128 - 2)) { if locations.len() > idx { let location = locations[idx].clone(); awaiting += 1; thread::spawn(move || { let response = minreq::get(location.clone()) .with_header("content-type", "application/json") .with_header("x-api-key", &config.api_key) .send() .and_then(|r| match r.json() { Ok(loc) => Ok(Ok(loc)), Err(_) => Ok(Err(r.json()?)), }) .map_err(GenericError::from); i_sender.send((location, response)).ok(); }); timer += interval; idx += 1; } else { while awaiting > 0 { if let Ok((loc, rec)) = i_receiver.try_recv() { API::add_error_back(loc, &rec, &mut locations); awaiting -= 1; sender.send(rec).ok(); } } if locations.len() <= idx { break; } } } else { if let Ok((loc, rec)) = i_receiver.try_recv() { API::add_error_back(loc, &rec, &mut locations); awaiting -= 1; sender.send(rec).ok(); } } } }); self.location_req_que.clear(); receiver } 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.clone())); } break; } timestamped.push((t.clone(), item.clone())); } Err(e) => panic!(e), } } else { println!("Skipped item, did not have timestmap"); } } timestamped } fn add_error_back( location: String, rec: &Result, ErrorModel>, GenericError>, locations: &mut Vec, ) -> bool { if let Ok(inner) = rec { if let Err(_) = inner { locations.push(location); true } else { false } } else { false } } fn request(&mut self, url: String) -> Result { let response = minreq::get(url) .with_header("content-type", "application/json") .with_header("x-api-key", &self.config.api_key) .send(); response.map_err(GenericError::from) } fn parse_timestamp( timestamp: &Option, config: &Config, ) -> Option> { if let Some(timestamp) = ×tamp { Some(NaiveDateTime::parse_from_str( ×tamp, &config.timestamp_format, )) } else { None } } }