use chrono::{NaiveDateTime, Utc};
use hyper::HeaderMap;
use hyper::header::HeaderValue;
use std::collections::HashMap;
use futures::lock::Mutex;
use std::borrow::Cow;
use crate::{Error, Snowflake};
use std::time::Instant;
lazy_static::lazy_static! {
pub static ref BUCKETS: Mutex<HashMap<Key<'static>, Bucket>> = Mutex::default();
}
#[derive(Hash, PartialEq, Eq)]
pub struct Key<'a> {
token: Cow<'a, str>,
bucket: Cow<'a, str>,
major: Option<Snowflake>
}
impl<'a> Key<'a> {
pub fn lookup(token: &'a str, bucket: &'a str, major: Option<Snowflake>) -> Key<'a> {
Key {
token: Cow::Borrowed(token),
bucket: Cow::Borrowed(bucket),
major
}
}
pub fn insert(token: String, bucket: String, major: Option<Snowflake>) -> Key<'a> {
Key {
token: Cow::Owned(token),
bucket: Cow::Owned(bucket),
major
}
}
}
pub struct Bucket {
pub id: String,
pub limit: u16,
pub remaining: u16,
pub reset: NaiveDateTime,
}
impl Bucket {
pub fn new(headers: &HeaderMap<HeaderValue>) -> Result<Option<Bucket>, Error> {
let bucket: Option<&HeaderValue> = headers.get("x-ratelimit-bucket");
let limit: Option<&HeaderValue> = headers.get("x-ratelimit-limit");
let remaining: Option<&HeaderValue> = headers.get("x-ratelimit-remaining");
let reset: Option<&HeaderValue> = headers.get("x-ratelimit-reset");
if let (Some(bucket), Some(limit), Some(remaining), Some(reset)) = (bucket, limit, remaining, reset) {
let reset = {
let reset = reset.to_str().unwrap();
let mut split_reset = reset.split('.');
let secs = split_reset.next().unwrap().parse::<i64>().unwrap();
if let Some(m) = split_reset.next() {
NaiveDateTime::from_timestamp(secs, m.parse::<u32>().unwrap() * 1_000_000)
} else {
NaiveDateTime::from_timestamp(secs, 0)
}
};
let bucket = Bucket {
id: bucket.to_str()?.to_owned(),
limit: limit.to_str()?.parse::<u16>()?,
remaining: remaining.to_str()?.parse::<u16>()?,
reset,
};
Ok(Some(bucket))
} else {
Ok(None)
}
}
}
pub async fn collect_outdated_buckets() {
let mut removes = 0;
let now: NaiveDateTime = Utc::now().naive_utc();
let time = {
let start = Instant::now();
let buckets: &mut HashMap<Key<'static>, Bucket> = &mut *BUCKETS.lock().await;
buckets.retain(|_, bucket| {
if bucket.reset < now {
removes += 1;
false
} else {
true
}
});
start.elapsed().as_micros()
};
trace!("Removed {} outdated rate-limit buckets in {}µs", removes, time);
}