kget/ftp/
client.rs

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        // Download file
70        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}