207 lines
6.8 KiB
Rust
207 lines
6.8 KiB
Rust
//! Runtime configuration defaults and human-readable size parsing helpers.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
/// Runtime configuration used to build and operate the download engine.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct RuntimeConfig {
|
|
/// Number of worker threads available to the runtime.
|
|
pub worker_threads: usize,
|
|
/// Maximum number of simultaneously active downloads.
|
|
pub max_active_downloads: usize,
|
|
/// Maximum number of tracked downloads.
|
|
pub max_downloads: usize,
|
|
/// Desired split count per download.
|
|
pub split: usize,
|
|
/// Canonical maximum connections per server option.
|
|
pub max_connections_per_server: usize,
|
|
/// Compatibility alias for the maximum connections per server option.
|
|
pub max_connection_per_server: usize,
|
|
/// Global download-rate cap in bytes per second.
|
|
pub max_overall_download_limit: Option<u64>,
|
|
/// Per-download download-rate cap in bytes per second.
|
|
pub max_download_limit: Option<u64>,
|
|
/// Global upload-rate cap in bytes per second.
|
|
pub max_overall_upload_limit: Option<u64>,
|
|
/// Per-download upload-rate cap in bytes per second.
|
|
pub max_upload_limit: Option<u64>,
|
|
/// Minimum size used when splitting work into segments.
|
|
pub min_split_size: u64,
|
|
/// Default piece length for newly created downloads.
|
|
pub piece_length: u64,
|
|
/// RPC listen port.
|
|
pub rpc_port: u16,
|
|
/// `BitTorrent` listen port.
|
|
pub listen_port: u16,
|
|
/// Configured disk-cache size in bytes.
|
|
pub disk_cache_bytes: u64,
|
|
/// Event queue buffer size.
|
|
pub event_buffer_size: usize,
|
|
/// Optional session file path.
|
|
pub session_path: Option<PathBuf>,
|
|
/// Interval between session saves in seconds.
|
|
pub save_session_interval_secs: u64,
|
|
/// Graceful shutdown timeout in seconds.
|
|
pub graceful_shutdown_timeout_secs: u64,
|
|
/// Whether XML-RPC endpoints are enabled.
|
|
pub allow_xmlrpc: bool,
|
|
/// Whether JSON-RPC endpoints are enabled.
|
|
pub allow_jsonrpc: bool,
|
|
/// Whether resume behavior is enabled.
|
|
pub allow_resume: bool,
|
|
/// Whether IPv6 support is enabled.
|
|
pub enable_ipv6: bool,
|
|
/// Whether HTTP 400 responses are retryable.
|
|
pub retry_on_400: bool,
|
|
/// Whether HTTP 403 responses are retryable.
|
|
pub retry_on_403: bool,
|
|
/// Whether HTTP 406 responses are retryable.
|
|
pub retry_on_406: bool,
|
|
/// Whether unknown failures are retryable.
|
|
pub retry_on_unknown: bool,
|
|
}
|
|
|
|
impl Default for RuntimeConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
worker_threads: 4,
|
|
max_active_downloads: 5,
|
|
max_downloads: 16,
|
|
split: 5,
|
|
max_connections_per_server: 1,
|
|
max_connection_per_server: 1,
|
|
max_overall_download_limit: None,
|
|
max_download_limit: None,
|
|
max_overall_upload_limit: None,
|
|
max_upload_limit: None,
|
|
min_split_size: 1_024,
|
|
piece_length: 1_024,
|
|
rpc_port: 6_800,
|
|
listen_port: 6_881,
|
|
disk_cache_bytes: 16 * 1_024 * 1_024,
|
|
event_buffer_size: 256,
|
|
session_path: None,
|
|
save_session_interval_secs: 30,
|
|
graceful_shutdown_timeout_secs: 10,
|
|
allow_xmlrpc: true,
|
|
allow_jsonrpc: true,
|
|
allow_resume: true,
|
|
enable_ipv6: false,
|
|
retry_on_400: true,
|
|
retry_on_403: true,
|
|
retry_on_406: true,
|
|
retry_on_unknown: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RuntimeConfig {
|
|
/// Returns a copy with the session path set.
|
|
#[must_use]
|
|
pub fn with_session_path(mut self, path: impl Into<PathBuf>) -> Self {
|
|
self.session_path = Some(path.into());
|
|
self
|
|
}
|
|
|
|
/// Returns a copy with the worker-thread count overridden.
|
|
#[must_use]
|
|
pub fn with_worker_threads(mut self, count: usize) -> Self {
|
|
self.worker_threads = count;
|
|
self
|
|
}
|
|
|
|
/// Returns a copy with the RPC port overridden.
|
|
#[must_use]
|
|
pub fn with_rpc_port(mut self, port: u16) -> Self {
|
|
self.rpc_port = port;
|
|
self
|
|
}
|
|
|
|
/// Returns a copy with the session path parsed from a string-like value.
|
|
#[must_use]
|
|
pub fn with_session_path_str(mut self, path: impl Into<String>) -> Self {
|
|
self.session_path = Some(PathBuf::from(path.into()));
|
|
self
|
|
}
|
|
|
|
/// Returns the effective max-connections-per-server setting.
|
|
#[must_use]
|
|
pub fn effective_max_connections_per_server(&self) -> usize {
|
|
self.max_connections_per_server
|
|
.max(self.max_connection_per_server)
|
|
.max(1)
|
|
}
|
|
|
|
/// Returns the effective split count.
|
|
#[must_use]
|
|
pub fn effective_split(&self) -> usize {
|
|
self.split.max(1)
|
|
}
|
|
|
|
/// Returns the effective maximum parallel segment count.
|
|
#[must_use]
|
|
pub fn effective_parallel_segments(&self) -> usize {
|
|
self.effective_split()
|
|
.min(self.effective_max_connections_per_server())
|
|
.max(1)
|
|
}
|
|
|
|
/// Returns retryability flags for the common HTTP error buckets.
|
|
#[must_use]
|
|
pub fn retryable_status_codes(&self) -> [bool; 4] {
|
|
[
|
|
self.retry_on_400,
|
|
self.retry_on_403,
|
|
self.retry_on_406,
|
|
self.retry_on_unknown,
|
|
]
|
|
}
|
|
}
|
|
|
|
/// Parses a human-readable byte-size string into a byte count.
|
|
#[must_use]
|
|
#[expect(
|
|
clippy::redundant_pub_crate,
|
|
reason = "session and request helpers reuse the parser while the runtime module remains crate-private"
|
|
)]
|
|
pub(crate) fn parse_human_size_text(value: &str) -> Option<u64> {
|
|
let trimmed = value.trim();
|
|
if trimmed.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let split_index = trimmed
|
|
.find(|ch: char| !ch.is_ascii_digit())
|
|
.unwrap_or(trimmed.len());
|
|
let (digits, suffix) = trimmed.split_at(split_index);
|
|
let base = digits.parse::<u64>().ok()?;
|
|
let suffix = suffix.trim();
|
|
let factor = if suffix.is_empty() {
|
|
1
|
|
} else if suffix.eq_ignore_ascii_case("k") || suffix.eq_ignore_ascii_case("kb") {
|
|
1_024
|
|
} else if suffix.eq_ignore_ascii_case("m") || suffix.eq_ignore_ascii_case("mb") {
|
|
1_024 * 1_024
|
|
} else if suffix.eq_ignore_ascii_case("g") || suffix.eq_ignore_ascii_case("gb") {
|
|
1_024 * 1_024 * 1_024
|
|
} else {
|
|
return None;
|
|
};
|
|
Some(base.saturating_mul(factor))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parse_human_size_text_supports_plain_and_suffix_forms() {
|
|
assert_eq!(parse_human_size_text("2048"), Some(2048));
|
|
assert_eq!(parse_human_size_text("2K"), Some(2 * 1024));
|
|
assert_eq!(parse_human_size_text("4M"), Some(4 * 1024 * 1024));
|
|
assert_eq!(parse_human_size_text("3gb"), Some(3 * 1024 * 1024 * 1024));
|
|
assert_eq!(parse_human_size_text(""), None);
|
|
assert_eq!(parse_human_size_text("12T"), None);
|
|
}
|
|
}
|