mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-19 15:46:17 +00:00
fix(torrent): use live peer telemetry in properties
- source the Properties peer card from live Aria2 status counts - distinguish connected peers from unavailable peer details - remove redundant peer-summary IPC and harden count parsing - add responsive, accessible peer/seeder presentation and regressions
This commit is contained in:
@@ -346,16 +346,6 @@ pub struct TorrentPeerDiagnostics {
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../../src/bindings/")]
|
||||
pub struct TorrentPeerSummary {
|
||||
#[ts(type = "number")]
|
||||
pub total_peers: u32,
|
||||
#[ts(type = "number")]
|
||||
pub total_seeders: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../../src/bindings/")]
|
||||
|
||||
+22
-23
@@ -7470,17 +7470,6 @@ async fn get_torrent_peers(
|
||||
state.queue_manager.get_aria2_torrent_peers(&id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_torrent_peer_summary(
|
||||
caller: tauri::WebviewWindow,
|
||||
properties: tauri::State<'_, properties_window::PropertiesWindowRegistry>,
|
||||
state: tauri::State<'_, AppState>,
|
||||
id: String,
|
||||
) -> Result<crate::ipc::TorrentPeerSummary, String> {
|
||||
properties_window::ensure_properties_or_main(&caller, &properties, &id)?;
|
||||
state.queue_manager.get_aria2_torrent_peer_summary(&id).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_torrent_availability(
|
||||
caller: tauri::WebviewWindow,
|
||||
@@ -11194,7 +11183,7 @@ mod tests {
|
||||
observe_aria2_connections, observe_aria2_connections_with_epoch,
|
||||
Aria2ConnectionObservation, Aria2ConnectionSample, Aria2RecoveryReason,
|
||||
FrontendExitFlush,
|
||||
aria2_active_connection_count,
|
||||
aria2_active_connection_count, aria2_nonnegative_count,
|
||||
parse_media_playlist_metadata,
|
||||
normalize_media_connections,
|
||||
validate_enqueue_url, validate_enqueue_uris, validate_keychain_grant_request_id,
|
||||
@@ -11734,6 +11723,10 @@ mod tests {
|
||||
0
|
||||
);
|
||||
assert_eq!(aria2_active_connection_count(&json!({})), 0);
|
||||
assert_eq!(aria2_nonnegative_count(&json!({"numSeeders": "6"}), "numSeeders"), Some(6));
|
||||
assert_eq!(aria2_nonnegative_count(&json!({"numSeeders": 6}), "numSeeders"), Some(6));
|
||||
assert_eq!(aria2_nonnegative_count(&json!({"numSeeders": "-1"}), "numSeeders"), None);
|
||||
assert_eq!(aria2_nonnegative_count(&json!({}), "numSeeders"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -13940,17 +13933,23 @@ struct Aria2ConnectionSample<'a> {
|
||||
now: Instant,
|
||||
}
|
||||
|
||||
fn aria2_active_connection_count(status_info: &serde_json::Value) -> i32 {
|
||||
fn aria2_count_value(value: &serde_json::Value) -> Option<i32> {
|
||||
value
|
||||
.as_str()
|
||||
.and_then(|value| value.parse::<i64>().ok())
|
||||
.or_else(|| value.as_i64())
|
||||
.and_then(|value| i32::try_from(value).ok())
|
||||
}
|
||||
|
||||
fn aria2_nonnegative_count(status_info: &serde_json::Value, key: &str) -> Option<i32> {
|
||||
status_info
|
||||
.get("connections")
|
||||
.and_then(|value| {
|
||||
value
|
||||
.as_str()
|
||||
.and_then(|value| value.parse::<i32>().ok())
|
||||
.or_else(|| value.as_i64().and_then(|value| i32::try_from(value).ok()))
|
||||
})
|
||||
.get(key)
|
||||
.and_then(aria2_count_value)
|
||||
.filter(|value| *value >= 0)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn aria2_active_connection_count(status_info: &serde_json::Value) -> i32 {
|
||||
aria2_nonnegative_count(status_info, "connections").unwrap_or(0)
|
||||
}
|
||||
|
||||
const ARIA2_CONNECTION_RECOVERY_DELAY: Duration = Duration::from_secs(30);
|
||||
@@ -14968,7 +14967,7 @@ pub fn run() {
|
||||
let speed_bytes = status_info.get("downloadSpeed").and_then(|s| s.as_str()).unwrap_or("0").parse::<f64>().unwrap_or(0.0);
|
||||
let uploaded_bytes = status_info.get("uploadLength").and_then(|s| s.as_str()).and_then(|value| value.parse::<u64>().ok());
|
||||
let upload_speed_bytes = status_info.get("uploadSpeed").and_then(|s| s.as_str()).and_then(|value| value.parse::<f64>().ok());
|
||||
let num_seeders = status_info.get("numSeeders").and_then(|s| s.as_str()).and_then(|value| value.parse::<i32>().ok());
|
||||
let num_seeders = aria2_nonnegative_count(status_info, "numSeeders");
|
||||
let is_seeder = status_info.get("seeder").is_some_and(|value| {
|
||||
value.as_str() == Some("true") || value.as_bool() == Some(true)
|
||||
});
|
||||
@@ -15558,7 +15557,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_frontend_exit, 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, get_torrent_peer_summary, get_torrent_availability, get_torrent_file_progress, get_torrent_piece_progress, get_torrent_file_selection, set_torrent_file_selection, get_torrent_details, get_torrent_magnet_link, export_torrent_metadata, move_torrent_data, cancel_torrent_move_data, verify_torrent_data, get_torrent_web_seeds, set_torrent_web_seeds, set_torrent_max_open_files, set_torrent_overall_upload_limit, set_global_speed_limit, remove_download, get_download_primary_path,
|
||||
get_extension_server_port, set_extension_frontend_ready, ack_frontend_exit, 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, get_torrent_availability, get_torrent_file_progress, get_torrent_piece_progress, get_torrent_file_selection, set_torrent_file_selection, get_torrent_details, get_torrent_magnet_link, export_torrent_metadata, move_torrent_data, cancel_torrent_move_data, verify_torrent_data, get_torrent_web_seeds, set_torrent_web_seeds, set_torrent_max_open_files, set_torrent_overall_upload_limit, 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,
|
||||
|
||||
+9
-49
@@ -2734,19 +2734,6 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
||||
parse_torrent_peer_diagnostics(result)
|
||||
}
|
||||
|
||||
/// Return only aggregate peer/seeder counts for the current Torrent GID.
|
||||
/// No peer addresses, IDs, bitfields, or transfer rates cross the IPC
|
||||
/// boundary.
|
||||
pub async fn get_aria2_torrent_peer_summary(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<crate::ipc::TorrentPeerSummary, String> {
|
||||
let result = self
|
||||
.get_aria2_torrent_peer_result(id, "peer summary")
|
||||
.await?;
|
||||
parse_torrent_peer_summary(result)
|
||||
}
|
||||
|
||||
/// Compute bounded, anonymized swarm availability for the current
|
||||
/// Torrent lifecycle. The raw local/peer bitfields are consumed in native
|
||||
/// memory and never returned to the frontend.
|
||||
@@ -5955,9 +5942,14 @@ pub(crate) fn parse_torrent_availability(
|
||||
})
|
||||
}
|
||||
|
||||
fn torrent_peer_summary_from_array(
|
||||
struct TorrentPeerCounts {
|
||||
total_peers: u32,
|
||||
total_seeders: u32,
|
||||
}
|
||||
|
||||
fn torrent_peer_counts_from_array(
|
||||
peers: &[serde_json::Value],
|
||||
) -> Result<crate::ipc::TorrentPeerSummary, String> {
|
||||
) -> Result<TorrentPeerCounts, String> {
|
||||
if peers.len() > MAX_TORRENT_PEER_RESPONSE {
|
||||
return Err("aria2.getPeers returned too many peers".to_string());
|
||||
}
|
||||
@@ -5973,28 +5965,19 @@ fn torrent_peer_summary_from_array(
|
||||
total_seeders = total_seeders.saturating_add(1);
|
||||
}
|
||||
}
|
||||
Ok(crate::ipc::TorrentPeerSummary {
|
||||
Ok(TorrentPeerCounts {
|
||||
total_peers: u32::try_from(peers.len()).unwrap_or(u32::MAX),
|
||||
total_seeders,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_torrent_peer_summary(
|
||||
result: serde_json::Value,
|
||||
) -> Result<crate::ipc::TorrentPeerSummary, String> {
|
||||
let peers = result
|
||||
.as_array()
|
||||
.ok_or_else(|| "aria2.getPeers returned a non-array result".to_string())?;
|
||||
torrent_peer_summary_from_array(peers)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_torrent_peer_diagnostics(
|
||||
result: serde_json::Value,
|
||||
) -> Result<crate::ipc::TorrentPeerDiagnostics, String> {
|
||||
let peers = result
|
||||
.as_array()
|
||||
.ok_or_else(|| "aria2.getPeers returned a non-array result".to_string())?;
|
||||
let summary = torrent_peer_summary_from_array(peers)?;
|
||||
let summary = torrent_peer_counts_from_array(peers)?;
|
||||
let mut sanitized = Vec::with_capacity(peers.len().min(MAX_TORRENT_PEER_DIAGNOSTICS));
|
||||
|
||||
for peer in peers.iter().take(MAX_TORRENT_PEER_DIAGNOSTICS) {
|
||||
@@ -8344,28 +8327,10 @@ mod tests {
|
||||
assert!(!serialized.contains("bitfield"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn torrent_peer_summary_counts_all_seeders_without_returning_peer_data() {
|
||||
let result = serde_json::json!([
|
||||
{"ip": "192.0.2.10", "seeder": "true", "bitfield": "secret"},
|
||||
{"ip": "192.0.2.11", "seeder": false},
|
||||
{"ip": "192.0.2.12", "seeder": true}
|
||||
]);
|
||||
|
||||
let summary = parse_torrent_peer_summary(result).unwrap();
|
||||
assert_eq!(summary.total_peers, 3);
|
||||
assert_eq!(summary.total_seeders, 2);
|
||||
let serialized = serde_json::to_string(&summary).unwrap();
|
||||
assert!(!serialized.contains("192.0.2."));
|
||||
assert!(!serialized.contains("bitfield"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn torrent_peer_diagnostics_reject_non_array_results() {
|
||||
let error = parse_torrent_peer_diagnostics(serde_json::json!({"peers": []})).unwrap_err();
|
||||
assert!(error.contains("non-array"));
|
||||
let summary_error = parse_torrent_peer_summary(serde_json::json!({"peers": []})).unwrap_err();
|
||||
assert!(summary_error.contains("non-array"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -8375,11 +8340,6 @@ mod tests {
|
||||
}, "not-a-peer"]))
|
||||
.unwrap_err();
|
||||
assert!(error.contains("malformed"));
|
||||
let summary_error = parse_torrent_peer_summary(serde_json::json!([{
|
||||
"seeder": true
|
||||
}, "not-a-peer"]))
|
||||
.unwrap_err();
|
||||
assert!(summary_error.contains("malformed"));
|
||||
}
|
||||
|
||||
fn test_torrent_progress_metadata() -> Vec<crate::ipc::TorrentFile> {
|
||||
|
||||
Reference in New Issue
Block a user