Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions packages/app-lib/src/util/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ use crate::LAUNCHER_USER_AGENT;
use crate::event::LoadingBarId;
use crate::event::emit::emit_loading;
use bytes::Bytes;
use chrono::DateTime;
use reqwest::Method;
use serde::de::DeserializeOwned;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::time::{self};
use std::time::{self, Duration};
use tokio::sync::Semaphore;
use tokio::{fs::File, io::AsyncWriteExt};
use tokio::{fs::File, io::AsyncWriteExt, time::sleep};

#[derive(Debug)]
pub struct IoSemaphore(pub Semaphore);
Expand Down Expand Up @@ -77,13 +78,14 @@ pub async fn fetch_advanced(
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
) -> crate::Result<Bytes> {
let _permit = semaphore.0.acquire().await?;
let is_modrinth = url.starts_with("https://cdn.modrinth.com")
|| url.starts_with(env!("MODRINTH_API_URL"))
|| url.starts_with(env!("MODRINTH_API_URL_V3"));

let creds = if header
.as_ref()
.is_none_or(|x| &*x.0.to_lowercase() != "authorization")
&& (url.starts_with("https://cdn.modrinth.com")
|| url.starts_with(env!("MODRINTH_API_URL"))
|| url.starts_with(env!("MODRINTH_API_URL_V3")))
&& is_modrinth
{
crate::state::ModrinthCredentials::get_active(exec).await?
} else {
Expand Down Expand Up @@ -116,6 +118,40 @@ pub async fn fetch_advanced(
|| resp.status().is_server_error()
{
let backup_error = resp.error_for_status_ref().unwrap_err();
if resp.status() == 429 && attempt <= FETCH_ATTEMPTS {
if is_modrinth // x-ratelimit-reset is not portable across different servers
&& let Some(reset_header) =
resp.headers().get("X-Ratelimit-Reset")
&& let Ok(seconds) = reset_header.to_str()
&& let Ok(seconds) = seconds.parse::<u64>()
{
sleep(Duration::from_secs(seconds)).await;
continue;
} else if let Some(retry_header) =
resp.headers().get("Retry-After")
&& let Ok(retry) = retry_header.to_str()
{
if let Ok(seconds) = retry.parse::<u64>() {
// when retry-after retruns a delay in seconds
sleep(Duration::from_secs(seconds)).await;
continue;
} else if let Ok(date) =
DateTime::parse_from_rfc2822(retry)
// when retry-after returns an http date
{
let now = chrono::Utc::now();
// Convert now to the same timezone as date
let now_fixed =
now.with_timezone(date.offset());
let wait_duration = date - now_fixed;
sleep(Duration::from_secs(
wait_duration.num_seconds() as u64,
))
.await;
continue;
}
}
}
if let Ok(error) = resp.json().await {
return Err(ErrorKind::LabrinthError(error).into());
}
Expand Down