dn 0.1.3

这是一个使用 Rust 编写的多线程下载工具,支持断点下载和重试功能。通过该工具,你可以高效地下载文件,充分利用多线程提升下载速度,同时在下载过程中支持断点续传,保证下载的稳定性和可靠性。
use super::{
    parse::DownloadStatus,
    status::{ARGS, M, TEMP_DIR},
    utils::tools::{create_bar, create_client},
};
use anyhow::{anyhow, Result};
use futures::StreamExt;
use indicatif::ProgressBar;
use reqwest::header::RANGE;
use serde::{Deserialize, Serialize};
use tokio::{fs::OpenOptions, io::AsyncWriteExt};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Block {
    pub id: String,
    pub start: u64,
    pub end: u64,
    pub size: u64,
    pub status: DownloadStatus,
    pub retry: u8,
    pub max_retry: u8,
}

impl Block {
    pub fn new(id: String, start: u64, end: u64, size: u64, max_retry: u8) -> Self {
        Self {
            id,
            start,
            end,
            size,
            status: DownloadStatus::Started,
            retry: 0,
            max_retry,
        }
    }

    pub async fn download(&mut self) -> Result<()> {
        let bar = M.add(create_bar(self.size));
        loop {
            match self.status {
                DownloadStatus::Progress(p) => self.run(p, &bar).await?,
                DownloadStatus::Started => self.status = DownloadStatus::Progress(0),
                DownloadStatus::Completed => {
                    bar.set_message(format!("{} finished", self.id));
                    break;
                }
                DownloadStatus::Failed => {
                    bar.set_message(format!("{} failed!!", self.id));
                    break;
                }
            }
        }
        bar.finish();
        Ok(())
    }

    async fn run(&mut self, p: u64, bar: &ProgressBar) -> Result<()> {
        let client = create_client();
        let url = &ARGS.url;

        let range = format!("bytes={}-{}", self.start + p, self.end);
        match client.get(url).header(RANGE, range).send().await {
            Ok(res) => {
                bar.set_position(p);
                bar.set_message(format!("{} downling", self.id));
                let mut stream = res.bytes_stream();

                let file_path = TEMP_DIR.join(format!("{}.{}", ARGS.filename(), self.id));

                let mut file = OpenOptions::new()
                    .create(true)
                    .append(true)
                    .write(true)
                    .open(file_path)
                    .await?;

                while let Some(chunk) = stream.next().await {
                    let mut chunk = chunk?;
                    let chunk_length = chunk.len() as u64;
                    file.write_all_buf(&mut chunk).await?;
                    bar.inc(chunk_length);
                }
                file.flush().await?;
                self.status = DownloadStatus::Completed;
            }
            Err(_) => {
                self.retry += 1;
                if self.retry > self.max_retry {
                    return Err(anyhow!(format!("{} faild", self.id)));
                }
            }
        }
        Ok(())
    }
}