rustup_available_packages/
downloader.rs

1use super::skip_errors::SkipMissingExt;
2use crate::{
3    cache::FsCache,
4    manifest::Manifest,
5    source::{DefaultSource, SourceInfo},
6    Error,
7};
8use chrono::{Duration, NaiveDate};
9use std::{io, iter};
10
11/// Manifests downloader and parser.
12pub struct Downloader<S> {
13    client: reqwest::blocking::Client,
14    source: S,
15    cache: FsCache,
16    skip_missing_days: usize,
17}
18
19impl<'a> Downloader<DefaultSource<'a>> {
20    /// Create a new instance of the [`Downloader`] with a [`DefaultSource`].
21    pub fn with_default_source(channel: &'a str) -> Self {
22        Self::new(DefaultSource::new(channel))
23    }
24}
25
26impl<S> Downloader<S> {
27    /// Create a new instance of the [`Downloader`] with a provided instance of [`SourceInfo`].
28    pub fn new(source: S) -> Self {
29        Downloader {
30            client: reqwest::blocking::Client::new(),
31            source,
32            cache: FsCache::noop(),
33            skip_missing_days: 0,
34        }
35    }
36}
37
38impl<S> Downloader<S>
39where
40    S: SourceInfo,
41{
42    /// Sets a cache for the downloader. By default a [`NoopCache`] is used.
43    pub fn set_cache(self, c: FsCache) -> Downloader<S> {
44        Downloader {
45            client: self.client,
46            source: self.source,
47            cache: c,
48            skip_missing_days: self.skip_missing_days,
49        }
50    }
51
52    /// Set to non zero if you want to silently skip days for which manifest files are missing.
53    /// Not more than `skip` days will be skipped.
54    /// Please not that this setting only affects the [`get_last_manifests`] method.
55    ///
56    /// Off (zero) by default.
57    pub fn skip_missing_days(self, skip: usize) -> Downloader<S> {
58        Downloader {
59            client: self.client,
60            source: self.source,
61            cache: self.cache,
62            skip_missing_days: skip,
63        }
64    }
65
66    /// Get latest available manifests for given `days`. If `days` is 0 or 1 only the latest
67    /// manifest is fetched.
68    ///
69    /// The returned vector is sorted in descending order of dates.
70    pub fn get_last_manifests(&self, days: usize) -> Result<Vec<Manifest>, Error> {
71        let latest = self.get_latest_manifest()?;
72        let latest_day = latest.date;
73        log::info!("Latest manifest is for {}", latest_day);
74        let rest = (1..days)
75            .filter_map(|day| latest_day.checked_sub_signed(Duration::days(day as i64)))
76            .map(|date| self.get_manifest(date))
77            .skip_missing(self.skip_missing_days);
78        iter::once(Ok(latest)).chain(rest).collect()
79    }
80
81    /// Gets manifest for a given date.
82    pub fn get_manifest(&self, day: NaiveDate) -> Result<Manifest, Error> {
83        if let Some(cached) = self.cache.get(day) {
84            return Ok(cached);
85        }
86        let manifest = self.get_manifest_by_url(self.source.make_manifest_url(day))?;
87        self.cache.store(&manifest);
88        Ok(manifest)
89    }
90
91    /// Gets manifest for a given date. If the `date` is `None`, the latest available manifest is
92    /// requested.
93    ///
94    /// This call is never cached.
95    pub fn get_latest_manifest(&self) -> Result<Manifest, Error> {
96        self.get_manifest_by_url(self.source.make_latest_manifest_url())
97    }
98
99    /// Fetches a manifest from a given url.
100    ///
101    /// This call is never cached.
102    pub fn get_manifest_by_url(&self, url: impl AsRef<str>) -> Result<Manifest, Error> {
103        let url = url.as_ref();
104        log::info!("Fetching a manifest from {}", url);
105        let mut response = self
106            .client
107            .get(url)
108            .send()
109            .map_err(|e| Error::Reqwest(e, url.into()))?;
110        if !response.status().is_success() {
111            return Err(Error::BadResponse(response.status(), url.into()));
112        }
113        let mut bytes = Vec::new();
114        io::copy(&mut response, &mut bytes).map_err(|e| Error::Io(e, url.into()))?;
115
116        toml::from_slice(&bytes).map_err(|e| Error::TomlDe(e, url.to_string()))
117    }
118}