First Push
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""Server-Sent Event bus for pushing updates to displays.
|
||||
|
||||
Events are scoped by user_id so that each user's display only receives
|
||||
updates about their own data (alarms, calendar, pomodoro, ...).
|
||||
"""
|
||||
import json
|
||||
import queue
|
||||
import threading
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""Simple pub/sub event system with SSE support.
|
||||
|
||||
publish(event_type, data, user_id=None): when user_id is given, only
|
||||
subscribers registered for that user receive the event.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._subscribers: list[tuple[int | None, queue.Queue]] = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def subscribe(self, user_id: int | None = None) -> queue.Queue:
|
||||
"""Subscribe to events. user_id=None receives ALL events (broadcast)."""
|
||||
q: queue.Queue = queue.Queue()
|
||||
with self._lock:
|
||||
self._subscribers.append((user_id, q))
|
||||
return q
|
||||
|
||||
def unsubscribe(self, q: queue.Queue):
|
||||
with self._lock:
|
||||
self._subscribers = [(uid, sq) for uid, sq in self._subscribers if sq is not q]
|
||||
|
||||
def publish(self, event_type: str, data: dict, user_id: int | None = None):
|
||||
msg = json.dumps({"type": event_type, "data": data})
|
||||
with self._lock:
|
||||
dead = []
|
||||
for sub_uid, q in self._subscribers:
|
||||
if sub_uid is not None and user_id is not None and sub_uid != user_id:
|
||||
continue
|
||||
try:
|
||||
q.put_nowait(msg)
|
||||
except queue.Full:
|
||||
dead.append((sub_uid, q))
|
||||
for item in dead:
|
||||
self._subscribers.remove(item)
|
||||
|
||||
def broadcast(self):
|
||||
"""Send heartbeat to all subscribers."""
|
||||
self.publish("heartbeat", {"ts": __import__("time").time()})
|
||||
Reference in New Issue
Block a user