Skip to main content

kget/torrent/
mod.rs

1//! BitTorrent download support.
2//!
3//! This module provides torrent downloading capabilities through multiple backends:
4//!
5//! - **Native** (`torrent-native` feature): Built-in BitTorrent client using librqbit
6//! - **Transmission** (`torrent-transmission` feature): Transmission daemon RPC
7//! - **External**: Opens magnet links in the system's default torrent client
8//!
9//! # Native Torrent Client
10//!
11//! The native client provides full BitTorrent protocol support:
12//! - Magnet link parsing and metadata download
13//! - DHT (Distributed Hash Table) for peer discovery
14//! - Parallel piece downloading
15//! - Progress callbacks for UI integration
16//!
17//! # Example
18//!
19//! ```rust,no_run
20//! use kget::torrent::{download_magnet, TorrentCallbacks};
21//! use kget::{ProxyConfig, Optimizer};
22//! use std::sync::Arc;
23//!
24//! let callbacks = TorrentCallbacks {
25//!     status: Some(Arc::new(|msg| println!("{}", msg))),
26//!     progress: Some(Arc::new(|p| println!("{:.1}%", p * 100.0))),
27//! };
28//!
29//! download_magnet(
30//!     "magnet:?xt=urn:btih:...",
31//!     "./downloads",
32//!     false,
33//!     ProxyConfig::default(),
34//!     Optimizer::new(),
35//!     callbacks,
36//! ).expect("Torrent download failed");
37//! ```
38//!
39//! # Backend Selection
40//!
41//! The backend is selected via the `KGET_TORRENT_BACKEND` environment variable:
42//! - `native`: Use built-in client (requires `torrent-native` feature)
43//! - `transmission`: Use Transmission RPC (requires `torrent-transmission` feature)
44//! - Any other value: Open in system's default torrent client
45//!
46//! If not set, defaults to `native` when available, otherwise `external`.
47
48use std::error::Error;
49use std::sync::Arc;
50
51use crate::config::ProxyConfig;
52use crate::optimization::Optimizer;
53
54mod external;
55mod settings;
56
57#[cfg(feature = "torrent-transmission")]
58mod transmission;
59
60#[cfg(feature = "torrent-native")]
61mod native;
62
63/// Type alias for status message callbacks.
64pub type StatusCb = Arc<dyn Fn(String) + Send + Sync>;
65
66/// Type alias for progress callbacks (0.0 to 1.0).
67pub type ProgressCb = Arc<dyn Fn(f32) + Send + Sync>;
68
69/// Callbacks for torrent download progress and status updates.
70///
71/// Both callbacks are optional. If not provided, no updates are sent.
72///
73/// # Example
74///
75/// ```rust
76/// use kget::torrent::TorrentCallbacks;
77/// use std::sync::Arc;
78///
79/// // With callbacks
80/// let callbacks = TorrentCallbacks {
81///     status: Some(Arc::new(|msg| println!("Status: {}", msg))),
82///     progress: Some(Arc::new(|p| println!("Progress: {:.1}%", p * 100.0))),
83/// };
84///
85/// // Without callbacks
86/// let silent = TorrentCallbacks::default();
87/// ```
88#[derive(Default, Clone)]
89pub struct TorrentCallbacks {
90    /// Callback for human-readable status messages
91    pub status: Option<StatusCb>,
92    /// Callback for progress updates (0.0 to 1.0)
93    pub progress: Option<ProgressCb>,
94}
95
96fn emit_status(cb: &TorrentCallbacks, msg: impl Into<String>) {
97    if let Some(f) = &cb.status {
98        f(msg.into());
99    }
100}
101
102fn emit_progress(cb: &TorrentCallbacks, p: f32) {
103    if let Some(f) = &cb.progress {
104        f(p.clamp(0.0, 1.0));
105    }
106}
107
108fn selected_backend() -> String {
109    std::env::var("KGET_TORRENT_BACKEND")
110        .unwrap_or_else(|_| {
111            // Default to native if available, otherwise external
112            #[cfg(feature = "torrent-native")]
113            { "native".to_string() }
114            #[cfg(not(feature = "torrent-native"))]
115            { "external".to_string() }
116        })
117        .to_lowercase()
118}
119
120/// Download a torrent from a magnet link.
121///
122/// This function automatically selects the best available backend:
123/// 1. Native client (if `torrent-native` feature is enabled)
124/// 2. Transmission RPC (if `torrent-transmission` feature is enabled)
125/// 3. External client (opens in system default torrent app)
126///
127/// Override the backend with `KGET_TORRENT_BACKEND` environment variable.
128///
129/// # Arguments
130///
131/// * `magnet` - Magnet link starting with `magnet:?`
132/// * `output_dir` - Directory to save downloaded files
133/// * `quiet` - Suppress console output
134/// * `proxy` - Proxy configuration (native backend only)
135/// * `optimizer` - Optimizer for peer limits
136/// * `cb` - Callbacks for progress and status updates
137///
138/// # Example
139///
140/// ```rust,no_run
141/// use kget::torrent::{download_magnet, TorrentCallbacks};
142/// use kget::{ProxyConfig, Optimizer};
143/// use std::sync::Arc;
144///
145/// // Simple download
146/// download_magnet(
147///     "magnet:?xt=urn:btih:HASH&dn=filename",
148///     "/home/user/Downloads",
149///     false,
150///     ProxyConfig::default(),
151///     Optimizer::new(),
152///     TorrentCallbacks::default(),
153/// ).unwrap();
154///
155/// // With progress tracking
156/// let callbacks = TorrentCallbacks {
157///     status: Some(Arc::new(|msg| log::info!("{}", msg))),
158///     progress: Some(Arc::new(|p| {
159///         update_progress_bar(p);
160///     })),
161/// };
162///
163/// download_magnet(
164///     "magnet:?xt=urn:btih:HASH",
165///     "./downloads",
166///     true, // quiet mode
167///     ProxyConfig::default(),
168///     Optimizer::new(),
169///     callbacks,
170/// ).unwrap();
171///
172/// fn update_progress_bar(p: f32) {
173///     // Update UI
174/// }
175/// ```
176///
177/// # Errors
178///
179/// Returns an error if:
180/// - Magnet link is invalid
181/// - Network connection fails
182/// - Output directory cannot be accessed
183/// - Download is interrupted
184pub fn download_magnet(
185    magnet: &str,
186    output_dir: &str,
187    quiet: bool,
188    proxy: ProxyConfig,
189    optimizer: Optimizer,
190    cb: TorrentCallbacks,
191) -> Result<(), Box<dyn Error + Send + Sync>> {
192    emit_progress(&cb, 0.0);
193
194    match selected_backend().as_str() {
195        "native" => {
196            #[cfg(feature = "torrent-native")]
197            {
198                return native::download_magnet_native(
199                    magnet,
200                    output_dir,
201                    quiet,
202                    proxy,
203                    optimizer,
204                    cb,
205                );
206            }
207
208            #[cfg(not(feature = "torrent-native"))]
209            {
210                emit_status(
211                    &cb,
212                    "Native torrent backend not available (compile with --features torrent-native). Falling back to external client.",
213                );
214            }
215        }
216        "transmission" => {
217            #[cfg(feature = "torrent-transmission")]
218            {
219                return transmission::download_via_transmission(
220                    magnet,
221                    output_dir,
222                    quiet,
223                    proxy,
224                    optimizer,
225                    cb,
226                );
227            }
228
229            #[cfg(not(feature = "torrent-transmission"))]
230            {
231                emit_status(
232                    &cb,
233                    "Torrent backend 'transmission' not available (compile with --features torrent-transmission). Falling back to external client.",
234                );
235            }
236        }
237        _ => {}
238    }
239
240    emit_status(
241        &cb,
242        format!(
243            "Opening magnet link in your default torrent client (output folder may be managed by that client): {}",
244            magnet
245        ),
246    );
247
248    external::open_magnet_in_default_client(magnet)?;
249    emit_progress(&cb, 1.0);
250    Ok(())
251}