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