Skip to main content

kget/
config.rs

1//! Configuration management for KGet.
2//!
3//! This module provides configuration structures for all KGet features:
4//! proxy settings, optimization parameters, torrent options, and protocol-specific configs.
5//!
6//! Configuration is stored in JSON format at:
7//! - macOS: `~/Library/Application Support/kget/config.json`
8//! - Linux: `~/.config/kget/config.json`
9//! - Windows: `%APPDATA%\kget\config.json`
10//!
11//! # Example
12//!
13//! ```rust
14//! use kget::Config;
15//!
16//! // Load existing config or create default
17//! let config = Config::load().unwrap_or_default();
18//!
19//! // Modify and save
20//! let mut config = config;
21//! config.optimization.max_connections = 8;
22//! config.save().unwrap();
23//! ```
24
25use serde::{Deserialize, Serialize};
26use std::path::PathBuf;
27use std::fs;
28use std::io;
29use dirs::config_dir;
30
31/// Proxy configuration for routing downloads through a proxy server.
32///
33/// Supports HTTP, HTTPS, and SOCKS5 proxies with optional authentication.
34///
35/// # Example
36///
37/// ```rust
38/// use kget::{ProxyConfig, ProxyType};
39///
40/// let proxy = ProxyConfig {
41///     enabled: true,
42///     url: Some("http://proxy.example.com:8080".to_string()),
43///     username: Some("user".to_string()),
44///     password: Some("pass".to_string()),
45///     proxy_type: ProxyType::Http,
46/// };
47/// ```
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ProxyConfig {
50    /// Whether to use the proxy for downloads
51    pub enabled: bool,
52    /// Proxy server URL (e.g., "http://proxy:8080" or "socks5://127.0.0.1:1080")
53    pub url: Option<String>,
54    /// Username for proxy authentication (optional)
55    pub username: Option<String>,
56    /// Password for proxy authentication (optional)
57    pub password: Option<String>,
58    /// Type of proxy protocol to use
59    pub proxy_type: ProxyType,
60}
61
62/// Supported proxy protocol types.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum ProxyType {
65    /// HTTP proxy (for HTTP downloads)
66    Http,
67    /// HTTPS proxy (for HTTPS downloads)
68    Https,
69    /// SOCKS5 proxy (works with all protocols)
70    Socks5,
71}
72
73/// Configuration for download optimization features.
74///
75/// Controls compression, caching, speed limiting, and parallel connections.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct OptimizationConfig {
78    /// Enable automatic compression for cached downloads
79    pub compression: bool,
80    /// Compression level (1-9). Affects algorithm selection:
81    /// - 1-3: Gzip (fast)
82    /// - 4-6: LZ4 (balanced)
83    /// - 7-9: Brotli (high compression)
84    pub compression_level: u8,
85    /// Enable download caching
86    pub cache_enabled: bool,
87    /// Directory for cached files (default: ~/.cache/kget)
88    pub cache_dir: String,
89    /// Speed limit in bytes per second (None = unlimited)
90    pub speed_limit: Option<u64>,
91    /// Maximum parallel connections per download (1-32)
92    pub max_connections: usize,
93}
94
95// Function to provide the default value for max_peer_connections
96fn default_torrent_max_peer_connections() -> u32 {
97     50
98}
99
100// Function to provide the default value for max_upload_slots
101fn default_torrent_max_upload_slots() -> u32 {
102    4 
103}
104
105/// Configuration for BitTorrent downloads.
106///
107/// These settings apply when using the native torrent client (`torrent-native` feature)
108/// or the Transmission RPC integration (`torrent-transmission` feature).
109///
110/// # Example
111///
112/// ```rust
113/// use kget::Config;
114///
115/// let mut config = Config::default();
116/// config.torrent.enabled = true;
117/// config.torrent.download_dir = Some("/home/user/Downloads".to_string());
118/// config.torrent.max_peers = 100;
119/// ```
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TorrentConfig {
122    /// Enable torrent download support
123    pub enabled: bool,
124    /// Default download directory (None = use current directory)
125    pub download_dir: Option<String>,
126    /// Maximum number of peers to connect to
127    pub max_peers: usize,
128    /// Maximum number of seeds to upload to
129    pub max_seeds: usize,
130    /// Custom listen port for incoming connections (None = random)
131    pub port: Option<u16>,
132    /// Enable DHT (Distributed Hash Table) for peer discovery
133    pub dht_enabled: bool,
134    /// Maximum peer connections per torrent
135    #[serde(default = "default_torrent_max_peer_connections")]
136    pub max_peer_connections: u32,
137    /// Maximum upload slots per torrent
138    #[serde(default = "default_torrent_max_upload_slots")]
139    pub max_upload_slots: u32,
140}
141
142/// Configuration for FTP downloads.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct FtpConfig {
145    /// Use passive mode for FTP connections (recommended for NAT/firewall)
146    pub passive_mode: bool,
147    /// Default FTP port (21)
148    pub default_port: u16,
149}
150
151/// Configuration for SFTP (SSH File Transfer Protocol) downloads.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct SftpConfig {
154    /// Default SFTP port (22)
155    pub default_port: u16,
156    /// Path to SSH private key file for authentication
157    pub key_path: Option<String>,
158}
159
160/// Main configuration structure containing all KGet settings.
161///
162/// This is the top-level configuration object that aggregates
163/// all protocol-specific and feature configurations.
164///
165/// # Loading Configuration
166///
167/// ```rust
168/// use kget::Config;
169///
170/// // Load from file or use defaults
171/// let config = Config::load().unwrap_or_default();
172/// println!("Max connections: {}", config.optimization.max_connections);
173/// ```
174///
175/// # Saving Configuration
176///
177/// ```rust,no_run
178/// use kget::Config;
179///
180/// let mut config = Config::default();
181/// config.optimization.max_connections = 16;
182/// config.save().expect("Failed to save config");
183/// ```
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct Config {
186    /// Proxy server settings
187    pub proxy: ProxyConfig,
188    /// Download optimization settings
189    pub optimization: OptimizationConfig,
190    /// BitTorrent configuration
191    pub torrent: TorrentConfig,
192    /// FTP protocol configuration
193    pub ftp: FtpConfig,
194    /// SFTP protocol configuration
195    pub sftp: SftpConfig,
196}
197
198impl Config {
199    /// Load configuration from the standard config file location.
200    ///
201    /// If the config file doesn't exist, returns `Config::default()`.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if the config file exists but cannot be read or parsed.
206    pub fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
207        let config_path = Self::get_config_path()?;
208        
209        if !config_path.exists() {
210            // If the config file does not exist, return default config
211            return Ok(Self::default());
212        }
213        
214        let config_str = fs::read_to_string(config_path)?;
215        // The error occurs here if the existing JSON file does not have the field.
216        let config: Config = serde_json::from_str(&config_str)?;
217
218        Ok(config)
219    }
220    
221    /// Save the current configuration to the standard config file location.
222    ///
223    /// Creates the config directory if it doesn't exist.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if the config file cannot be written.
228    pub fn save(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
229        let config_path = Self::get_config_path()?;
230
231        // Create config directory if it doesn't exist
232        if let Some(parent) = config_path.parent() {
233            fs::create_dir_all(parent)?;
234        }
235        
236        let config_str = serde_json::to_string_pretty(self)?;
237        fs::write(config_path, config_str)?;
238        
239        Ok(())
240    }
241    
242    fn get_config_path() -> Result<PathBuf, io::Error> {
243        let mut path = config_dir().ok_or_else(|| {
244            io::Error::new(io::ErrorKind::NotFound, "Not able to find config directory")
245        })?;
246        
247        path.push("kget");
248        path.push("config.json");
249        
250        Ok(path)
251    }
252}
253
254impl Default for Config {
255    fn default() -> Self {
256        Self {
257            proxy: ProxyConfig {
258                enabled: false,
259                url: None,
260                username: None,
261                password: None,
262                proxy_type: ProxyType::Http,
263            },
264            optimization: OptimizationConfig {
265                compression: true,
266                compression_level: 6,
267                cache_enabled: true,
268                cache_dir: "~/.cache/kget".to_string(),
269                speed_limit: None,
270                max_connections: 4,
271            },
272            torrent: TorrentConfig {
273                enabled: false,
274                download_dir: None,
275                max_peers: 50,
276                max_seeds: 25,
277                port: None,
278                dht_enabled: true,
279                max_peer_connections: default_torrent_max_peer_connections(),
280                max_upload_slots: default_torrent_max_upload_slots(),
281            },
282            ftp: FtpConfig {
283                passive_mode: true,
284                default_port: 21,
285            },
286            sftp: SftpConfig {
287                default_port: 22,
288                key_path: None,
289            },
290        }
291    }
292}