Skip to main content

pkg/net_backend/
mod.rs

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    // Specific variant for timeout errors
101    #[error("Download timed out")]
102    Timeout,
103    // Specific variant for HTTP status errors (e.g., 404, 500)
104    #[cfg(feature = "library")]
105    #[error("HTTP error status: {0}")]
106    HttpStatus(reqwest::StatusCode),
107    // Fallback for other generic reqwest errors
108    #[cfg(feature = "library")]
109    #[error("Other reqwest error: {0}")]
110    Reqwest(reqwest::Error),
111    // IO errors remain the same
112    #[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}