feat(torrents): add global open-file limit

This commit is contained in:
NimBold
2026-08-02 10:50:30 +03:30
parent 48b4d984a2
commit 2d427b96d3
19 changed files with 332 additions and 6 deletions
+6
View File
@@ -30,6 +30,10 @@ fn default_torrent_enable_lpd() -> bool {
false
}
fn default_torrent_max_open_files() -> u32 {
crate::queue::DEFAULT_TORRENT_MAX_OPEN_FILES
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export, export_to = "../../src/bindings/")]
@@ -493,6 +497,8 @@ pub struct PersistedSettings {
pub torrent_enable_pex: bool,
#[serde(default = "default_torrent_enable_lpd")]
pub torrent_enable_lpd: bool,
#[serde(default = "default_torrent_max_open_files")]
pub torrent_max_open_files: u32,
pub custom_user_agent: String,
pub ask_where_to_save_each_file: bool,
pub remember_last_used_download_directory: bool,
+71 -1
View File
@@ -6626,6 +6626,32 @@ fn apply_aria2_torrent_peer_discovery_options(
.arg(format!("--bt-enable-lpd={enable_lpd}"));
}
fn apply_aria2_torrent_global_options(
command: &mut std::process::Command,
max_open_files: u32,
) {
let max_open_files = queue::normalize_torrent_max_open_files(max_open_files)
.unwrap_or(queue::DEFAULT_TORRENT_MAX_OPEN_FILES);
command.arg(format!("--bt-max-open-files={max_open_files}"));
}
#[tauri::command(rename_all = "snake_case")]
async fn set_torrent_max_open_files(
state: tauri::State<'_, AppState>,
max_open_files: u32,
) -> Result<(), String> {
let max_open_files = queue::normalize_torrent_max_open_files(max_open_files)?;
rpc_call(
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
&state.aria2_secret,
"aria2.changeGlobalOption",
serde_json::json!([{"bt-max-open-files": max_open_files.to_string()}]),
)
.await
.map(|_| ())
.map_err(|error| format!("Failed to set Torrent maximum open files: {error}"))
}
#[tauri::command]
async fn set_global_speed_limit(
state: tauri::State<'_, AppState>,
@@ -7718,6 +7744,7 @@ mod tests {
cookie_scope_for_url, metadata_authentication_error, metadata_cookie_header_present,
metadata_headers, metadata_response_error,
normalize_speed_limit_for_aria2,
apply_aria2_torrent_global_options,
apply_aria2_torrent_peer_discovery_options,
parse_firelink_deep_link, parse_ffmpeg_version, parse_media_progress_line,
redact_log_line, redact_log_line_for_output, sanitize_ytdlp_config_value,
@@ -7739,6 +7766,7 @@ mod tests {
#[cfg(target_os = "macos")]
use super::should_apply_dock_badge_update;
use serde_json::json;
use crate::queue;
use std::time::{Duration, Instant};
#[test]
@@ -7768,6 +7796,31 @@ mod tests {
);
}
#[test]
fn aria2_torrent_global_options_are_bounded_and_explicit() {
let mut command = std::process::Command::new("aria2c");
apply_aria2_torrent_global_options(&mut command, 256);
assert_eq!(
command
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect::<Vec<_>>(),
vec!["--bt-max-open-files=256"]
);
let mut fallback_command = std::process::Command::new("aria2c");
apply_aria2_torrent_global_options(
&mut fallback_command,
queue::MAX_TORRENT_MAX_OPEN_FILES + 1,
);
assert_eq!(
fallback_command
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect::<Vec<_>>(),
vec!["--bt-max-open-files=100"]
);
}
#[test]
fn retained_torrent_metadata_survives_unrelated_persisted_field_corruption() {
let record = json!({
@@ -10419,6 +10472,21 @@ pub fn run() {
)
})
.unwrap_or((true, false, true, false));
let torrent_max_open_files = persisted_settings
.as_ref()
.map(|settings| settings.torrent_max_open_files)
.unwrap_or(queue::DEFAULT_TORRENT_MAX_OPEN_FILES);
let torrent_max_open_files = match queue::normalize_torrent_max_open_files(
torrent_max_open_files,
) {
Ok(value) => value,
Err(error) => {
log::error!(
"invalid persisted Torrent open-file limit; using Aria2 default: {error}"
);
queue::DEFAULT_TORRENT_MAX_OPEN_FILES
}
};
let aria2_secret_clone = aria2_secret.clone();
let app_handle_bg = app.handle().clone();
@@ -10453,6 +10521,8 @@ pub fn run() {
.arg("--check-certificate=true")
.arg(format!("--stop-with-process={}", std::process::id()));
apply_aria2_torrent_global_options(&mut cmd, torrent_max_open_files);
apply_aria2_torrent_peer_discovery_options(
&mut cmd,
torrent_peer_discovery.0,
@@ -11017,7 +11087,7 @@ pub fn run() {
authorize_keychain_access,
acknowledge_pairing_token_change,
check_file_exists, toggle_tray_icon, set_extension_pairing_token,
get_extension_server_port, set_extension_frontend_ready, ack_extension_download, set_concurrent_limit, set_queue_concurrency_limits, set_download_speed_limit, set_torrent_upload_limit, set_torrent_peer_options, get_torrent_peers, set_global_speed_limit, remove_download, get_download_primary_path,
get_extension_server_port, set_extension_frontend_ready, ack_extension_download, set_concurrent_limit, set_queue_concurrency_limits, set_download_speed_limit, set_torrent_upload_limit, set_torrent_peer_options, get_torrent_peers, set_torrent_max_open_files, set_global_speed_limit, remove_download, get_download_primary_path,
detach_download_for_reconfigure,
enqueue_download, enqueue_many, cancel_enqueue_generation, move_in_queue, move_many_in_queue, remove_from_queue, get_pending_order,
commands::reveal_in_file_manager, commands::open_downloaded_file,
+23
View File
@@ -24,11 +24,23 @@ pub const DOWNLOAD_CONNECTIONS_MAX: i32 = 16;
pub const MAX_TORRENT_PIECE_PRIORITY_SIZE_MIB: u64 = 1024;
pub const MAX_TORRENT_TRACKER_TIMEOUT: u32 = 604_800;
pub const MAX_TORRENT_TRACKER_INTERVAL: u32 = 604_800;
pub const DEFAULT_TORRENT_MAX_OPEN_FILES: u32 = 100;
pub const MIN_TORRENT_MAX_OPEN_FILES: u32 = 1;
pub const MAX_TORRENT_MAX_OPEN_FILES: u32 = 4_096;
pub fn clamp_download_connections(connections: i32) -> i32 {
connections.clamp(DOWNLOAD_CONNECTIONS_MIN, DOWNLOAD_CONNECTIONS_MAX)
}
pub fn normalize_torrent_max_open_files(value: u32) -> Result<u32, String> {
if !(MIN_TORRENT_MAX_OPEN_FILES..=MAX_TORRENT_MAX_OPEN_FILES).contains(&value) {
return Err(format!(
"torrent maximum open files must be between {MIN_TORRENT_MAX_OPEN_FILES} and {MAX_TORRENT_MAX_OPEN_FILES}"
));
}
Ok(value)
}
fn reorder_selected_queue_tasks(
queue_tasks: &[QueuedTask],
ids: &[String],
@@ -4831,6 +4843,17 @@ mod tests {
assert!(!options.contains_key("bt-tracker-interval"));
}
#[test]
fn torrent_max_open_files_is_bounded() {
assert_eq!(normalize_torrent_max_open_files(1).unwrap(), 1);
assert_eq!(
normalize_torrent_max_open_files(MAX_TORRENT_MAX_OPEN_FILES).unwrap(),
MAX_TORRENT_MAX_OPEN_FILES
);
assert!(normalize_torrent_max_open_files(0).is_err());
assert!(normalize_torrent_max_open_files(MAX_TORRENT_MAX_OPEN_FILES + 1).is_err());
}
#[test]
fn torrent_trackers_are_emitted_as_the_aria2_tracker_option() {
let mut options = serde_json::Map::new();
+19
View File
@@ -188,6 +188,15 @@ fn sanitize_persisted_setting_values(state: &mut Value) {
sanitize_integer_setting(state, "maxConcurrentDownloads", |value| value.as_u64().is_some());
sanitize_integer_setting(state, "perServerConnections", |value| value.as_i64().is_some());
sanitize_integer_setting(state, "maxAutomaticRetries", |value| value.as_i64().is_some());
sanitize_integer_setting(state, "torrentMaxOpenFiles", |value| {
value
.as_u64()
.is_some_and(|value| {
(crate::queue::MIN_TORRENT_MAX_OPEN_FILES as u64..=
crate::queue::MAX_TORRENT_MAX_OPEN_FILES as u64)
.contains(&value)
})
});
for key in [
"torrentEnableDht",
"torrentEnableDht6",
@@ -308,6 +317,10 @@ fn validate_settings(settings: &mut PersistedSettings) {
settings.max_concurrent_downloads = settings.max_concurrent_downloads.min(12);
settings.per_server_connections = settings.per_server_connections.clamp(1, 16);
settings.max_automatic_retries = settings.max_automatic_retries.clamp(0, 10);
settings.torrent_max_open_files = crate::queue::normalize_torrent_max_open_files(
settings.torrent_max_open_files,
)
.unwrap_or(crate::queue::DEFAULT_TORRENT_MAX_OPEN_FILES);
if !matches!(
settings.last_custom_speed_limit_unit.as_str(),
"KB/s" | "MB/s"
@@ -500,6 +513,7 @@ fn default_settings() -> PersistedSettings {
torrent_enable_dht6: false,
torrent_enable_pex: true,
torrent_enable_lpd: false,
torrent_max_open_files: crate::queue::DEFAULT_TORRENT_MAX_OPEN_FILES,
custom_user_agent: String::new(),
ask_where_to_save_each_file: false,
remember_last_used_download_directory: false,
@@ -792,6 +806,7 @@ mod tests {
"torrentEnableDht6": 1,
"torrentEnablePex": null,
"torrentEnableLpd": [],
"torrentMaxOpenFiles": 0,
"theme": "not-a-theme",
"calendarPreference": "lunar",
"siteLogins": [{"id": "valid", "urlPattern": "example.com", "username": "user"}, {"id": 3}]
@@ -809,6 +824,10 @@ mod tests {
assert!(!settings.torrent_enable_dht6);
assert!(settings.torrent_enable_pex);
assert!(!settings.torrent_enable_lpd);
assert_eq!(
settings.torrent_max_open_files,
crate::queue::DEFAULT_TORRENT_MAX_OPEN_FILES
);
assert!(matches!(settings.theme, crate::ipc::Theme::System));
assert!(matches!(
settings.calendar_preference,