1use std::error::Error;
2use std::path::Path;
3use std::io::{Read, Write};
4use url::Url;
5use suppaftp::FtpStream;
6use crate::progress::create_progress_bar;
7use crate::config::ProxyConfig;
8use crate::optimization::Optimizer;
9use crate::utils::print;
10
11pub struct FtpDownloader {
12 url: String,
13 output_path: String,
14 quiet_mode: bool,
15 proxy: ProxyConfig,
16 optimizer: Optimizer,
17}
18
19impl FtpDownloader {
20 pub fn new(
21 url: String,
22 output_path: String,
23 quiet_mode: bool,
24 proxy: ProxyConfig,
25 optimizer: Optimizer,
26 ) -> Self {
27 Self {
28 url,
29 output_path,
30 quiet_mode,
31 proxy,
32 optimizer,
33 }
34 }
35
36 pub fn download(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
37 let url = Url::parse(&self.url)?;
38 let host = url.host_str().ok_or("Invalid host")?;
39 let port = url.port().unwrap_or(21);
40 let path = url.path();
41
42 print(&format!("Connecting to FTP server {}:{}...", host, port), self.quiet_mode);
43
44 let mut ftp = if self.proxy.enabled {
45 self.connect_via_proxy(host, port)?
46 } else {
47 FtpStream::connect((host, port))?
48 };
49
50 let username = url.username();
51 let password = url.password().unwrap_or("anonymous");
52
53 print(&format!("Logging in as {}...", username), self.quiet_mode);
54 ftp.login(username, password)?;
55
56 ftp.transfer_type(suppaftp::types::FileType::Binary)?;
57
58 let size = ftp.size(path)? as u64;
59
60 let progress = create_progress_bar(
61 self.quiet_mode,
62 format!("Downloading {}", path),
63 Some(size),
64 false
65 );
66
67 let mut file = std::fs::File::create(&self.output_path)?;
68
69 let mut downloaded = 0;
71 ftp.retr(path, |reader| {
72 let mut buffer = vec![0; 8192];
73 loop {
74 match reader.read(&mut buffer) {
75 Ok(0) => break,
76 Ok(n) => {
77 file.write_all(&buffer[..n]).map_err(|e| suppaftp::FtpError::ConnectionError(e))?;
78 downloaded += n;
79 progress.set_position(downloaded as u64);
80 }
81 Err(e) => return Err(suppaftp::FtpError::ConnectionError(e)),
82 }
83 }
84 Ok(())
85 })?;
86
87 progress.finish();
88 print("Download completed successfully!", self.quiet_mode);
89
90 Ok(())
91 }
92
93 fn connect_via_proxy(&self, host: &str, port: u16) -> Result<FtpStream, Box<dyn Error + Send + Sync>> {
94 match self.proxy.proxy_type {
95 crate::config::ProxyType::Http | crate::config::ProxyType::Https => {
96 Err("HTTP/HTTPS proxy not supported for FTP".into())
97 }
98 crate::config::ProxyType::Socks5 => {
99 if let Some(proxy_url) = &self.proxy.url {
100 let proxy = Url::parse(proxy_url)?;
101 let proxy_host = proxy.host_str().ok_or("Invalid proxy host")?;
102 let proxy_port = proxy.port().unwrap_or(1080);
103
104 let stream = socks::Socks5Stream::connect(
105 (proxy_host, proxy_port),
106 (host, port),
107 )?;
108
109 Ok(FtpStream::connect_with_stream(stream.into_inner())?)
110 } else {
111 Err("Proxy URL not configured".into())
112 }
113 }
114 }
115 }
116}