bytefetch 0.2.0

A Rust library that makes HTTP file downloads easier to implement.
Documentation
use reqwest::Client;
use std::{
    marker::PhantomData,
    sync::{Arc, Mutex},
    time::Duration,
};
use tokio_util::sync::CancellationToken;

use crate::{
    HttpDownloader,
    http::{
        HttpDownloadConfig, HttpDownloadMode, ProgressState, Status, builder_utils,
        info::HttpDownloadInfo,
    },
};

pub struct ClientRequired;
pub struct FromStateBuilder;

pub struct HttpDownloaderFromStateBuilder<State = FromStateBuilder> {
    filename: String,
    client: Option<Client>,
    state: PhantomData<State>,
    timeout: Option<Duration>,
}

impl HttpDownloaderFromStateBuilder<ClientRequired> {
    pub fn client(mut self, client: Client) -> HttpDownloaderFromStateBuilder<FromStateBuilder> {
        self.client = Some(client);
        HttpDownloaderFromStateBuilder {
            client: self.client,
            state: PhantomData::<FromStateBuilder>,
            filename: self.filename,
            timeout: self.timeout,
        }
    }
}

impl HttpDownloaderFromStateBuilder {
    pub(super) fn new(filename: String) -> HttpDownloaderFromStateBuilder<ClientRequired> {
        HttpDownloaderFromStateBuilder::<ClientRequired> {
            client: None,
            state: PhantomData::<ClientRequired>,
            filename,
            timeout: None,
        }
    }

    fn generate_info(
        filename: String,
        content_length: Option<u64>,
        tasks_count: u8,
    ) -> HttpDownloadInfo {
        HttpDownloadInfo::default()
            .set_filename(filename)
            .set_content_length(content_length)
            .set_is_resumable(tasks_count > 0)
    }

    fn get_byte_ranges_and_downloaded_bytes(
        config: &HttpDownloadConfig,
        mode: &HttpDownloadMode,
        state: &ProgressState,
    ) -> (Vec<(u64, u64)>, u64) {
        match mode {
            HttpDownloadMode::NonResumable => (vec![], 0),
            HttpDownloadMode::ResumableStream => {
                let progress = state.get_progress(0);
                (vec![(progress, 0)], progress)
            }
            HttpDownloadMode::ResumableMultithread => {
                let mut byte_ranges = vec![];
                let mut downloaded_bytes = 0;
                for index in 0..config.tasks_count as usize {
                    let (start, end) = builder_utils::calculate_part_range(
                        config.split_result.unwrap(),
                        index as u64,
                    );
                    let start_offset = state.get_progress(index);
                    byte_ranges.push((start_offset, end));
                    downloaded_bytes += start_offset - start;
                }
                (byte_ranges, downloaded_bytes)
            }
        }
    }

    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    pub fn build(self) -> HttpDownloader {
        let mut url = String::new();
        let mut content_length = None;
        let mut tasks_count = 0;
        let state = ProgressState::load(
            &self.filename,
            &mut url,
            &mut content_length,
            &mut tasks_count,
        );

        let info = Self::generate_info(self.filename, content_length, tasks_count);
        let mode = builder_utils::determine_mode(tasks_count, &info);
        let mut config = HttpDownloadConfig::default()
            .set_tasks_count(tasks_count)
            .set_timeout(self.timeout)
            .mark_resumed();
        config.split_result = builder_utils::try_split_content(&mode, &content_length, tasks_count);

        let (byte_ranges, number) =
            Self::get_byte_ranges_and_downloaded_bytes(&config, &mode, &state);

        info.add_to_downloaded_bytes(number);

        HttpDownloader {
            client: Arc::new(self.client.unwrap()),
            raw_url: Arc::new(url),
            info,
            mode,
            config,
            byte_ranges,
            status: Arc::new(Mutex::new(Status::Pending)),
            token: CancellationToken::new(),
        }
    }
}