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}