1 parent
96a32ea8fb
commit
a643e7e3ab
106 files changed
+2223
-4669
No files matched your search
+8
-21
@@ -3,29 +3,25 @@ use std::collections::HashMap;
|
||||
use serde::Serialize;
|
||||
use tokio::sync::{RwLock, broadcast};
|
||||
|
||||
/// Events that can be pushed to connected WebSocket clients.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
#[allow(dead_code)]
|
||||
pub enum MailEvent {
|
||||
/// New messages arrived in a folder.
|
||||
NewMessages {
|
||||
folder: String,
|
||||
count: u32,
|
||||
latest_uid: Option<u32>,
|
||||
latest_sender: Option<String>,
|
||||
latest_subject: Option<String>,
|
||||
},
|
||||
/// Flags changed on messages in a folder.
|
||||
FlagsChanged { folder: String },
|
||||
/// Folder list or counts changed.
|
||||
|
||||
FlagsChanged {
|
||||
folder: String,
|
||||
},
|
||||
|
||||
FolderUpdated,
|
||||
}
|
||||
|
||||
/// Fan-out event bus: per-user broadcast channels.
|
||||
///
|
||||
/// Each user (keyed by `user_hash`) gets a `broadcast::Sender` that can have
|
||||
/// multiple receivers (one per WebSocket connection). When all receivers are
|
||||
/// dropped the channel is cleaned up.
|
||||
pub struct EventBus {
|
||||
channels: RwLock<HashMap<String, broadcast::Sender<MailEvent>>>,
|
||||
}
|
||||
@@ -37,10 +33,7 @@ impl EventBus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe to events for a user. Creates the channel if it doesn't exist.
|
||||
/// Returns a receiver that will receive all future events for this user.
|
||||
pub async fn subscribe(&self, user_hash: &str) -> broadcast::Receiver<MailEvent> {
|
||||
// Fast path: read lock.
|
||||
{
|
||||
let channels = self.channels.read().await;
|
||||
if let Some(tx) = channels.get(user_hash) {
|
||||
@@ -48,7 +41,6 @@ impl EventBus {
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path: write lock to insert.
|
||||
let mut channels = self.channels.write().await;
|
||||
let tx = channels.entry(user_hash.to_string()).or_insert_with(|| {
|
||||
let (tx, _) = broadcast::channel(64);
|
||||
@@ -57,22 +49,16 @@ impl EventBus {
|
||||
tx.subscribe()
|
||||
}
|
||||
|
||||
/// Publish an event to all connected clients for a user.
|
||||
/// No-op if no one is listening.
|
||||
pub async fn publish(&self, user_hash: &str, event: MailEvent) {
|
||||
let channels = self.channels.read().await;
|
||||
if let Some(tx) = channels.get(user_hash) {
|
||||
// Ignore error (no receivers connected).
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a user's channel when no more connections exist.
|
||||
/// Called when the last WebSocket for a user disconnects.
|
||||
pub async fn cleanup(&self, user_hash: &str) {
|
||||
let mut channels = self.channels.write().await;
|
||||
if let Some(tx) = channels.get(user_hash) {
|
||||
// Only remove if no active receivers remain.
|
||||
if tx.receiver_count() == 0 {
|
||||
channels.remove(user_hash);
|
||||
}
|
||||
@@ -101,6 +87,7 @@ mod tests {
|
||||
MailEvent::NewMessages {
|
||||
folder: "INBOX".to_string(),
|
||||
count: 1,
|
||||
latest_uid: Some(42),
|
||||
latest_sender: Some("alice@example.com".to_string()),
|
||||
latest_subject: Some("Hello".to_string()),
|
||||
},
|
||||
@@ -120,7 +107,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn publish_to_nonexistent_user_is_noop() {
|
||||
let bus = EventBus::new();
|
||||
// Should not panic.
|
||||
|
||||
bus.publish("ghost", MailEvent::FolderUpdated).await;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user