Initial commit: InkFlow — EPUB vers livre audio local (MLX/Kokoro)
This commit is contained in:
47
backend/inkflow/api/ws.py
Normal file
47
backend/inkflow/api/ws.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Gestionnaire de connexions WebSocket avec diffusion thread-safe.
|
||||
|
||||
L'orchestrateur tourne dans un thread worker ; il appelle `broadcast_threadsafe`
|
||||
qui replanifie l'envoi sur la boucle asyncio de l'API.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import WebSocket
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self) -> None:
|
||||
self.active: dict[str, set[WebSocket]] = defaultdict(set)
|
||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
|
||||
def bind_loop(self, loop: asyncio.AbstractEventLoop) -> None:
|
||||
self._loop = loop
|
||||
|
||||
async def connect(self, slug: str, ws: WebSocket) -> None:
|
||||
await ws.accept()
|
||||
self.active[slug].add(ws)
|
||||
|
||||
def disconnect(self, slug: str, ws: WebSocket) -> None:
|
||||
self.active[slug].discard(ws)
|
||||
|
||||
def broadcast_threadsafe(self, slug: str, data: dict) -> None:
|
||||
"""Appelable depuis n'importe quel thread (worker orchestrateur)."""
|
||||
if self._loop is None:
|
||||
return
|
||||
self._loop.call_soon_threadsafe(self._dispatch, slug, data)
|
||||
|
||||
def _dispatch(self, slug: str, data: dict) -> None:
|
||||
for ws in list(self.active.get(slug, ())):
|
||||
asyncio.create_task(self._safe_send(slug, ws, data))
|
||||
|
||||
async def _safe_send(self, slug: str, ws: WebSocket, data: dict) -> None:
|
||||
try:
|
||||
await ws.send_json({"type": "state", "state": data})
|
||||
except Exception: # noqa: BLE001 — connexion fermee
|
||||
self.disconnect(slug, ws)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
Reference in New Issue
Block a user