chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user