diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index fafba22152..678294327c 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -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); @@ -77,13 +78,14 @@ pub async fn fetch_advanced( exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result { 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 { @@ -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::() + { + 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::() { + // 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()); }