diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index 625d525..93614ef 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -302,9 +302,6 @@ pub struct TorrentPeer { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub port: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub peer_id: Option, #[ts(type = "number")] pub download_speed: u64, #[ts(type = "number")] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f438649..80bcef4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -14253,7 +14253,7 @@ pub fn run() { total_bytes: (total > 0).then_some(total as f64), total_is_estimate: Some(false), active_connections: Some(active_connections), - requested_connections: Some(requested_connections), + requested_connections: (!is_torrent).then_some(requested_connections), uploaded_bytes: torrent_telemetry .map(|value| value.uploaded_bytes as f64) .or_else(|| uploaded_bytes.map(|value| value as f64)), diff --git a/src-tauri/src/properties_window.rs b/src-tauri/src/properties_window.rs index 7f352c2..58466bf 100644 --- a/src-tauri/src/properties_window.rs +++ b/src-tauri/src/properties_window.rs @@ -255,6 +255,7 @@ fn is_properties_action(action: &str) -> bool { matches!( action, "apply-properties" + | "set-torrent-file-selection" | "pause-resume" | "verify-torrent" | "set-download-limit" diff --git a/src-tauri/src/queue.rs b/src-tauri/src/queue.rs index 6e82603..eaf05dc 100644 --- a/src-tauri/src/queue.rs +++ b/src-tauri/src/queue.rs @@ -1773,7 +1773,13 @@ impl QueueManager { .lock() .await .get(id) - .and_then(|payload| payload.connections) + .and_then(|payload| { + if payload.is_torrent { + None + } else { + payload.connections + } + }) .map(clamp_download_connections) } @@ -5114,6 +5120,10 @@ fn apply_aria2_connection_options( ); } +fn should_apply_aria2_connection_options(payload: &SpawnPayload) -> bool { + !payload.is_torrent +} + fn apply_aria2_follow_options( options: &mut serde_json::Map, payload: &SpawnPayload, @@ -5286,14 +5296,6 @@ fn aria2_peer_port(value: Option<&serde_json::Value>) -> Option { } } -fn aria2_peer_id(value: Option<&serde_json::Value>) -> Option { - let value = value?.as_str()?.trim(); - if value.is_empty() || value.len() > 128 || value.chars().any(char::is_control) { - return None; - } - Some(value.to_string()) -} - fn aria2_peer_bool(value: Option<&serde_json::Value>) -> bool { match value { Some(serde_json::Value::Bool(value)) => *value, @@ -5465,7 +5467,6 @@ pub(crate) fn parse_torrent_peer_diagnostics( sanitized.push(crate::ipc::TorrentPeer { ip: aria2_peer_ip(peer.get("ip")), port: aria2_peer_port(peer.get("port")), - peer_id: aria2_peer_id(peer.get("peerId")), download_speed: aria2_peer_number(peer.get("downloadSpeed")), upload_speed: aria2_peer_number(peer.get("uploadSpeed")), seeder, @@ -6118,8 +6119,10 @@ impl SidecarSpawner for ProductionSpawner { if !payload.is_torrent { options.insert("out".to_string(), serde_json::json!(safe_filename)); } - let conn = effective_aria2_connections(id, payload).await; - apply_aria2_connection_options(&mut options, conn); + if should_apply_aria2_connection_options(payload) { + let conn = effective_aria2_connections(id, payload).await; + apply_aria2_connection_options(&mut options, conn); + } apply_aria2_follow_options(&mut options, payload); apply_aria2_torrent_options(&mut options, payload)?; let mt = aria2_attempt_limit(payload.max_tries); @@ -6897,6 +6900,33 @@ mod tests { ); } + #[test] + fn torrent_payloads_do_not_use_generic_connection_options() { + let torrent = SpawnPayload { + is_torrent: true, + connections: Some(16), + ..Default::default() + }; + let normal = SpawnPayload { + is_torrent: false, + connections: Some(16), + ..Default::default() + }; + let mut torrent_options = serde_json::Map::new(); + if should_apply_aria2_connection_options(&torrent) { + apply_aria2_connection_options(&mut torrent_options, 16); + } + assert!(!torrent_options.contains_key("split")); + assert!(!torrent_options.contains_key("max-connection-per-server")); + + let mut normal_options = serde_json::Map::new(); + if should_apply_aria2_connection_options(&normal) { + apply_aria2_connection_options(&mut normal_options, 16); + } + assert_eq!(normal_options.get("split"), Some(&serde_json::json!("16"))); + assert_eq!(normal_options.get("max-connection-per-server"), Some(&serde_json::json!("16"))); + } + #[test] fn torrent_network_and_storage_settings_are_normalized_at_the_boundary() { assert_eq!( @@ -7647,7 +7677,7 @@ mod tests { } #[test] - fn torrent_peer_diagnostics_are_bounded_and_omit_bitfields() { + fn torrent_peer_diagnostics_are_bounded_and_omit_identity_and_bitfields() { let mut result = vec![serde_json::json!({ "peerId": "secret-peer-id", "ip": "192.0.2.10", @@ -7678,11 +7708,11 @@ mod tests { assert!(diagnostics.truncated); assert_eq!(diagnostics.peers[0].ip.as_deref(), Some("192.0.2.10")); assert_eq!(diagnostics.peers[0].port, Some(6881)); - assert_eq!(diagnostics.peers[0].peer_id.as_deref(), Some("secret-peer-id")); let serialized = serde_json::to_string(&diagnostics).unwrap(); - assert!(serialized.contains("peerId")); assert!(serialized.contains("192.0.2.")); assert!(!serialized.contains("\"port\":null")); + assert!(!serialized.contains("peerId")); + assert!(!serialized.contains("secret-peer-id")); assert!(!serialized.contains("bitfield")); } diff --git a/src/bindings/TorrentPeer.ts b/src/bindings/TorrentPeer.ts index 115008b..a751e0b 100644 --- a/src/bindings/TorrentPeer.ts +++ b/src/bindings/TorrentPeer.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type TorrentPeer = { ip?: string, port?: number, peerId?: string, downloadSpeed: number, uploadSpeed: number, seeder: boolean, amChoking: boolean, peerChoking: boolean, }; +export type TorrentPeer = { ip?: string, port?: number, downloadSpeed: number, uploadSpeed: number, seeder: boolean, amChoking: boolean, peerChoking: boolean, }; diff --git a/src/components/AddDownloadsModal.tsx b/src/components/AddDownloadsModal.tsx index e188eeb..48fbc5a 100644 --- a/src/components/AddDownloadsModal.tsx +++ b/src/components/AddDownloadsModal.tsx @@ -1501,7 +1501,10 @@ export const AddDownloadsModal = () => { fileName: finalFile, category, dateAdded: new Date().toISOString(), - connections: Number(connections), + // HTTP connections and yt-dlp fragment concurrency are separate + // from BitTorrent peer limits. Torrent rows use bt-max-peers below + // and must not inherit the generic 1–16 HTTP setting. + connections: item.isTorrent ? undefined : Number(connections), speedLimit: speedLimitEnabled ? `${speedLimit}K` : undefined, username: useAuth ? username.trim() : undefined, password: useAuth ? password.trim() : undefined, @@ -2774,13 +2777,15 @@ export const AddDownloadsModal = () => { {t($ => $.addDownloads.transferSettings)}
-
- -
- setConnections(Number(e.target.value))} className="add-download-range w-24 accent-blue-500 cursor-pointer" aria-label={t($ => $.addDownloads.connectionsPerFileAria)} /> - {connections} -
-
+ {!(selectedItemIndex !== null && parsedItems[selectedItemIndex]?.isTorrent) && ( +
+ +
+ setConnections(Number(e.target.value))} className="add-download-range w-24 accent-blue-500 cursor-pointer" aria-label={t($ => $.addDownloads.connectionsPerFileAria)} /> + {connections} +
+
+ )}
@@ -529,7 +731,7 @@ export const PropertiesWindowApp = () => { {formatDownloadBytes(snapshot.downloadedBytes ?? 0)} / {total} {snapshot.speed || '—'} {snapshot.eta || '—'} - {snapshot.activeConnections ?? '—'} / {snapshot.requestedConnections ?? snapshot.connections ?? '—'} {t($ => $.properties.connections)} + {connectionMetric} {isTorrent && {formatTorrentRatio(snapshot.torrentUploadedBytes ?? 0, snapshot.downloadedBytes ?? 0, 'en-US')}} @@ -563,32 +765,55 @@ export const PropertiesWindowApp = () => {
{activeTab === 'overview' &&
- - + +
{t($ => $.properties.url)}

{snapshot.url}

{t($ => $.properties.category)}

{snapshot.category}

+
+ {t($ => $.properties.dateAdded)}{snapshot.dateAdded || '—'} + {t($ => $.properties.lastTry)}{snapshot.lastTry || '—'} + {t($ => $.properties.queueId)}{snapshot.queueId || '—'}{snapshot.queuePosition === undefined ? '' : ` · ${snapshot.queuePosition + 1}`} + {t($ => $.properties.resumable)}{snapshot.resumable === false ? '—' : '✓'} + {snapshot.lastError && <>{t($ => $.properties.lastError)}{snapshot.lastError}} +
+ {snapshot.isMedia === true &&
+ {t($ => $.addDownloads.format)}{snapshot.mediaFormatSelector || '—'} + {t($ => $.addDownloads.quality)}{snapshot.mediaQuality || '—'} + {t($ => $.properties.configuredConcurrency)}{snapshot.connections ?? '—'} +
} {isTorrent && details &&
+ {t($ => $.properties.torrentDetailsDisplayName)}{details.displayName || '—'} {t($ => $.properties.torrentDetailsInfoHash)}{details.infoHash} + {t($ => $.properties.torrentDetailsSize)}{formatDownloadBytes(details.totalBytes)} + {t($ => $.properties.torrentDetailsFiles)}{details.fileCount} {t($ => $.properties.torrentDetailsPieces)}{details.pieceCount} × {formatDownloadBytes(details.pieceLength)} {t($ => $.properties.torrentDetailsPrivate)}{details.private ? t($ => $.properties.torrentDetailsPrivateYes) : t($ => $.properties.torrentDetailsPrivateNo)} + {t($ => $.properties.torrentDetailsCreated)}{details.creationDate || '—'} + {t($ => $.properties.torrentDetailsCreator)}{details.creator || '—'} + {t($ => $.properties.torrentDetailsComment)}{details.comment || '—'}
} - {isTorrent &&
} + {isTorrent &&
}
} {activeTab === 'files' && isTorrent &&
-
-
{fileProgress?.files.map(file => { const checked = selectedFiles === null ? file.selected : selectedFiles.includes(file.index); return ; })}
{t($ => $.properties.torrentFileProgressSelected)}#{t($ => $.properties.torrentFileProgressPath)}{t($ => $.properties.size)}{t($ => $.properties.torrentFileProgressCompleted)}
{ const current = selectedFiles ?? fileProgress.files.filter(candidate => candidate.selected).map(candidate => candidate.index); const next = checked ? current.filter(index => index !== file.index) : [...current, file.index]; setSelectedFiles(next); setDraftTab('files'); }} aria-label={`${file.index + 1} ${file.relativePath}`} />{file.index + 1}{file.relativePath}{formatDownloadBytes(file.length)}{formatDownloadBytes(file.completedLength)} ({file.length ? Math.round(file.completedLength / file.length * 100) : 0}%)
+
+
{fileProgress?.files.map(file => { const checked = selectedFiles === null ? file.selected : selectedFiles.includes(file.index); return ; })}
{t($ => $.properties.torrentFileProgressSelected)}#{t($ => $.properties.torrentFileProgressPath)}{t($ => $.properties.size)}{t($ => $.properties.torrentFileProgressCompleted)}
{ const current = selectedFiles ?? fileProgress.files.filter(candidate => candidate.selected).map(candidate => candidate.index); const next = checked ? current.filter(index => index !== file.index) : [...current, file.index]; setSelectedFiles(next); setDraftTab('files'); }} aria-label={`${file.index + 1} ${file.relativePath}`} />{file.index + 1}{file.relativePath}{formatDownloadBytes(file.length)}{formatDownloadBytes(file.completedLength)} ({file.length ? Math.round(file.completedLength / file.length * 100) : 0}%)
{diagnosticsLoading &&

{t($ => $.properties.torrentPeerDiagnosticsLoading)}

} {!diagnosticsLoading && !fileProgress && !diagnosticError &&

{t($ => $.properties.torrentFileProgressUnavailable)}

} {diagnosticError &&

{diagnosticError}

}
} {activeTab === 'trackers' && isTorrent &&
-