Compare commits

...

2 Commits

Author SHA1 Message Date
spinline
1432dec828 perf: implement dynamic polling interval based on active clients
All checks were successful
Build MIPS Binary / build (push) Successful in 4m35s
2026-02-08 23:57:32 +03:00
spinline
1bb3475d61 perf: optimize torrent store with HashMap for O(1) updates
All checks were successful
Build MIPS Binary / build (push) Successful in 4m57s
2026-02-08 23:52:23 +03:00
4 changed files with 62 additions and 55 deletions

View File

@@ -359,6 +359,14 @@ async fn main() {
let mut backoff_duration = Duration::from_secs(1); let mut backoff_duration = Duration::from_secs(1);
loop { loop {
// Determine polling interval based on active clients
let active_clients = event_bus_tx.receiver_count();
let loop_interval = if active_clients > 0 {
Duration::from_secs(1)
} else {
Duration::from_secs(30)
};
// 1. Fetch Torrents // 1. Fetch Torrents
let torrents_result = sse::fetch_torrents(&client).await; let torrents_result = sse::fetch_torrents(&client).await;
@@ -429,6 +437,9 @@ async fn main() {
} }
previous_torrents = new_torrents; previous_torrents = new_torrents;
// Success case: sleep for the determined interval
tokio::time::sleep(loop_interval).await;
} }
Err(e) => { Err(e) => {
tracing::error!("Error fetching torrents in background: {}", e); tracing::error!("Error fetching torrents in background: {}", e);
@@ -449,20 +460,15 @@ async fn main() {
"Backoff: Sleeping for {:?} due to rTorrent error.", "Backoff: Sleeping for {:?} due to rTorrent error.",
backoff_duration backoff_duration
); );
tokio::time::sleep(backoff_duration).await;
} }
} }
// Handle Stats // Handle Stats
match stats_result { if let Ok(stats) = stats_result {
Ok(stats) => { let _ = event_bus_tx.send(AppEvent::Stats(stats));
let _ = event_bus_tx.send(AppEvent::Stats(stats));
}
Err(e) => {
tracing::warn!("Error fetching global stats: {}", e);
}
} }
tokio::time::sleep(backoff_duration).await;
} }
}); });

View File

@@ -6,52 +6,47 @@ use crate::api;
pub fn Sidebar() -> impl IntoView { pub fn Sidebar() -> impl IntoView {
let store = use_context::<crate::store::TorrentStore>().expect("store not provided"); let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
let total_count = move || store.torrents.get().len(); let total_count = move || store.torrents.with(|map| map.len());
let downloading_count = move || { let downloading_count = move || {
store store.torrents.with(|map| {
.torrents map.values()
.get() .filter(|t| t.status == shared::TorrentStatus::Downloading)
.iter() .count()
.filter(|t| t.status == shared::TorrentStatus::Downloading) })
.count()
}; };
let seeding_count = move || { let seeding_count = move || {
store store.torrents.with(|map| {
.torrents map.values()
.get() .filter(|t| t.status == shared::TorrentStatus::Seeding)
.iter() .count()
.filter(|t| t.status == shared::TorrentStatus::Seeding) })
.count()
}; };
let completed_count = move || { let completed_count = move || {
store store.torrents.with(|map| {
.torrents map.values()
.get() .filter(|t| {
.iter() t.status == shared::TorrentStatus::Seeding
.filter(|t| { || (t.status == shared::TorrentStatus::Paused && t.percent_complete >= 100.0)
t.status == shared::TorrentStatus::Seeding })
|| (t.status == shared::TorrentStatus::Paused && t.percent_complete >= 100.0) .count()
}) })
.count()
}; };
let paused_count = move || { let paused_count = move || {
store store.torrents.with(|map| {
.torrents map.values()
.get() .filter(|t| t.status == shared::TorrentStatus::Paused)
.iter() .count()
.filter(|t| t.status == shared::TorrentStatus::Paused) })
.count()
}; };
let inactive_count = move || { let inactive_count = move || {
store store.torrents.with(|map| {
.torrents map.values()
.get() .filter(|t| {
.iter() t.status == shared::TorrentStatus::Paused
.filter(|t| { || t.status == shared::TorrentStatus::Error
t.status == shared::TorrentStatus::Paused })
|| t.status == shared::TorrentStatus::Error .count()
}) })
.count()
}; };
let close_drawer = move || { let close_drawer = move || {

View File

@@ -82,9 +82,10 @@ pub fn TorrentTable() -> impl IntoView {
let sort_dir = create_rw_signal(SortDirection::Descending); let sort_dir = create_rw_signal(SortDirection::Descending);
let filtered_torrents = move || { let filtered_torrents = move || {
let mut torrents = store // Convert HashMap values to Vec for filtering and sorting
.torrents let torrents: Vec<shared::Torrent> = store.torrents.with(|map| map.values().cloned().collect());
.get()
let mut torrents = torrents
.into_iter() .into_iter()
.filter(|t| { .filter(|t| {
let filter = store.filter.get(); let filter = store.filter.get();

View File

@@ -113,9 +113,11 @@ impl FilterStatus {
} }
} }
use std::collections::HashMap;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct TorrentStore { pub struct TorrentStore {
pub torrents: RwSignal<Vec<Torrent>>, pub torrents: RwSignal<HashMap<String, Torrent>>,
pub filter: RwSignal<FilterStatus>, pub filter: RwSignal<FilterStatus>,
pub search_query: RwSignal<String>, pub search_query: RwSignal<String>,
pub global_stats: RwSignal<GlobalStats>, pub global_stats: RwSignal<GlobalStats>,
@@ -124,7 +126,7 @@ pub struct TorrentStore {
} }
pub fn provide_torrent_store() { pub fn provide_torrent_store() {
let torrents = create_rw_signal(Vec::<Torrent>::new()); let torrents = create_rw_signal(HashMap::new());
let filter = create_rw_signal(FilterStatus::All); let filter = create_rw_signal(FilterStatus::All);
let search_query = create_rw_signal(String::new()); let search_query = create_rw_signal(String::new());
let global_stats = create_rw_signal(GlobalStats::default()); let global_stats = create_rw_signal(GlobalStats::default());
@@ -193,12 +195,15 @@ pub fn provide_torrent_store() {
if let Ok(event) = serde_json::from_str::<AppEvent>(&data_str) { if let Ok(event) = serde_json::from_str::<AppEvent>(&data_str) {
match event { match event {
AppEvent::FullList { torrents: list, .. } => { AppEvent::FullList { torrents: list, .. } => {
torrents.set(list); let map: HashMap<String, Torrent> = list
.into_iter()
.map(|t| (t.hash.clone(), t))
.collect();
torrents.set(map);
} }
AppEvent::Update(update) => { AppEvent::Update(update) => {
torrents.update(|list| { torrents.update(|map| {
if let Some(t) = list.iter_mut().find(|t| t.hash == update.hash) if let Some(t) = map.get_mut(&update.hash) {
{
if let Some(name) = update.name { if let Some(name) = update.name {
t.name = name; t.name = name;
} }