Files
imagefind/backend/imagefind/events.py
T

52 lines
1.7 KiB
Python

from __future__ import annotations
import asyncio
import itertools
import time
from collections.abc import AsyncIterator
class EventBroker:
"""Small in-process fan-out for UI invalidation events."""
def __init__(self) -> None:
self._loop: asyncio.AbstractEventLoop | None = None
self._subscribers: dict[int, asyncio.Queue[dict]] = {}
self._ids = itertools.count(1)
def bind(self) -> None:
self._loop = asyncio.get_running_loop()
def _publish(self, event: dict) -> None:
for queue in list(self._subscribers.values()):
if queue.full():
try:
queue.get_nowait()
except asyncio.QueueEmpty:
pass
queue.put_nowait(event)
def publish(self, topic: str, **payload) -> None:
loop = self._loop
if loop is None or loop.is_closed():
return
event = {"topic": topic, "at": time.time(), **payload}
loop.call_soon_threadsafe(self._publish, event)
async def subscribe(self) -> AsyncIterator[dict | None]:
subscriber_id = next(self._ids)
queue: asyncio.Queue[dict] = asyncio.Queue(maxsize=32)
self._subscribers[subscriber_id] = queue
try:
yield {"topic": "connected", "at": time.time()}
while True:
try:
yield await asyncio.wait_for(queue.get(), timeout=20)
except TimeoutError:
yield None
finally:
self._subscribers.pop(subscriber_id, None)
def status(self) -> dict[str, int | bool]:
return {"ready": self._loop is not None, "subscribers": len(self._subscribers)}