Add persistent hierarchy cache (plugin DB, TTL 15m); v1.0.5
This commit is contained in:
parent
6cf0255f01
commit
47ed0599fa
4 changed files with 86 additions and 9 deletions
|
|
@ -6,7 +6,15 @@ space_id: "!vervang-mij:example.org"
|
|||
# Hoe diep de bot in geneste subspaces mag kijken.
|
||||
max_depth: 5
|
||||
|
||||
# Hoeveel rooms er per bericht getoond worden (Matrix-events hebben een max grootte,
|
||||
# dus bij ~200 rooms wordt de lijst automatisch over meerdere berichten verdeeld).
|
||||
rooms_per_message: 50
|
||||
|
||||
# Harde bovengrens voor het totale aantal rooms dat wordt opgehaald/verstuurd.
|
||||
# Voorkomt dat een enorme (gefedereerde) space-boom leidt tot duizenden berichten.
|
||||
max_rooms: 300
|
||||
|
||||
# Hoe lang (minuten) de opgehaalde lijst wordt gecached in de plugin-database.
|
||||
# Binnen deze periode gebruikt !rooms de cache i.p.v. de hele boom opnieuw op te
|
||||
# halen (minder traffic naar de homeserver en federatie). 0 = caching uit.
|
||||
cache_ttl_minutes: 15
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
maubot: 0.1.0
|
||||
id: nl.roomsoverview
|
||||
version: 1.0.4
|
||||
version: 1.0.5
|
||||
license: MIT
|
||||
modules:
|
||||
- roomsoverview
|
||||
main_class: RoomsOverviewPlugin
|
||||
database: true
|
||||
database_type: asyncpg
|
||||
config: true
|
||||
extra_files:
|
||||
- base-config.yaml
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,4 +1,6 @@
|
|||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from urllib.parse import quote
|
||||
|
||||
|
|
@ -6,10 +8,35 @@ import aiohttp
|
|||
|
||||
from maubot import Plugin, MessageEvent
|
||||
from maubot.handlers import command
|
||||
from mautrix.util.async_db import Connection, Scheme, UpgradeTable
|
||||
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
|
||||
|
||||
_REQUEST_TIMEOUT = aiohttp.ClientTimeout(total=30)
|
||||
|
||||
upgrade_table = UpgradeTable()
|
||||
|
||||
|
||||
@upgrade_table.register(description="Initial revision")
|
||||
async def upgrade_v1(conn: Connection, scheme: Scheme) -> None:
|
||||
if scheme == Scheme.SQLITE:
|
||||
await conn.execute(
|
||||
"""CREATE TABLE roomsoverview_cache (
|
||||
space_id TEXT PRIMARY KEY,
|
||||
rooms TEXT NOT NULL,
|
||||
complete INTEGER NOT NULL,
|
||||
fetched_at BIGINT NOT NULL
|
||||
)"""
|
||||
)
|
||||
else:
|
||||
await conn.execute(
|
||||
"""CREATE TABLE roomsoverview_cache (
|
||||
space_id TEXT PRIMARY KEY,
|
||||
rooms TEXT NOT NULL,
|
||||
complete BOOLEAN NOT NULL,
|
||||
fetched_at BIGINT NOT NULL
|
||||
)"""
|
||||
)
|
||||
|
||||
|
||||
def _escape_md(text: str) -> str:
|
||||
"""Escape markdown-significant characters when embedding user content."""
|
||||
|
|
@ -31,6 +58,7 @@ class Config(BaseProxyConfig):
|
|||
helper.copy("max_depth")
|
||||
helper.copy("rooms_per_message")
|
||||
helper.copy("max_rooms")
|
||||
helper.copy("cache_ttl_minutes")
|
||||
|
||||
|
||||
class RoomsOverviewPlugin(Plugin):
|
||||
|
|
@ -41,6 +69,10 @@ class RoomsOverviewPlugin(Plugin):
|
|||
def get_config_class(cls):
|
||||
return Config
|
||||
|
||||
@classmethod
|
||||
def get_db_upgrade_table(cls) -> UpgradeTable:
|
||||
return upgrade_table
|
||||
|
||||
async def _fetch_hierarchy(self, space_id: str) -> Tuple[List[Dict[str, Any]], bool]:
|
||||
"""Fetch the paginated space hierarchy for a room.
|
||||
|
||||
|
|
@ -123,6 +155,31 @@ class RoomsOverviewPlugin(Plugin):
|
|||
break
|
||||
return rooms, True
|
||||
|
||||
async def _load_cache(
|
||||
self, space_id: str
|
||||
) -> Optional[Tuple[List[Dict[str, Any]], bool, int]]:
|
||||
row = await self.database.fetchrow(
|
||||
"SELECT rooms, complete, fetched_at FROM roomsoverview_cache "
|
||||
"WHERE space_id = $1",
|
||||
space_id,
|
||||
)
|
||||
if not row:
|
||||
return None
|
||||
return json.loads(row["rooms"]), bool(row["complete"]), int(row["fetched_at"])
|
||||
|
||||
async def _store_cache(
|
||||
self, space_id: str, rooms: List[Dict[str, Any]], complete: bool
|
||||
) -> None:
|
||||
await self.database.execute(
|
||||
"INSERT INTO roomsoverview_cache (space_id, rooms, complete, fetched_at) "
|
||||
"VALUES ($1, $2, $3, $4) "
|
||||
"ON CONFLICT (space_id) DO UPDATE SET rooms = $2, complete = $3, fetched_at = $4",
|
||||
space_id,
|
||||
json.dumps(rooms, ensure_ascii=False),
|
||||
complete,
|
||||
int(time.time()),
|
||||
)
|
||||
|
||||
@command.new(name="rooms", help="Toon alle rooms in de geconfigureerde space")
|
||||
async def handle_rooms(self, evt: MessageEvent) -> None:
|
||||
space_id = str(self.config.get("space_id", "") or "")
|
||||
|
|
@ -133,14 +190,24 @@ class RoomsOverviewPlugin(Plugin):
|
|||
return
|
||||
|
||||
await evt.mark_read()
|
||||
entries, complete = await self._fetch_hierarchy(space_id)
|
||||
|
||||
rooms = [
|
||||
r
|
||||
for r in entries
|
||||
if r.get("room_id") != space_id
|
||||
and (r.get("room_type") or "") != "m.space"
|
||||
]
|
||||
ttl_minutes = max(0, int(self.config.get("cache_ttl_minutes", 15) or 15))
|
||||
cached = await self._load_cache(space_id)
|
||||
now = int(time.time())
|
||||
if cached and ttl_minutes > 0 and now - cached[2] < ttl_minutes * 60:
|
||||
rooms, complete = cached[0], cached[1]
|
||||
self.log.info(
|
||||
f"rooms: cache hit voor {space_id} (age {now - cached[2]}s)"
|
||||
)
|
||||
else:
|
||||
entries, complete = await self._fetch_hierarchy(space_id)
|
||||
rooms = [
|
||||
r
|
||||
for r in entries
|
||||
if r.get("room_id") != space_id
|
||||
and (r.get("room_type") or "") != "m.space"
|
||||
]
|
||||
await self._store_cache(space_id, rooms, complete)
|
||||
|
||||
if not rooms:
|
||||
if not complete:
|
||||
|
|
|
|||
Loading…
Reference in a new issue