use anyhow::{Context, Result};
use reqwest::Client;
use std::time::Duration;
pub fn create_client(timeout_secs: u64, user_agent: Option<&str>) -> Result<Client> {
let mut builder = Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.connect_timeout(Duration::from_secs(10))
.pool_max_idle_per_host(10)
.tcp_keepalive(Duration::from_secs(60));
if let Some(ua) = user_agent {
builder = builder.user_agent(ua);
} else {
builder = builder.user_agent(format!(
"smirrors/{} (Linux mirror tester)",
crate::VERSION
));
}
builder
.build()
.context("Failed to create HTTP client")
}
pub fn parse_size(size_str: &str) -> Result<usize> {
let size_str = size_str.trim().to_uppercase();
let (number_part, unit) = if size_str.ends_with("GB") {
(size_str.trim_end_matches("GB"), 1024 * 1024 * 1024)
} else if size_str.ends_with("MB") {
(size_str.trim_end_matches("MB"), 1024 * 1024)
} else if size_str.ends_with("KB") {
(size_str.trim_end_matches("KB"), 1024)
} else if size_str.ends_with("B") {
(size_str.trim_end_matches("B"), 1)
} else {
(size_str.as_str(), 1)
};
let number: f64 = number_part
.trim()
.parse()
.context("Invalid size format")?;
Ok((number * unit as f64) as usize)
}
pub fn parse_duration(duration_str: &str) -> Result<u64> {
let duration_str = duration_str.trim().to_lowercase();
let (number_part, multiplier) = if duration_str.ends_with("ms") {
let num_str = duration_str.trim_end_matches("ms");
let number: f64 = num_str
.trim()
.parse()
.context("Invalid duration format")?;
let secs = (number / 1000.0).max(0.0);
return Ok(if secs < 1.0 && secs > 0.0 { 1 } else { secs.round() as u64 });
} else if duration_str.ends_with("h") {
(duration_str.trim_end_matches('h'), 3600)
} else if duration_str.ends_with("m") {
(duration_str.trim_end_matches('m'), 60)
} else if duration_str.ends_with("s") {
(duration_str.trim_end_matches('s'), 1)
} else if duration_str.ends_with("d") {
(duration_str.trim_end_matches('d'), 86400)
} else {
(duration_str.as_str(), 1)
};
let number: u64 = number_part
.trim()
.parse()
.context("Invalid duration format")?;
Ok(number * multiplier)
}
pub fn format_size(bytes: usize) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as usize, UNITS[unit_index])
} else {
format!("{:.2} {}", size, UNITS[unit_index])
}
}
pub fn format_duration(secs: u64) -> String {
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m", secs / 60)
} else if secs < 86400 {
format!("{}h", secs / 3600)
} else {
format!("{}d", secs / 86400)
}
}
pub fn extract_hostname(url: &str) -> Result<String> {
let parsed = url::Url::parse(url)?;
parsed
.host_str()
.map(|s| s.to_string())
.context("URL has no hostname")
}
pub async fn check_url_reachable(url: &url::Url) -> Result<bool> {
let client = create_client(10, None)?;
match client.head(url.as_str()).send().await {
Ok(response) => Ok(response.status().is_success()),
Err(e) => Err(anyhow::anyhow!("Failed to reach URL: {}", e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_size() {
assert_eq!(parse_size("1MB").unwrap(), 1024 * 1024);
assert_eq!(parse_size("500KB").unwrap(), 500 * 1024);
assert_eq!(parse_size("1GB").unwrap(), 1024 * 1024 * 1024);
assert_eq!(parse_size("100B").unwrap(), 100);
assert_eq!(parse_size("2.5MB").unwrap(), (2.5 * 1024.0 * 1024.0) as usize);
}
#[test]
fn test_parse_duration() {
assert_eq!(parse_duration("1h").unwrap(), 3600);
assert_eq!(parse_duration("30m").unwrap(), 1800);
assert_eq!(parse_duration("45s").unwrap(), 45);
assert_eq!(parse_duration("2d").unwrap(), 172800);
}
#[test]
fn test_format_size() {
assert_eq!(format_size(1024), "1.00 KB");
assert_eq!(format_size(1024 * 1024), "1.00 MB");
assert_eq!(format_size(500), "500 B");
}
#[test]
fn test_format_duration() {
assert_eq!(format_duration(45), "45s");
assert_eq!(format_duration(120), "2m");
assert_eq!(format_duration(3600), "1h");
assert_eq!(format_duration(86400), "1d");
}
#[test]
fn test_extract_hostname() {
assert_eq!(
extract_hostname("https://example.com/path").unwrap(),
"example.com"
);
assert_eq!(
extract_hostname("http://mirror.site.org:8080/").unwrap(),
"mirror.site.org"
);
}
}