1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(
3 missing_docs,
4 rustdoc::broken_intra_doc_links,
5 missing_debug_implementations
6)]
7
8use std::time::{Duration, Instant};
10
11use futures::{future::BoxFuture, FutureExt};
12use log::{info, trace};
13use reqwest::{header::LOCATION, ClientBuilder, Response, StatusCode};
14
15use crate::response::external::Root;
16
17#[cfg(test)]
18mod test;
19
20mod errors;
21pub use errors::Error;
22
23pub use reqwest::Client;
24
25mod response;
26#[cfg(feature = "time")]
27#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
28#[doc(no_inline)]
29pub use chrono;
30
31pub use response::{external::Protocol, internal::*};
32
33type Result<T> = std::result::Result<T, Error>;
34
35pub(crate) const FILE_PATH: &str = "core/os/x86_64/core.db.tar.gz";
36
37pub async fn get_mirrors(source: &str, with_timeout: Option<u64>) -> Result<ArchLinux> {
55 let response = get_response(source, with_timeout).await?;
56
57 let root: Root = response.json().await?;
58
59 let body = ArchLinux::from(root);
60 let count = body.countries.len();
61 info!("located mirrors from {count} countries");
62 Ok(body)
63}
64
65async fn get_response(source: &str, with_timeout: Option<u64>) -> Result<Response> {
66 trace!("creating http client");
67 let client = get_client(with_timeout)?;
68
69 trace!("sending request");
70 let response = client.get(source).send().await?;
71
72 Ok(response)
73}
74
75pub async fn get_mirrors_with_raw(
90 source: &str,
91 with_timeout: Option<u64>,
92) -> Result<(ArchLinux, String)> {
93 let response = get_response(source, with_timeout).await?;
94 deserialise_mirrors(response).await
95}
96
97async fn deserialise_mirrors(response: Response) -> Result<(ArchLinux, String)> {
98 let root: Root = response.json().await?;
99
100 let value = serde_json::to_string(&root)?;
101 Ok((ArchLinux::from(root), value))
102}
103
104pub async fn get_mirrors_with_client(source: &str, client: Client) -> Result<(ArchLinux, String)> {
107 let response = client.get(source).send().await?;
108 deserialise_mirrors(response).await
109}
110
111pub fn parse_local(contents: &str) -> Result<ArchLinux> {
128 let vals = ArchLinux::from(serde_json::from_str::<Root>(contents)?);
129 Ok(vals)
130}
131
132pub fn get_client(with_timeout: Option<u64>) -> Result<Client> {
148 let timeout = with_timeout.map(Duration::from_secs);
149
150 let mut client_builder = ClientBuilder::new();
151 if let Some(timeout) = timeout {
152 client_builder = client_builder.timeout(timeout).connect_timeout(timeout);
153 }
154
155 Ok(client_builder.build()?)
156}
157
158pub fn rate_mirror(url: String, client: Client) -> BoxFuture<'static, Result<(Duration, String)>> {
176 async move {
177 let uri = format!("{url}{FILE_PATH}");
178
179 let now = Instant::now();
180
181 let response = client.get(&uri).send().await?;
182
183 if response.status() == StatusCode::OK {
184 Ok((now.elapsed(), url))
185 } else if response.status() == StatusCode::MOVED_PERMANENTLY {
186 if let Some(new_uri) = response.headers().get(LOCATION) {
187 let new_url = String::from_utf8_lossy(new_uri.as_bytes()).replace(FILE_PATH, "");
188 rate_mirror(new_url.to_string(), client.clone()).await
189 } else {
190 Err(Error::Rate {
191 qualified_url: uri,
192 url,
193 status_code: response.status(),
194 })
195 }
196 } else {
197 Err(Error::Rate {
198 qualified_url: uri,
199 url,
200 status_code: response.status(),
201 })
202 }
203 }
204 .boxed()
205}
206
207#[cfg(feature = "time")]
225#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
226pub async fn get_last_sync(
227 mirror: impl Into<String>,
228 client: Client,
229) -> Result<(chrono::DateTime<chrono::Utc>, String)> {
230 let mirror = mirror.into();
231 let lastsync_url = format!("{mirror}lastsync");
232
233 let timestamp = client
234 .get(&lastsync_url)
235 .send()
236 .await
237 .map_err(|e| Error::Request(e.to_string()))?
238 .text()
239 .await?;
240
241 let result = chrono::NaiveDateTime::parse_from_str(×tamp, "%s")
242 .map(|res| chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(res, chrono::Utc))
243 .map_err(Error::TimeError)?;
244
245 Ok((result, mirror))
246}