You've already forked AstralRinth
Tiltify API query backoff (#6447)
* Tiltify API querying backoff * fix backoff * fix clippy * fix2
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use eyre::eyre;
|
||||
use eyre::{Result, eyre};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -13,7 +13,24 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
pub struct TiltifyClient {
|
||||
http: HttpClient,
|
||||
token: Mutex<Option<TiltifyAccessToken>>,
|
||||
state: Mutex<TiltifyState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TiltifyState {
|
||||
token: Option<TiltifyAccessToken>,
|
||||
rate_limited_until: Instant,
|
||||
rate_limit_backoff: Duration,
|
||||
}
|
||||
|
||||
impl TiltifyState {
|
||||
fn set_fetch_backoff(&mut self) {
|
||||
self.rate_limited_until = Instant::now() + self.rate_limit_backoff;
|
||||
self.rate_limit_backoff = self
|
||||
.rate_limit_backoff
|
||||
.saturating_mul(2)
|
||||
.min(TILTIFY_MAX_RATE_LIMIT_BACKOFF);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -28,38 +45,34 @@ struct TiltifyTokenResponse {
|
||||
expires_in: u64,
|
||||
}
|
||||
|
||||
const TILTIFY_INITIAL_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(60);
|
||||
const TILTIFY_MAX_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(15 * 60);
|
||||
|
||||
impl TiltifyClient {
|
||||
pub fn new(http: HttpClient) -> Self {
|
||||
Self {
|
||||
http,
|
||||
token: Mutex::new(None),
|
||||
state: Mutex::new(TiltifyState {
|
||||
token: None,
|
||||
rate_limited_until: Instant::now(),
|
||||
rate_limit_backoff: TILTIFY_INITIAL_RATE_LIMIT_BACKOFF,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn access_token(&self) -> eyre::Result<String> {
|
||||
let mut token = self.token.lock().await;
|
||||
pub async fn access_token(&self) -> Result<String> {
|
||||
let mut state = self.state.lock().await;
|
||||
|
||||
if let Some(token) = token.as_ref()
|
||||
if let Some(token) = state.token.as_ref()
|
||||
&& token.expires_at > Instant::now()
|
||||
{
|
||||
return Ok(token.access_token.clone());
|
||||
}
|
||||
|
||||
let response = self.fetch_access_token().await?;
|
||||
let expires_at = Instant::now()
|
||||
+ Duration::from_secs(response.expires_in)
|
||||
.saturating_sub(Duration::from_secs(60));
|
||||
let access_token = response.access_token;
|
||||
if state.rate_limited_until > Instant::now() {
|
||||
return Err(eyre!("waiting for rate limit to reset"));
|
||||
}
|
||||
|
||||
*token = Some(TiltifyAccessToken {
|
||||
access_token: access_token.clone(),
|
||||
expires_at,
|
||||
});
|
||||
|
||||
Ok(access_token)
|
||||
}
|
||||
|
||||
async fn fetch_access_token(&self) -> eyre::Result<TiltifyTokenResponse> {
|
||||
if ENV.TILTIFY_CLIENT_ID.is_empty()
|
||||
|| ENV.TILTIFY_CLIENT_SECRET.is_empty()
|
||||
{
|
||||
@@ -79,13 +92,33 @@ impl TiltifyClient {
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.wrap_err("fetching OAuth token")?
|
||||
.error_for_status()
|
||||
.wrap_err("fetching OAuth token")?
|
||||
.inspect_err(|_| state.set_fetch_backoff())
|
||||
.wrap_err("fetching OAuth token")?;
|
||||
|
||||
let response = match response.error_for_status() {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
state.set_fetch_backoff();
|
||||
return Err(error).wrap_err("fetching OAuth token");
|
||||
}
|
||||
};
|
||||
|
||||
let response = response
|
||||
.json::<TiltifyTokenResponse>()
|
||||
.await
|
||||
.wrap_err("parsing OAuth token response")?;
|
||||
|
||||
Ok(response)
|
||||
let expires_at = Instant::now()
|
||||
+ Duration::from_secs(response.expires_in)
|
||||
.saturating_sub(Duration::from_secs(60));
|
||||
let access_token = response.access_token;
|
||||
|
||||
state.token = Some(TiltifyAccessToken {
|
||||
access_token: access_token.clone(),
|
||||
expires_at,
|
||||
});
|
||||
state.rate_limit_backoff = TILTIFY_INITIAL_RATE_LIMIT_BACKOFF;
|
||||
|
||||
Ok(access_token)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user