use super::skip_errors::SkipMissingExt;
use crate::{
cache::FsCache,
manifest::Manifest,
source::{DefaultSource, SourceInfo},
Error,
};
use chrono::{Duration, NaiveDate};
use std::{io, iter};
pub struct Downloader<S> {
client: reqwest::blocking::Client,
source: S,
cache: FsCache,
skip_missing_days: usize,
}
impl<'a> Downloader<DefaultSource<'a>> {
pub fn with_default_source(channel: &'a str) -> Self {
Self::new(DefaultSource::new(channel))
}
}
impl<S> Downloader<S> {
pub fn new(source: S) -> Self {
Downloader {
client: reqwest::blocking::Client::new(),
source,
cache: FsCache::noop(),
skip_missing_days: 0,
}
}
}
impl<S> Downloader<S>
where
S: SourceInfo,
{
pub fn set_cache(self, c: FsCache) -> Downloader<S> {
Downloader {
client: self.client,
source: self.source,
cache: c,
skip_missing_days: self.skip_missing_days,
}
}
pub fn skip_missing_days(self, skip: usize) -> Downloader<S> {
Downloader {
client: self.client,
source: self.source,
cache: self.cache,
skip_missing_days: skip,
}
}
pub fn get_last_manifests(&self, days: usize) -> Result<Vec<Manifest>, Error> {
let latest = self.get_latest_manifest()?;
let latest_day = latest.date;
log::info!("Latest manifest is for {}", latest_day);
let rest = (1..days)
.filter_map(|day| latest_day.checked_sub_signed(Duration::days(day as i64)))
.map(|date| self.get_manifest(date))
.skip_missing(self.skip_missing_days);
iter::once(Ok(latest)).chain(rest).collect()
}
pub fn get_manifest(&self, day: NaiveDate) -> Result<Manifest, Error> {
if let Some(cached) = self.cache.get(day) {
return Ok(cached);
}
let manifest = self.get_manifest_by_url(self.source.make_manifest_url(day))?;
self.cache.store(&manifest);
Ok(manifest)
}
pub fn get_latest_manifest(&self) -> Result<Manifest, Error> {
self.get_manifest_by_url(self.source.make_latest_manifest_url())
}
pub fn get_manifest_by_url(&self, url: impl AsRef<str>) -> Result<Manifest, Error> {
let url = url.as_ref();
log::info!("Fetching a manifest from {}", url);
let mut response = self
.client
.get(url)
.send()
.map_err(|e| Error::Reqwest(e, url.into()))?;
if !response.status().is_success() {
return Err(Error::BadResponse(response.status(), url.into()));
}
let mut bytes = Vec::new();
io::copy(&mut response, &mut bytes).map_err(|e| Error::Io(e, url.into()))?;
toml::from_slice(&bytes).map_err(|e| Error::TomlDe(e, url.to_string()))
}
}