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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if resp.status() == 429 && attempt <= FETCH_ATTEMPTS {
if resp.status() == StatusCode::TOO_MANY_REQUESTS && attempt <= FETCH_ATTEMPTS {

make the HTTP status code explicit

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,
))
Comment on lines +147 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use tokio::time::sleep_until instead

.await;
continue;
}
}
}
if let Ok(error) = resp.json().await {
return Err(ErrorKind::LabrinthError(error).into());
}
Expand Down