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}