use crate::error::{MhoError, Result};
use crate::{log_debug, log_error, log_info, log_warn};
use reqwest::Client;
use std::sync::OnceLock;
use std::time::Duration;
pub fn new_client() -> &'static Client {
static CLIENT: OnceLock<Client> = OnceLock::new();
CLIENT.get_or_init(|| {
reqwest::Client::builder()
.timeout(Duration::from_secs(600))
.connect_timeout(Duration::from_secs(30))
.tcp_keepalive(Duration::from_secs(60))
.build()
.expect("Failed to create HTTP client")
})
}
const INITIAL_DELAY_MS: u64 = 1000; const MAX_DELAY_MS: u64 = 30000; const MAX_RETRIES: u32 = 3;
fn calculate_delay(attempt: u32) -> Duration {
if attempt == 0 {
return Duration::from_millis(0);
}
let delay_ms = INITIAL_DELAY_MS * 2u64.pow(attempt - 1);
let capped_delay = delay_ms.min(MAX_DELAY_MS);
Duration::from_millis(capped_delay)
}
pub async fn download_text(url: &str) -> Result<String> {
log_info!("Starting text download from {}", url);
let mut attempt = 0;
let mut last_error = None;
while attempt <= MAX_RETRIES {
if attempt > 0 {
let delay = calculate_delay(attempt);
log_warn!(
"Retry attempt {} after {:?} delay for {}",
attempt,
delay,
url
);
tokio::time::sleep(delay).await;
}
match download_text_attempt(url).await {
Ok(text) => {
log_info!("Text download completed successfully: {}", url);
return Ok(text);
}
Err(e) => {
log_error!("Download attempt {} failed: {}", attempt + 1, e);
last_error = Some(e);
attempt += 1;
}
}
}
Err(last_error
.unwrap_or_else(|| MhoError::Download("Download failed after all retries".to_string())))
}
async fn download_text_attempt(url: &str) -> Result<String> {
log_debug!("Sending HTTP GET request to {}", url);
let response = new_client()
.get(url)
.send()
.await
.map_err(|e| MhoError::Download(format!("HTTP request failed: {}", e)))?;
if !response.status().is_success() {
return Err(MhoError::Download(format!(
"HTTP request failed with status: {}",
response.status()
)));
}
log_debug!("Reading response body as text");
let text = response
.text()
.await
.map_err(|e| MhoError::Download(format!("Failed to read response body: {}", e)))?;
Ok(text)
}