import os import sqlite3 from typing import Dict from datetime import datetime, timedelta DB_FILE = "dereth.db" # Maximum allowed database size (in MB). Defaults to 2048 (2GB). Override via env DB_MAX_SIZE_MB. MAX_DB_SIZE_MB = int(os.getenv("DB_MAX_SIZE_MB", "2048")) # Retention window for telemetry history in days. Override via env DB_RETENTION_DAYS. MAX_RETENTION_DAYS = int(os.getenv("DB_RETENTION_DAYS", "7")) # SQLite runtime limits customization DB_MAX_SQL_LENGTH = int(os.getenv("DB_MAX_SQL_LENGTH", "1000000000")) DB_MAX_SQL_VARIABLES = int(os.getenv("DB_MAX_SQL_VARIABLES", "32766")) # Number of WAL frames to write before forcing a checkpoint (override via env DB_WAL_AUTOCHECKPOINT_PAGES) DB_WAL_AUTOCHECKPOINT_PAGES = int(os.getenv("DB_WAL_AUTOCHECKPOINT_PAGES", "1000")) def init_db() -> None: """Create tables if they do not exist (extended with kills_per_hour and onlinetime).""" # Open connection with a longer timeout conn = sqlite3.connect(DB_FILE, timeout=30) # Bump SQLite runtime limits conn.setlimit(sqlite3.SQLITE_LIMIT_LENGTH, DB_MAX_SQL_LENGTH) conn.setlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH, DB_MAX_SQL_LENGTH) conn.setlimit(sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER, DB_MAX_SQL_VARIABLES) c = conn.cursor() # Enable auto_vacuum FULL and rebuild DB so that deletions shrink the file c.execute("PRAGMA auto_vacuum=FULL;") conn.commit() c.execute("VACUUM;") conn.commit() # Switch to WAL mode for concurrency, adjust checkpointing, and enforce max size c.execute("PRAGMA journal_mode=WAL") c.execute("PRAGMA synchronous=NORMAL") c.execute(f"PRAGMA wal_autocheckpoint={DB_WAL_AUTOCHECKPOINT_PAGES}") # History log c.execute( """ CREATE TABLE IF NOT EXISTS telemetry_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, character_name TEXT NOT NULL, char_tag TEXT, session_id TEXT NOT NULL, timestamp TEXT NOT NULL, ew REAL, ns REAL, z REAL, kills INTEGER, kills_per_hour TEXT, onlinetime TEXT, deaths INTEGER, rares_found INTEGER, prismatic_taper_count INTEGER, vt_state TEXT ) """ ) # Live snapshot (upsert) c.execute( """ CREATE TABLE IF NOT EXISTS live_state ( character_name TEXT PRIMARY KEY, char_tag TEXT, session_id TEXT, timestamp TEXT, ew REAL, ns REAL, z REAL, kills INTEGER, kills_per_hour TEXT, onlinetime TEXT, deaths INTEGER, rares_found INTEGER, prismatic_taper_count INTEGER, vt_state TEXT ) """ ) conn.commit() conn.close() def save_snapshot(data: Dict) -> None: """Insert snapshot into history and upsert into live_state (with new fields).""" # Open connection with a longer busy timeout conn = sqlite3.connect(DB_FILE, timeout=30) # Bump SQLite runtime limits on this connection conn.setlimit(sqlite3.SQLITE_LIMIT_LENGTH, DB_MAX_SQL_LENGTH) conn.setlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH, DB_MAX_SQL_LENGTH) conn.setlimit(sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER, DB_MAX_SQL_VARIABLES) c = conn.cursor() # Ensure WAL mode, checkpointing, and size limit on this connection c.execute("PRAGMA journal_mode=WAL") c.execute("PRAGMA synchronous=NORMAL") c.execute(f"PRAGMA wal_autocheckpoint={DB_WAL_AUTOCHECKPOINT_PAGES}") # Insert full history row c.execute( """ INSERT INTO telemetry_log ( character_name, char_tag, session_id, timestamp, ew, ns, z, kills, kills_per_hour, onlinetime, deaths, rares_found, prismatic_taper_count, vt_state ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( data["character_name"], data.get("char_tag", ""), data["session_id"], data["timestamp"], data["ew"], data["ns"], data.get("z", 0.0), data["kills"], data.get("kills_per_hour", ""), data.get("onlinetime", ""), data.get("deaths", 0), data.get("rares_found", 0), data.get("prismatic_taper_count", 0), data.get("vt_state", "Unknown"), ), ) # Upsert into live_state c.execute( """ INSERT INTO live_state ( character_name, char_tag, session_id, timestamp, ew, ns, z, kills, kills_per_hour, onlinetime, deaths, rares_found, prismatic_taper_count, vt_state ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(character_name) DO UPDATE SET char_tag = excluded.char_tag, session_id = excluded.session_id, timestamp = excluded.timestamp, ew = excluded.ew, ns = excluded.ns, z = excluded.z, kills = excluded.kills, kills_per_hour = excluded.kills_per_hour, onlinetime = excluded.onlinetime, deaths = excluded.deaths, rares_found = excluded.rares_found, prismatic_taper_count = excluded.prismatic_taper_count, vt_state = excluded.vt_state """, ( data["character_name"], data.get("char_tag", ""), data["session_id"], data["timestamp"], data["ew"], data["ns"], data.get("z", 0.0), data["kills"], data.get("kills_per_hour", ""), data.get("onlinetime", ""), data.get("deaths", 0), data.get("rares_found", 0), data.get("prismatic_taper_count", 0), data.get("vt_state", "Unknown"), ), ) conn.commit() conn.close()