//! 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, /// Per-download download-rate cap in bytes per second. pub max_download_limit: Option, /// Global upload-rate cap in bytes per second. pub max_overall_upload_limit: Option, /// Per-download upload-rate cap in bytes per second. pub max_upload_limit: Option, /// 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, /// 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) -> 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) -> 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 { 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::().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); } }