From 19126f49817e9ee2d64664c9e98962cd5d78f1b6 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Fri, 23 Jul 2021 22:29:28 -0400 Subject: [PATCH] Add retry logic to API calls Since apparently these can 500 occasionally. --- src/nexus_api/download_link.rs | 47 ++++++++++++++++++++++------------ src/nexus_api/files.rs | 44 +++++++++++++++++++------------ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/nexus_api/download_link.rs b/src/nexus_api/download_link.rs index 269be22..0907039 100644 --- a/src/nexus_api/download_link.rs +++ b/src/nexus_api/download_link.rs @@ -5,6 +5,7 @@ use serde_json::Value; use std::{env, time::Duration}; use tempfile::tempfile; use tokio::fs::File; +use tokio::time::sleep; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::{info, instrument, warn}; @@ -17,23 +18,36 @@ pub struct DownloadLinkResponse { #[instrument(skip(client))] pub async fn get(client: &Client, mod_id: i32, file_id: i64) -> Result { - let res = client - .get(format!( - "https://api.nexusmods.com/v1/games/{}/mods/{}/files/{}/download_link.json", - GAME_NAME, mod_id, file_id - )) - .header("accept", "application/json") - .header("apikey", env::var("NEXUS_API_KEY")?) - .header("user-agent", USER_AGENT) - .send() - .await? - .error_for_status()?; + for attempt in 1..=3 { + let res = match client + .get(format!( + "https://api.nexusmods.com/v1/games/{}/mods/{}/files/{}/download_link.json", + GAME_NAME, mod_id, file_id + )) + .header("accept", "application/json") + .header("apikey", env::var("NEXUS_API_KEY")?) + .header("user-agent", USER_AGENT) + .send() + .await? + .error_for_status() + { + Ok(res) => res, + Err(err) => { + warn!(error = %err, attempt, "Failed to get download link for file, trying again after 1 second"); + sleep(std::time::Duration::from_secs(1)).await; + continue; + } + }; - info!(status = %res.status(), "fetched file download link from API"); - let wait = rate_limit_wait_duration(&res)?; - let json = res.json::().await?; + info!(status = %res.status(), "fetched file download link from API"); + let wait = rate_limit_wait_duration(&res)?; + let json = res.json::().await?; - Ok(DownloadLinkResponse { wait, json }) + return Ok(DownloadLinkResponse { wait, json }); + } + Err(anyhow!( + "Failed to get download link for file in three attempts" + )) } impl DownloadLinkResponse { @@ -76,7 +90,8 @@ impl DownloadLinkResponse { return Ok(tokio_file); } Err(err) => { - warn!(error = %err, attempt, "Failed to download file, trying again"); + warn!(error = %err, attempt, "Failed to download file, trying again after 1 second"); + sleep(std::time::Duration::from_secs(1)).await; } } } diff --git a/src/nexus_api/files.rs b/src/nexus_api/files.rs index 5faaddf..6f5b2b3 100644 --- a/src/nexus_api/files.rs +++ b/src/nexus_api/files.rs @@ -3,7 +3,8 @@ use chrono::NaiveDateTime; use reqwest::Client; use serde_json::Value; use std::{env, time::Duration}; -use tracing::{info, instrument}; +use tokio::time::sleep; +use tracing::{info, instrument, warn}; use super::{rate_limit_wait_duration, GAME_NAME, USER_AGENT}; @@ -26,23 +27,34 @@ pub struct ApiFile<'a> { #[instrument(skip(client))] pub async fn get(client: &Client, nexus_mod_id: i32) -> Result { - let res = client - .get(format!( - "https://api.nexusmods.com/v1/games/{}/mods/{}/files.json", - GAME_NAME, nexus_mod_id - )) - .header("accept", "application/json") - .header("apikey", env::var("NEXUS_API_KEY")?) - .header("user-agent", USER_AGENT) - .send() - .await? - .error_for_status()?; + for attempt in 1..=3 { + let res = match client + .get(format!( + "https://api.nexusmods.com/v1/games/{}/mods/{}/files.json", + GAME_NAME, nexus_mod_id + )) + .header("accept", "application/json") + .header("apikey", env::var("NEXUS_API_KEY")?) + .header("user-agent", USER_AGENT) + .send() + .await? + .error_for_status() + { + Ok(res) => res, + Err(err) => { + warn!(error = %err, attempt, "Failed to get files for mod, trying again after 1 second"); + sleep(std::time::Duration::from_secs(1)).await; + continue; + } + }; - info!(status = %res.status(), "fetched files for mod from API"); - let wait = rate_limit_wait_duration(&res)?; - let json = res.json::().await?; + info!(status = %res.status(), "fetched files for mod from API"); + let wait = rate_limit_wait_duration(&res)?; + let json = res.json::().await?; - Ok(FilesResponse { wait, json }) + return Ok(FilesResponse { wait, json }); + } + Err(anyhow!("Failed to get files for mod in three attempts")) } impl FilesResponse {