Compare commits
4 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d15392e148 | ||
|
|
f3121898e2 | ||
|
|
e1370db6ce | ||
|
|
1432dec828 |
@@ -45,6 +45,7 @@ pub struct AppState {
|
||||
pub db: db::Db,
|
||||
#[cfg(feature = "push-notifications")]
|
||||
pub push_store: push::PushSubscriptionStore,
|
||||
pub notify_poll: Arc<tokio::sync::Notify>,
|
||||
}
|
||||
|
||||
async fn auth_middleware(
|
||||
@@ -336,6 +337,8 @@ async fn main() {
|
||||
#[cfg(not(feature = "push-notifications"))]
|
||||
let push_store = ();
|
||||
|
||||
let notify_poll = Arc::new(tokio::sync::Notify::new());
|
||||
|
||||
let app_state = AppState {
|
||||
tx: tx.clone(),
|
||||
event_bus: event_bus.clone(),
|
||||
@@ -343,6 +346,7 @@ async fn main() {
|
||||
db: db.clone(),
|
||||
#[cfg(feature = "push-notifications")]
|
||||
push_store,
|
||||
notify_poll: notify_poll.clone(),
|
||||
};
|
||||
|
||||
// Spawn background task to poll rTorrent
|
||||
@@ -351,6 +355,7 @@ async fn main() {
|
||||
let socket_path = args.socket.clone(); // Clone for background task
|
||||
#[cfg(feature = "push-notifications")]
|
||||
let push_store_clone = app_state.push_store.clone();
|
||||
let notify_poll_clone = notify_poll.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let client = xmlrpc::RtorrentClient::new(&socket_path);
|
||||
@@ -359,6 +364,14 @@ async fn main() {
|
||||
let mut backoff_duration = Duration::from_secs(1);
|
||||
|
||||
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
|
||||
let torrents_result = sse::fetch_torrents(&client).await;
|
||||
|
||||
@@ -429,6 +442,14 @@ async fn main() {
|
||||
}
|
||||
|
||||
previous_torrents = new_torrents;
|
||||
|
||||
// Success case: wait for the determined interval OR a wakeup notification
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(loop_interval) => {},
|
||||
_ = notify_poll_clone.notified() => {
|
||||
tracing::debug!("Background loop awakened by new client connection");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error fetching torrents in background: {}", e);
|
||||
@@ -449,20 +470,15 @@ async fn main() {
|
||||
"Backoff: Sleeping for {:?} due to rTorrent error.",
|
||||
backoff_duration
|
||||
);
|
||||
|
||||
tokio::time::sleep(backoff_duration).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Stats
|
||||
match stats_result {
|
||||
Ok(stats) => {
|
||||
if let Ok(stats) = stats_result {
|
||||
let _ = event_bus_tx.send(AppEvent::Stats(stats));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Error fetching global stats: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(backoff_duration).await;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ pub enum ScgiError {
|
||||
#[allow(dead_code)]
|
||||
#[error("Protocol Error: {0}")]
|
||||
Protocol(String),
|
||||
#[error("Timeout: SCGI request took too long")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
pub struct ScgiRequest {
|
||||
@@ -78,20 +80,30 @@ impl ScgiRequest {
|
||||
}
|
||||
|
||||
pub async fn send_request(socket_path: &str, request: ScgiRequest) -> Result<Bytes, ScgiError> {
|
||||
let perform_request = async {
|
||||
let mut stream = UnixStream::connect(socket_path).await?;
|
||||
let data = request.encode();
|
||||
stream.write_all(&data).await?;
|
||||
|
||||
let mut response = Vec::new();
|
||||
stream.read_to_end(&mut response).await?;
|
||||
Ok::<Vec<u8>, std::io::Error>(response)
|
||||
};
|
||||
|
||||
let response = tokio::time::timeout(std::time::Duration::from_secs(10), perform_request)
|
||||
.await
|
||||
.map_err(|_| ScgiError::Timeout)??;
|
||||
|
||||
let double_newline = b"\r\n\r\n";
|
||||
if let Some(pos) = response
|
||||
let mut response_vec = response;
|
||||
if let Some(pos) = response_vec
|
||||
.windows(double_newline.len())
|
||||
.position(|window| window == double_newline)
|
||||
{
|
||||
Ok(Bytes::from(response.split_off(pos + double_newline.len())))
|
||||
Ok(Bytes::from(
|
||||
response_vec.split_off(pos + double_newline.len()),
|
||||
))
|
||||
} else {
|
||||
Ok(Bytes::from(response))
|
||||
Ok(Bytes::from(response_vec))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,9 @@ pub async fn fetch_global_stats(client: &RtorrentClient) -> Result<GlobalStats,
|
||||
pub async fn sse_handler(
|
||||
State(state): State<AppState>,
|
||||
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
|
||||
// Notify background worker to wake up and poll immediately
|
||||
state.notify_poll.notify_one();
|
||||
|
||||
// Get initial value synchronously (from the watch channel's current state)
|
||||
let initial_rx = state.tx.subscribe();
|
||||
let initial_torrents = initial_rx.borrow().clone();
|
||||
|
||||
@@ -254,7 +254,7 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
<div class="flex items-center">"Status" {move || sort_arrow(SortColumn::Status)}</div>
|
||||
</th>
|
||||
<th class="w-24 cursor-pointer hover:bg-base-300 group select-none" on:click=move |_| handle_sort(SortColumn::DownSpeed)>
|
||||
<div class="flex items-center">"Down Speed" {move || sort_arrow(SortColumn::DownSpeed)}</div>
|
||||
<div class="flex items-center">"DL Speed" {move || sort_arrow(SortColumn::DownSpeed)}</div>
|
||||
</th>
|
||||
<th class="w-24 cursor-pointer hover:bg-base-300 group select-none" on:click=move |_| handle_sort(SortColumn::UpSpeed)>
|
||||
<div class="flex items-center">"Up Speed" {move || sort_arrow(SortColumn::UpSpeed)}</div>
|
||||
@@ -345,7 +345,7 @@ pub fn TorrentTable() -> impl IntoView {
|
||||
(SortColumn::Size, "Size"),
|
||||
(SortColumn::Progress, "Progress"),
|
||||
(SortColumn::Status, "Status"),
|
||||
(SortColumn::DownSpeed, "Down Speed"),
|
||||
(SortColumn::DownSpeed, "DL Speed"),
|
||||
(SortColumn::UpSpeed, "Up Speed"),
|
||||
(SortColumn::ETA, "ETA"),
|
||||
(SortColumn::AddedDate, "Date"),
|
||||
|
||||
Reference in New Issue
Block a user