yepzon-locationer/thingy_lib/src/api/mod.rs

287 lines
9.4 KiB
Rust
Raw Normal View History

pub mod structs;
2020-08-23 00:53:49 +02:00
use super::Config;
2020-08-29 01:27:23 +02:00
use super::LibError;
2020-08-23 00:53:49 +02:00
use chrono::format::ParseError;
use chrono::NaiveDateTime;
2020-08-23 19:54:34 +02:00
use minreq::Response;
use std::fmt::Display;
2020-08-23 19:54:34 +02:00
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::thread;
2020-08-23 00:53:49 +02:00
use std::time::{Duration, Instant};
pub use structs::*;
2020-08-23 00:53:49 +02:00
pub struct API {
2020-08-24 01:40:05 +02:00
pub config: Config,
2020-08-23 19:54:34 +02:00
location_req_que: Vec<String>,
2020-08-23 00:53:49 +02:00
}
impl API {
pub fn new(config: Config) -> API {
API {
config: config,
2020-08-23 19:54:34 +02:00
location_req_que: Vec::new(),
2020-08-23 00:53:49 +02:00
}
}
2020-08-29 01:27:23 +02:00
pub fn get_raw_string(&self, url: APIUrl) -> Result<String, LibError> {
Ok(self
.request(API::api_url(url, &self.config))?
.as_str()?
.to_owned())
}
2020-08-29 01:27:23 +02:00
pub fn get_sharetoken(&self, tag_id: &String) -> Result<SharetokenModel, LibError> {
2020-08-25 21:18:59 +02:00
let response = self.request(API::api_url(
APIUrl::Sharetoken(tag_id.clone()),
&self.config,
))?;
Ok(response.json()?)
}
2020-08-29 01:27:23 +02:00
pub fn get_tags(&self) -> Result<Vec<TagModel>, LibError> {
let response = self.request(API::api_url(APIUrl::Tags, &self.config))?;
2020-08-23 02:08:05 +02:00
let tags = response.json();
if let Err(_) = tags {
let err: ErrorModel = response.json()?;
2020-08-29 01:27:23 +02:00
Err(LibError::from(err))
2020-08-23 02:08:05 +02:00
} else {
2020-08-29 01:27:23 +02:00
tags.map_err(LibError::from)
2020-08-23 02:08:05 +02:00
}
2020-08-23 00:53:49 +02:00
}
2020-08-29 01:27:23 +02:00
pub fn get_tag(&self, tag_id: &String) -> Result<TagModel, LibError> {
2020-08-25 21:30:13 +02:00
let response = self.request(API::api_url(APIUrl::Tag(tag_id.clone()), &self.config))?;
Ok(response.json()?)
}
2020-08-29 01:27:23 +02:00
pub fn get_states(&self, tag_id: &String) -> Result<Vec<StateModel>, LibError> {
let response = self.request(API::api_url(APIUrl::States(tag_id.clone()), &self.config))?;
Ok(response.json()?)
}
2020-08-29 01:27:23 +02:00
pub fn get_current_locations(&self, tag_id: &String) -> Result<Vec<LocationModel>, LibError> {
let response = self.request(API::api_url(
APIUrl::CurrLocations(tag_id.clone()),
&self.config,
))?;
2020-08-23 02:08:05 +02:00
Ok(response.json()?)
2020-08-23 00:53:49 +02:00
}
2020-08-24 23:14:18 +02:00
pub fn get_locations(
&self,
2020-08-24 23:14:18 +02:00
tag_id: &String,
state_id: &String,
2020-08-29 01:27:23 +02:00
) -> Result<Vec<LocationModel>, LibError> {
let response = self.request(API::api_url(
APIUrl::Locations(tag_id.clone(), state_id.clone()),
&self.config,
))?;
2020-08-24 23:14:18 +02:00
Ok(response.json()?)
}
2020-08-23 19:54:34 +02:00
pub fn queue_location(&mut self, tag_id: &String, state_id: &String) {
self.location_req_que.push(API::api_url(
APIUrl::Locations(tag_id.clone(), state_id.clone()),
&self.config,
));
2020-08-23 19:54:34 +02:00
}
2020-08-24 00:36:47 +02:00
pub fn begin_location_fetch(
&mut self,
2020-08-29 01:27:23 +02:00
) -> Receiver<Result<Result<Vec<LocationModel>, ErrorModel>, LibError>> {
2020-08-23 19:54:34 +02:00
let (sender, receiver) = mpsc::channel();
2020-08-24 19:33:28 +02:00
let mut locations = self.location_req_que.clone();
2020-08-23 19:54:34 +02:00
let config = self.config.clone();
2020-08-24 00:36:47 +02:00
// sleep for a second to make sure no overlaps with previous requests.
thread::sleep(Duration::from_millis(1000));
2020-08-23 19:54:34 +02:00
thread::spawn(move || {
2020-08-24 00:36:47 +02:00
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 {
2020-08-23 19:54:34 +02:00
let sender = sender.clone();
let config = config.clone();
2020-08-24 00:36:47 +02:00
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;
2020-08-24 19:40:55 +02:00
if awaiting <= config.throttle && timer < (interval * (config.throttle as u128 - 2))
2020-08-24 00:36:47 +02:00
{
if locations.len() > idx {
let location = locations[idx].clone();
awaiting += 1;
thread::spawn(move || {
2020-08-24 19:33:28 +02:00
let response = minreq::get(location.clone())
2020-08-24 00:36:47 +02:00
.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()?)),
})
2020-08-29 01:27:23 +02:00
.map_err(LibError::from);
2020-08-24 19:33:28 +02:00
i_sender.send((location, response)).ok();
2020-08-23 19:54:34 +02:00
});
2020-08-24 00:36:47 +02:00
timer += interval;
idx += 1;
} else {
while awaiting > 0 {
2020-08-24 19:33:28 +02:00
if let Ok((loc, rec)) = i_receiver.try_recv() {
2020-08-24 19:37:36 +02:00
API::add_error_back(loc, &rec, &mut locations);
2020-08-24 00:36:47 +02:00
awaiting -= 1;
sender.send(rec).ok();
}
}
2020-08-24 19:33:28 +02:00
if locations.len() <= idx {
break;
}
2020-08-24 00:36:47 +02:00
}
} else {
2020-08-24 19:33:28 +02:00
if let Ok((loc, rec)) = i_receiver.try_recv() {
2020-08-24 19:37:36 +02:00
API::add_error_back(loc, &rec, &mut locations);
2020-08-24 00:36:47 +02:00
awaiting -= 1;
sender.send(rec).ok();
}
}
2020-08-23 19:54:34 +02:00
}
});
self.location_req_que.clear();
receiver
2020-08-23 00:53:49 +02:00
}
2020-08-29 01:27:23 +02:00
pub(crate) fn get_between<T: Timestamped + Clone>(
2020-08-24 00:36:47 +02:00
list: &Vec<T>,
2020-08-23 00:53:49 +02:00
from: Option<NaiveDateTime>,
to: Option<NaiveDateTime>,
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 {
2020-08-24 00:36:47 +02:00
timestamped.push((t.clone(), item.clone()));
2020-08-23 00:53:49 +02:00
}
break;
}
2020-08-24 00:36:47 +02:00
timestamped.push((t.clone(), item.clone()));
2020-08-23 00:53:49 +02:00
}
Err(e) => panic!(e),
}
} else {
println!("Skipped item, did not have timestmap");
}
}
timestamped
}
2020-08-25 21:30:13 +02:00
#[allow(dead_code)]
2020-08-29 01:27:23 +02:00
pub fn print_list<T: Display + Timestamped>(list: Result<Vec<T>, LibError>) {
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(),
2020-08-25 21:18:59 +02:00
APIUrl::Sharetoken(tag) => str::replace(&config.sharetoken_url, "{tag}", &tag),
2020-08-25 21:30:13 +02:00
APIUrl::Tag(tag) => str::replace(&config.tag_url, "{tag}", &tag),
}
}
2020-08-24 19:40:55 +02:00
fn add_error_back(
location: String,
2020-08-29 01:27:23 +02:00
rec: &Result<Result<Vec<LocationModel>, ErrorModel>, LibError>,
2020-08-24 19:40:55 +02:00
locations: &mut Vec<String>,
) -> bool {
if let Ok(inner) = rec {
if let Err(_) = inner {
locations.push(location);
true
} else {
false
}
} else {
false
}
}
2020-08-29 01:27:23 +02:00
fn request(&self, url: String) -> Result<Response, LibError> {
2020-08-23 00:53:49 +02:00
let response = minreq::get(url)
.with_header("content-type", "application/json")
.with_header("x-api-key", &self.config.api_key)
.send();
2020-08-29 01:27:23 +02:00
response.map_err(LibError::from)
2020-08-23 00:53:49 +02:00
}
fn parse_timestamp(
timestamp: &Option<String>,
config: &Config,
) -> Option<Result<NaiveDateTime, ParseError>> {
if let Some(timestamp) = &timestamp {
Some(NaiveDateTime::parse_from_str(
&timestamp,
&config.timestamp_format,
))
} else {
None
}
}
}
pub enum APIUrl {
CurrLocations(String),
Locations(String, String),
States(String),
Tags,
2020-08-25 21:30:13 +02:00
#[allow(dead_code)]
Tag(String),
2020-08-25 21:18:59 +02:00
Sharetoken(String),
}