feat: add global stats broadcasting and statusbar integration

This commit is contained in:
spinline
2026-02-03 23:12:51 +03:00
parent 6a5ce421b9
commit 563ffad3ab
5 changed files with 105 additions and 12 deletions

View File

@@ -148,12 +148,19 @@ async fn main() {
let mut previous_torrents: Vec<Torrent> = Vec::new();
loop {
match sse::fetch_torrents(&client).await {
// 1. Fetch Torrents
let torrents_result = sse::fetch_torrents(&client).await;
// 2. Fetch Global Stats
let stats_result = sse::fetch_global_stats(&client).await;
// Handle Torrents
match torrents_result {
Ok(new_torrents) => {
// 1. Update latest state (always)
// Update latest state
let _ = tx_clone.send(new_torrents.clone());
// 2. Calculate Diff and Broadcasting
// Calculate Diff and Broadcasting
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
@@ -180,6 +187,17 @@ async fn main() {
tracing::error!("Error fetching torrents in background: {}", e);
}
}
// Handle Stats
match stats_result {
Ok(stats) => {
let _ = event_bus_tx.send(AppEvent::Stats(stats));
}
Err(e) => {
tracing::warn!("Error fetching global stats: {}", e);
}
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
});

View File

@@ -3,7 +3,7 @@ use crate::AppState;
use axum::extract::State;
use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use shared::{AppEvent, Torrent, TorrentStatus};
use shared::{AppEvent, GlobalStats, Torrent, TorrentStatus};
use std::convert::Infallible;
use tokio_stream::StreamExt;
@@ -52,7 +52,6 @@ fn from_rtorrent_row(row: Vec<String>) -> Torrent {
let is_hashing = parse_long(row.get(11));
let label_raw = parse_string(row.get(12));
// Treat empty label as None
let label = if label_raw.is_empty() {
None
} else {
@@ -115,6 +114,31 @@ pub async fn fetch_torrents(client: &RtorrentClient) -> Result<Vec<Torrent>, Xml
Ok(torrents)
}
pub async fn fetch_global_stats(client: &RtorrentClient) -> Result<GlobalStats, XmlRpcError> {
// Parallel calls would be better but let's keep it simple sequential for now.
// NOTE: This adds 4 roundtrips per second. If this is too slow, we should use multicall via system.multicall (if supported)
// or just accept the overhead. Unix socket overhead is very low.
// We ignore errors on individual stats to not break the whole loop, using defaults.
// But connection errors should propagate.
let down_rate_str = client.call("throttle.global_down.rate", &[]).await?;
let up_rate_str = client.call("throttle.global_up.rate", &[]).await?;
let down_limit_str = client.call("throttle.global_down.max_rate", &[]).await?;
let up_limit_str = client.call("throttle.global_up.max_rate", &[]).await?;
// Optionally get free space. "directory.default" then "d.free_space_path"?? No "get_directory_free_space"
// Let's skip free space for high frequency updates.
Ok(GlobalStats {
down_rate: down_rate_str.parse().unwrap_or(0),
up_rate: up_rate_str.parse().unwrap_or(0),
down_limit: down_limit_str.parse().ok(),
up_limit: up_limit_str.parse().ok(),
free_space: None,
})
}
pub async fn sse_handler(
State(state): State<AppState>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {