|
# 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()
|