Puente Power Automate, servidor local del tablero HTML v7 y exportacion a Excel/TSV para monitoreo de proyectos en Microsoft Planner. Co-authored-by: Cursor <cursoragent@cursor.com>
60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""Hub de eventos SSE para notificar cambios del tablero a los navegadores."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import queue
|
|
import threading
|
|
from collections.abc import Iterator
|
|
|
|
|
|
class EventHub:
|
|
def __init__(self) -> None:
|
|
self._lock = threading.Lock()
|
|
self._clients: list[queue.Queue[tuple[str, str] | None]] = []
|
|
|
|
def subscribe(self) -> queue.Queue[tuple[str, str] | None]:
|
|
client_queue: queue.Queue[tuple[str, str] | None] = queue.Queue()
|
|
with self._lock:
|
|
self._clients.append(client_queue)
|
|
return client_queue
|
|
|
|
def unsubscribe(self, client_queue: queue.Queue[tuple[str, str] | None]) -> None:
|
|
with self._lock:
|
|
if client_queue in self._clients:
|
|
self._clients.remove(client_queue)
|
|
client_queue.put(None)
|
|
|
|
def broadcast(self, event: str, data: str = "") -> int:
|
|
with self._lock:
|
|
clients = list(self._clients)
|
|
for client_queue in clients:
|
|
try:
|
|
client_queue.put_nowait((event, data))
|
|
except queue.Full:
|
|
pass
|
|
return len(clients)
|
|
|
|
@property
|
|
def client_count(self) -> int:
|
|
with self._lock:
|
|
return len(self._clients)
|
|
|
|
|
|
def iter_sse_messages(
|
|
client_queue: queue.Queue[tuple[str, str] | None],
|
|
*,
|
|
heartbeat_seconds: int = 30,
|
|
) -> Iterator[bytes]:
|
|
"""Genera lineas SSE (incluye heartbeats) hasta que el cliente se desconecta."""
|
|
yield b": connected\n\n"
|
|
while True:
|
|
try:
|
|
item = client_queue.get(timeout=heartbeat_seconds)
|
|
except queue.Empty:
|
|
yield b": heartbeat\n\n"
|
|
continue
|
|
if item is None:
|
|
break
|
|
event, data = item
|
|
payload = f"event: {event}\ndata: {data}\n\n"
|
|
yield payload.encode("utf-8")
|