1use crate::file;
2use crate::writer::file::SeqFileWriter;
3use crate::{auto, DownloadResult, ProgressEntry};
4use reqwest::{Client, IntoUrl};
5use std::{io::ErrorKind, path::Path, time::Duration};
6use tokio::fs::{self, OpenOptions};
7
8#[derive(Debug, Clone)]
9pub struct DownloadOptions {
10 pub threads: usize,
11 pub client: Client,
12 pub can_fast_download: bool,
13 pub write_buffer_size: usize,
14 pub download_chunks: Vec<ProgressEntry>,
15 pub retry_gap: Duration,
16 pub file_size: u64,
17}
18
19#[derive(Debug)]
20pub enum DownloadErrorKind {
21 Reqwest(reqwest::Error),
22 Io(std::io::Error),
23}
24impl std::fmt::Display for DownloadErrorKind {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 DownloadErrorKind::Reqwest(err) => write!(f, "HTTP request failed: {}", err),
28 DownloadErrorKind::Io(err) => write!(f, "IO error: {}", err),
29 }
30 }
31}
32
33impl std::error::Error for DownloadErrorKind {
34 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35 match self {
36 DownloadErrorKind::Reqwest(err) => Some(err),
37 DownloadErrorKind::Io(err) => Some(err),
38 }
39 }
40}
41
42pub async fn download(
43 url: impl IntoUrl,
44 save_path: &Path,
45 options: DownloadOptions,
46) -> Result<DownloadResult, DownloadErrorKind> {
47 let save_folder = save_path.parent().unwrap();
48 if let Err(e) = fs::create_dir_all(save_folder).await {
49 if e.kind() != ErrorKind::AlreadyExists {
50 return Err(DownloadErrorKind::Io(e));
51 }
52 }
53 let file = OpenOptions::new()
54 .read(true)
55 .write(true)
56 .create(true)
57 .open(&save_path)
58 .await
59 .map_err(|e| DownloadErrorKind::Io(e))?;
60 let seq_file_writer = SeqFileWriter::new(
61 file.try_clone()
62 .await
63 .map_err(|e| DownloadErrorKind::Io(e))?,
64 options.write_buffer_size,
65 );
66 #[cfg(target_pointer_width = "64")]
67 let rand_file_writer = file::rand_file_writer_mmap::RandFileWriter::new(
68 file,
69 options.file_size,
70 options.write_buffer_size,
71 )
72 .await
73 .map_err(|e| DownloadErrorKind::Io(e))?;
74 #[cfg(not(target_pointer_width = "64"))]
75 let rand_file_writer = file::rand_file_writer_std::RandFileWriter::new(
76 file,
77 options.file_size,
78 options.write_buffer_size,
79 )
80 .await
81 .map_err(|e| DownloadErrorKind::Io(e))?;
82 auto::download(
83 url,
84 seq_file_writer,
85 rand_file_writer,
86 auto::DownloadOptions {
87 threads: options.threads,
88 client: options.client,
89 can_fast_download: options.can_fast_download,
90 download_chunks: options.download_chunks,
91 retry_gap: options.retry_gap,
92 file_size: options.file_size,
93 },
94 )
95 .await
96 .map_err(|e| DownloadErrorKind::Reqwest(e))
97}