"""Session persistence — SQLite + FTS5 for conversation history.""" import sqlite3 import threading import time def log(msg): print(f"[sessions] {msg}", flush=True) def set_logger(fn): global log log = fn _write_lock = threading.Lock() def init_db(db_path): """Create/open the session database. Returns a connection.""" conn = sqlite3.connect(db_path, check_same_thread=False) conn.execute("PRAGMA journal_mode=WAL") conn.execute(""" CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts REAL NOT NULL, nick TEXT NOT NULL, channel TEXT NOT NULL, text TEXT NOT NULL, full_text TEXT ) """) conn.execute(""" CREATE INDEX IF NOT EXISTS idx_messages_channel_ts ON messages(channel, ts) """) # FTS5 virtual table for full-text search conn.execute(""" CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(text, content=messages, content_rowid=id) """) conn.commit() _prune(conn) log(f"Session DB ready: {db_path}") return conn def save_message(conn, nick, channel, text, full_text=None): """Persist a message to the session database.""" with _write_lock: cur = conn.execute( "INSERT INTO messages (ts, nick, channel, text, full_text) VALUES (?, ?, ?, ?, ?)", (time.time(), nick, channel, text, full_text), ) conn.execute( "INSERT INTO messages_fts(rowid, text) VALUES (?, ?)", (cur.lastrowid, text), ) conn.commit() def load_recent(conn, limit=20): """Load the last N messages for boot recovery. Returns list of {"nick", "text", "channel"} dicts in chronological order.""" rows = conn.execute( "SELECT nick, text, channel FROM messages ORDER BY id DESC LIMIT ?", (limit,), ).fetchall() return [{"nick": r[0], "text": r[1], "channel": r[2]} for r in reversed(rows)] def _prune(conn, keep=1000): """Delete old messages beyond the last `keep`. Runs once at init.""" count = conn.execute("SELECT COUNT(*) FROM messages").fetchone()[0] if count <= keep: return deleted = count - keep conn.execute(""" DELETE FROM messages WHERE id NOT IN ( SELECT id FROM messages ORDER BY id DESC LIMIT ? ) """, (keep,)) # Rebuild FTS index after bulk delete conn.execute("INSERT INTO messages_fts(messages_fts) VALUES('rebuild')") conn.commit() log(f"Pruned {deleted} old messages (kept {keep})")