pakker/src/rate_limiter.rs
NotAShelf a1357b2501
treewide: general cleanup
Finally had the time to clean up after myself. Does a bunch of things,
without breakage as far as I'm aware. I've removed around 20 unnecessary
clones, and simplified the architechture a little bit. 

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4d22337b997a3bf5b0593e6068cd1bd86a6a6964
2026-03-03 23:35:05 +03:00

119 lines
2.9 KiB
Rust

use std::{
collections::HashMap,
sync::Arc,
time::{Duration, Instant},
};
use tokio::sync::Mutex;
use crate::error::Result;
#[derive(Clone)]
pub struct RateLimiter {
inner: Arc<Mutex<RateLimiterInner>>,
}
struct RateLimiterInner {
requests: HashMap<String, Vec<Instant>>,
config: RateLimitConfig,
}
#[derive(Clone, Debug)]
pub struct RateLimitConfig {
pub modrinth_requests_per_min: u32,
pub modrinth_burst: u32,
pub curseforge_requests_per_min: u32,
pub curseforge_burst: u32,
pub github_requests_per_min: u32,
pub github_burst: u32,
pub default_requests_per_min: u32,
pub default_burst: u32,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
modrinth_requests_per_min: 100,
modrinth_burst: 10,
curseforge_requests_per_min: 60,
curseforge_burst: 5,
github_requests_per_min: 50,
github_burst: 5,
default_requests_per_min: 30,
default_burst: 3,
}
}
}
impl RateLimiter {
pub fn new(config: Option<RateLimitConfig>) -> Self {
Self {
inner: Arc::new(Mutex::new(RateLimiterInner {
requests: HashMap::new(),
config: config.unwrap_or_default(),
})),
}
}
pub async fn acquire(&self, platform: &str) -> Result<()> {
let (rate, burst) = {
let inner = self.inner.lock().await;
match platform.to_lowercase().as_str() {
"modrinth" => {
(
inner.config.modrinth_requests_per_min,
inner.config.modrinth_burst,
)
},
"curseforge" => {
(
inner.config.curseforge_requests_per_min,
inner.config.curseforge_burst,
)
},
"github" => {
(
inner.config.github_requests_per_min,
inner.config.github_burst,
)
},
_ => {
(
inner.config.default_requests_per_min,
inner.config.default_burst,
)
},
}
};
let interval = Duration::from_secs(60) / rate.max(1);
loop {
let mut inner = self.inner.lock().await;
let now = Instant::now();
let platform_requests =
inner.requests.entry(platform.to_owned()).or_default();
platform_requests
.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
if platform_requests.len() >= burst as usize
&& let Some(oldest) = platform_requests.first()
{
let wait_time = interval.saturating_sub(now.duration_since(*oldest));
if wait_time > Duration::ZERO {
drop(inner);
tokio::time::sleep(wait_time).await;
continue;
}
}
platform_requests.push(Instant::now());
return Ok(());
}
}
pub async fn wait_for(&self, platform: &str) {
let _ = self.acquire(platform).await;
}
}