#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
missing_docs,
rustdoc::broken_intra_doc_links,
missing_debug_implementations
)]
use std::time::{Duration, Instant};
use futures::{future::BoxFuture, FutureExt};
use log::{info, trace};
use reqwest::{header::LOCATION, ClientBuilder, Response, StatusCode};
use crate::response::external::Root;
#[cfg(test)]
mod test;
mod errors;
pub use errors::Error;
pub use reqwest::Client;
mod response;
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
#[doc(no_inline)]
pub use chrono;
pub use response::{external::Protocol, internal::*};
type Result<T> = std::result::Result<T, Error>;
pub(crate) const FILE_PATH: &str = "core/os/x86_64/core.db.tar.gz";
pub async fn get_mirrors(source: &str, with_timeout: Option<u64>) -> Result<ArchLinux> {
let response = get_response(source, with_timeout).await?;
let root: Root = response.json().await?;
let body = ArchLinux::from(root);
let count = body.countries.len();
info!("located mirrors from {count} countries");
Ok(body)
}
async fn get_response(source: &str, with_timeout: Option<u64>) -> Result<Response> {
trace!("creating http client");
let client = get_client(with_timeout)?;
trace!("sending request");
let response = client.get(source).send().await?;
Ok(response)
}
pub async fn get_mirrors_with_raw(
source: &str,
with_timeout: Option<u64>,
) -> Result<(ArchLinux, String)> {
let response = get_response(source, with_timeout).await?;
deserialise_mirrors(response).await
}
async fn deserialise_mirrors(response: Response) -> Result<(ArchLinux, String)> {
let root: Root = response.json().await?;
let value = serde_json::to_string(&root)?;
Ok((ArchLinux::from(root), value))
}
pub async fn get_mirrors_with_client(source: &str, client: Client) -> Result<(ArchLinux, String)> {
let response = client.get(source).send().await?;
deserialise_mirrors(response).await
}
pub fn parse_local(contents: &str) -> Result<ArchLinux> {
let vals = ArchLinux::from(serde_json::from_str::<Root>(contents)?);
Ok(vals)
}
pub fn get_client(with_timeout: Option<u64>) -> Result<Client> {
let timeout = with_timeout.map(Duration::from_secs);
let mut client_builder = ClientBuilder::new();
if let Some(timeout) = timeout {
client_builder = client_builder.timeout(timeout).connect_timeout(timeout);
}
Ok(client_builder.build()?)
}
pub fn rate_mirror(url: String, client: Client) -> BoxFuture<'static, Result<(Duration, String)>> {
async move {
let uri = format!("{url}{FILE_PATH}");
let now = Instant::now();
let response = client.get(&uri).send().await?;
if response.status() == StatusCode::OK {
Ok((now.elapsed(), url))
} else if response.status() == StatusCode::MOVED_PERMANENTLY {
if let Some(new_uri) = response.headers().get(LOCATION) {
let new_url = String::from_utf8_lossy(new_uri.as_bytes()).replace(FILE_PATH, "");
rate_mirror(new_url.to_string(), client.clone()).await
} else {
Err(Error::Rate {
qualified_url: uri,
url,
status_code: response.status(),
})
}
} else {
Err(Error::Rate {
qualified_url: uri,
url,
status_code: response.status(),
})
}
}
.boxed()
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
pub async fn get_last_sync(
mirror: impl Into<String>,
client: Client,
) -> Result<(chrono::DateTime<chrono::Utc>, String)> {
let mirror = mirror.into();
let lastsync_url = format!("{mirror}lastsync");
let timestamp = client
.get(&lastsync_url)
.send()
.await
.map_err(|e| Error::Request(e.to_string()))?
.text()
.await?;
let result = chrono::NaiveDateTime::parse_from_str(×tamp, "%s")
.map(|res| chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(res, chrono::Utc))
.map_err(Error::TimeError)?;
Ok((result, mirror))
}