1use std::{cell::RefCell, rc::Rc};
2use std::{
3 fs::File,
4 io::{self, Write},
5 path::Path,
6};
7use thiserror::Error;
8
9mod curl_backend;
10#[cfg(feature = "library")]
11mod reqwest_backend;
12
13use crate::callback::Callback;
14
15pub use curl_backend::CurlBackend;
16#[cfg(not(feature = "library"))]
17pub use curl_backend::CurlBackend as DefaultNetBackend;
18#[cfg(feature = "library")]
19pub use reqwest_backend::ReqwestBackend;
20#[cfg(feature = "library")]
21pub use reqwest_backend::ReqwestBackend as DefaultNetBackend;
22
23pub enum DownloadBackendWriter {
24 ToFile(File),
25 ToBuf(Vec<u8>),
26}
27
28impl Write for DownloadBackendWriter {
29 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
30 match self {
31 DownloadBackendWriter::ToFile(file) => file.write(buf),
32 DownloadBackendWriter::ToBuf(items) => items.write(buf),
33 }
34 }
35
36 fn flush(&mut self) -> io::Result<()> {
37 match self {
38 DownloadBackendWriter::ToFile(file) => file.flush(),
39 DownloadBackendWriter::ToBuf(items) => items.flush(),
40 }
41 }
42}
43
44impl DownloadBackendWriter {
45 pub fn to_inner_buf(self) -> Vec<u8> {
46 match self {
47 DownloadBackendWriter::ToBuf(items) => items,
48 _ => panic!("Logic error, should be a buffer going here"),
49 }
50 }
51 pub fn to_inner_file(self) -> File {
52 match self {
53 DownloadBackendWriter::ToFile(file) => file,
54 _ => panic!("Logic error, should be a file handle going here"),
55 }
56 }
57}
58
59pub trait DownloadBackend {
60 fn new() -> Result<Self, DownloadError>
61 where
62 Self: Sized;
63
64 fn download(
65 &self,
66 remote_path: &str,
67 remote_len: Option<u64>,
68 writer: &mut DownloadBackendWriter,
69 callback: Rc<RefCell<dyn Callback>>,
70 ) -> Result<(), DownloadError>;
71
72 fn download_to_file(
73 &self,
74 remote_path: &str,
75 remote_len: Option<u64>,
76 local_path: &Path,
77 callback: Rc<RefCell<dyn Callback>>,
78 ) -> Result<(), DownloadError> {
79 let mut output = DownloadBackendWriter::ToFile(File::create(local_path)?);
80 self.download(remote_path, remote_len, &mut output, callback)
81 }
82
83 fn download_to_buf(
84 &self,
85 remote_path: &str,
86 callback: Rc<RefCell<dyn Callback>>,
87 ) -> Result<Vec<u8>, DownloadError> {
88 let mut output = DownloadBackendWriter::ToBuf(Vec::new());
89 self.download(remote_path, None, &mut output, callback)?;
90 Ok(output.to_inner_buf())
91 }
92
93 fn file_size(&self) -> Option<usize> {
94 None
95 }
96}
97
98#[derive(Error, Debug)]
99pub enum DownloadError {
100 #[error("Download timed out")]
102 Timeout,
103 #[cfg(feature = "library")]
105 #[error("HTTP error status: {0}")]
106 HttpStatus(reqwest::StatusCode),
107 #[cfg(feature = "library")]
109 #[error("Other reqwest error: {0}")]
110 Reqwest(reqwest::Error),
111 #[error("IO error: {0}")]
113 IO(#[from] io::Error),
114}
115
116#[cfg(feature = "library")]
117impl From<reqwest::Error> for DownloadError {
118 fn from(err: reqwest::Error) -> Self {
119 if err.is_timeout() {
120 DownloadError::Timeout
121 } else if err.is_status() {
122 DownloadError::HttpStatus(
123 err.status()
124 .unwrap_or(reqwest::StatusCode::INTERNAL_SERVER_ERROR),
125 )
126 } else {
127 DownloadError::Reqwest(err)
128 }
129 }
130}