spotify-launcher 0.5.2

Client for spotify's apt repository in Rust for Arch Linux
Documentation
use crate::errors::*;
use bytes::Bytes;
use reqwest::Response;
use std::time::Duration;
use tokio::time;

pub struct Client {
    client: reqwest::Client,
    timeout: Option<Duration>,
}

impl Client {
    #[inline]
    pub fn new(timeout: Option<u64>) -> Result<Client> {
        let (connect_timeout, timeout) = if let Some(timeout) = timeout {
            if timeout > 0 {
                let timeout = Duration::from_secs(timeout);
                (Some(timeout), Some(timeout))
            } else {
                (None, None)
            }
        } else {
            (Some(Duration::from_secs(10)), Some(Duration::from_secs(30)))
        };

        let mut builder = reqwest::ClientBuilder::new()
            .user_agent(format!("spotify-launcher/{}", env!("CARGO_PKG_VERSION")))
            .redirect(reqwest::redirect::Policy::limited(8));

        if let Some(timeout) = connect_timeout {
            builder = builder.connect_timeout(timeout);
        }

        let client = builder.build().context("Failed to create http client")?;

        Ok(Client { client, timeout })
    }

    async fn send_get(&self, url: &str) -> Result<Response> {
        let future = async {
            let resp = self
                .client
                .get(url)
                .send()
                .await
                .context("Failed to send http request")?;

            let status = resp.status();
            if !status.is_success() {
                bail!("Unexpected http status code: {:?}", status);
            }

            Ok(resp)
        };
        if let Some(timeout) = self.timeout {
            time::timeout(timeout, future)
                .await
                .context("Request timed out")?
        } else {
            future.await
        }
    }

    pub async fn fetch(&self, url: &str) -> Result<Vec<u8>> {
        debug!("Fetching {:?}...", url);
        let resp = self.send_get(url).await?;

        let body = resp.bytes();
        let body = if let Some(timeout) = self.timeout {
            time::timeout(timeout, body)
                .await
                .context("Reading http response timed out")?
        } else {
            body.await
        }
        .context("Failed to read http response")?;

        debug!("Fetched {} bytes", body.len());
        Ok(body.to_vec())
    }

    pub async fn fetch_stream(&self, url: &str) -> Result<Download> {
        debug!("Downloading {:?}...", url);
        let resp = self.send_get(url).await?;

        let total = resp.content_length().unwrap_or(0);

        Ok(Download {
            resp,
            timeout: self.timeout,
            progress: 0,
            total,
        })
    }
}

pub struct Download {
    resp: reqwest::Response,
    timeout: Option<Duration>,
    pub progress: u64,
    pub total: u64,
}

impl Download {
    pub async fn chunk(&mut self) -> Result<Option<Bytes>> {
        let future = self.resp.chunk();
        let bytes = if let Some(timeout) = self.timeout {
            if let Ok(bytes) = time::timeout(timeout, future).await {
                bytes?
            } else {
                bail!("Download timed out due to inactivity");
            }
        } else {
            future.await?
        };
        if let Some(bytes) = bytes {
            self.progress += bytes.len() as u64;
            Ok(Some(bytes))
        } else {
            Ok(None)
        }
    }
}