mangadex-api 4.2.1

SDK for the MangaDex API
Documentation
use std::sync::Arc;

use crate::{error::Error, Result};
use bytes::Bytes;
use bytes::BytesMut;
use mangadex_api_schema::v5::AtHomeServer;
use reqwest::{Client, Response};
use tokio::pin;
use tokio::time::Instant;
use tokio_stream::StreamExt;
use url::Url;

use super::AtHomeReport;
use super::DownloadMode;

use super::DownloadElement;

#[derive(Clone)]
#[non_exhaustive]
pub struct AtHomePreDownloadImageData {
    pub http_client: Client,
    pub filename: String,
    pub quality: DownloadMode,
    pub at_home: Arc<AtHomeServer>,
    pub report: bool,
}

impl AtHomePreDownloadImageData {
    async fn report(
        &self,
        start: Instant,
        page_url: Url,
        bytes: usize,
        success: bool,
        cached: bool,
    ) {
        if self.report {
            let end = Instant::now();
            let _ = AtHomeReport {
                url: page_url,
                success,
                cached,
                bytes,
                duration: end.duration_since(start).as_millis(),
            }
            .send(&self.http_client)
            .await;
        }
    }
    pub fn build_page_url(&self) -> Result<Url> {
        match self.at_home.base_url.join(&format!(
            "/{quality_mode}/{chapter_hash}/{page_filename}",
            quality_mode = Into::<String>::into(self.quality),
            chapter_hash = self.at_home.chapter.hash,
            page_filename = self.filename
        )) {
            Ok(d) => Ok(d),
            Err(e) => Result::Err(Error::ParseUrlError(e)),
        }
    }
    pub async fn download(&self) -> DownloadElement {
        self.download_with_checker(|_, _| false).await
    }
    pub async fn download_with_checker<C>(&self, mut should_skip: C) -> DownloadElement
    where
        C: FnMut(&Self, &Response) -> bool,
    {
        let page_url = match self.build_page_url() {
            Ok(o) => o,
            Err(e) => return (self.filename.clone(), Err(e)),
        };
        let page_url_clone = page_url.clone();
        let start = tokio::time::Instant::now();
        let res: Response = match self.http_client.get(page_url).send().await {
            Ok(d) => d,
            Err(e) => {
                self.report(start, page_url_clone, 0, false, false).await;
                return (self.filename.clone(), Err(Error::RequestError(e)));
            }
        };
        if should_skip(self, &res) {
            return (
                self.filename.clone(),
                Err(Error::SkippedDownload(self.filename.clone())),
            );
        }
        let is_cache: bool = match res.headers().get("X-Cache") {
            None => false,
            Some(d) => match d.to_str() {
                Ok(val) => val.starts_with("HIT"),
                Err(_) => false,
            },
        };

        let mut bytes: BytesMut = BytesMut::new();
        let byte_stream = res.bytes_stream();
        pin!(byte_stream);
        while let Some(chunk) = byte_stream.next().await {
            match chunk {
                Ok(chunk_bytes) => {
                    bytes.extend(chunk_bytes);
                }
                Err(chunk_error) => {
                    self.report(start, page_url_clone, bytes.len(), false, is_cache)
                        .await;
                    return (self.filename.clone(), Err(Error::RequestError(chunk_error)));
                }
            }
        }
        self.report(start, page_url_clone, bytes.len(), true, is_cache)
            .await;
        (self.filename.clone(), Ok(Bytes::from(bytes)))
    }
}