1use crate::config::ProxyConfig;
4use crate::optimization::Optimizer;
5use crate::progress::create_progress_bar;
6use crate::utils::print;
7use std::error::Error;
8use std::io::Write;
9use suppaftp::FtpStream;
10use url::Url;
11
12pub struct FtpDownloader {
53 url: String,
54 output_path: String,
55 quiet_mode: bool,
56 proxy: ProxyConfig,
57 #[allow(dead_code)]
58 optimizer: Optimizer,
59}
60
61impl FtpDownloader {
62 pub fn new(
72 url: String,
73 output_path: String,
74 quiet_mode: bool,
75 proxy: ProxyConfig,
76 optimizer: Optimizer,
77 ) -> Self {
78 Self {
79 url,
80 output_path,
81 quiet_mode,
82 proxy,
83 optimizer,
84 }
85 }
86
87 pub fn download(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
88 let url = Url::parse(&self.url)?;
89 let host = url.host_str().ok_or("Invalid host")?;
90 let port = url.port().unwrap_or(21);
91 let path = url.path();
92
93 print(
94 &format!("Connecting to FTP server {}:{}...", host, port),
95 self.quiet_mode,
96 );
97
98 let mut ftp = if self.proxy.enabled {
99 self.connect_via_proxy(host, port)?
100 } else {
101 FtpStream::connect((host, port))?
102 };
103
104 let username = if url.username().is_empty() {
106 "anonymous"
107 } else {
108 url.username()
109 };
110 let password = url.password().unwrap_or("anonymous");
111
112 print(&format!("Logging in as {}...", username), self.quiet_mode);
113 ftp.login(username, password)?;
114
115 ftp.transfer_type(suppaftp::types::FileType::Binary)?;
116
117 let size = ftp.size(path)? as u64;
118
119 let progress = create_progress_bar(
120 self.quiet_mode,
121 format!("Downloading {}", path),
122 Some(size),
123 false,
124 );
125
126 let mut file = std::fs::File::create(&self.output_path)?;
127
128 let mut downloaded = 0;
130 ftp.retr(path, |reader| {
131 let mut buffer = vec![0; 8192];
132 loop {
133 match reader.read(&mut buffer) {
134 Ok(0) => break,
135 Ok(n) => {
136 file.write_all(&buffer[..n])
137 .map_err(|e| suppaftp::FtpError::ConnectionError(e))?;
138 downloaded += n;
139 progress.set_position(downloaded as u64);
140 }
141 Err(e) => return Err(suppaftp::FtpError::ConnectionError(e)),
142 }
143 }
144 Ok(())
145 })?;
146
147 progress.finish();
148 print("Download completed successfully!", self.quiet_mode);
149
150 Ok(())
151 }
152
153 fn connect_via_proxy(
154 &self,
155 host: &str,
156 port: u16,
157 ) -> Result<FtpStream, Box<dyn Error + Send + Sync>> {
158 match self.proxy.proxy_type {
159 crate::config::ProxyType::Http | crate::config::ProxyType::Https => {
160 Err("HTTP/HTTPS proxy not supported for FTP".into())
161 }
162 crate::config::ProxyType::Socks5 => {
163 if let Some(proxy_url) = &self.proxy.url {
164 let proxy = Url::parse(proxy_url)?;
165 let proxy_host = proxy.host_str().ok_or("Invalid proxy host")?;
166 let proxy_port = proxy.port().unwrap_or(1080);
167
168 let stream =
169 socks::Socks5Stream::connect((proxy_host, proxy_port), (host, port))?;
170
171 Ok(FtpStream::connect_with_stream(stream.into_inner())?)
172 } else {
173 Err("Proxy URL not configured".into())
174 }
175 }
176 }
177 }
178}