# retoor <retoor@molodetz.nl>
import asyncio
import logging
from typing import Optional
from devplacepy.database import db, get_users_by_uids
from devplacepy.services.messaging.hub import message_hub
from devplacepy.services.messaging.persist import message_frame
logger = logging.getLogger("messaging.relay")
POLL_INTERVAL_SECONDS = 1.0
BATCH_LIMIT = 500
class MessageRelay:
def __init__(self) -> None:
self._task: Optional[asyncio.Task] = None
self._watermark: int = 0
self._primed: bool = False
def start(self) -> None:
if self._task is not None and not self._task.done():
return
self._task = asyncio.create_task(self._run())
logger.info("message relay started")
def _max_id(self) -> int:
if "messages" not in db.tables:
return 0
rows = list(db.query("SELECT MAX(id) AS max_id FROM messages"))
value = rows[0]["max_id"] if rows else None
return int(value or 0)
async def _run(self) -> None:
try:
if not self._primed:
self._watermark = self._max_id()
self._primed = True
logger.debug("message relay primed at watermark %d", self._watermark)
while message_hub.has_connections():
try:
await self._tick()
except Exception: # noqa: BLE001
logger.exception("message relay tick failed")
await asyncio.sleep(POLL_INTERVAL_SECONDS)
finally:
self._task = None
logger.info("message relay stopped (no active connections)")
async def _tick(self) -> None:
if "messages" not in db.tables:
return
rows = list(
db.query(
"SELECT * FROM messages WHERE id > :wm ORDER BY id ASC LIMIT :lim",
wm=self._watermark,
lim=BATCH_LIMIT,
)
)
if not rows:
return
connected = message_hub.connected_user_uids()
pending = [
row
for row in rows
if (row["sender_uid"] in connected or row["receiver_uid"] in connected)
and not message_hub.was_delivered(row["uid"])
]
sender_uids = {row["sender_uid"] for row in pending}
senders = get_users_by_uids(list(sender_uids)) if sender_uids else {}
for row in pending:
sender = senders.get(row["sender_uid"]) or {}
frame = message_frame(dict(row), sender.get("username", ""), sender_role=sender.get("role"))
message_hub.mark_delivered(row["uid"])
await message_hub.send_to_users(
[row["sender_uid"], row["receiver_uid"]], frame
)
self._watermark = max(self._watermark, max(row["id"] for row in rows))
logger.debug(
"message relay delivered %d of %d new rows, watermark %d",
len(pending),
len(rows),
self._watermark,
)
message_relay = MessageRelay()