Phase 1 (Foundation): project skeleton, TOML config + Pydantic validation, MQTT bus wrapper, SQLite schema (9 tables), Click CLI, process supervisor. Phase 2 (Camera): RTSP capture via OpenCV, MOG2 motion detection with configurable sensitivity/zones, adaptive FPS recording (2fps idle/30fps motion) via FFmpeg subprocess, HLS live streaming, pre-motion ring buffer. Phase 3 (Web UI): Flask + Bootstrap 5 dark theme, 6 blueprints, Jinja2 templates (dashboard, kiosk 2x2 grid, events, sensors, recordings, settings), PWA with service worker + Web Push, full admin settings UI with config persistence. Remote Access: WireGuard tunnel configs, nginx reverse proxy with HLS caching + rate limiting, bandwidth-optimized remote HLS stream (426x240 @ 500kbps), DO droplet setup script, certbot TLS. 29 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Database engine setup and initialization."""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy import create_engine, event, text
|
|
from sqlalchemy.engine import Engine
|
|
|
|
from vigilar.storage.schema import metadata
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_engine: Engine | None = None
|
|
|
|
|
|
def _set_wal_mode(dbapi_conn: object, connection_record: object) -> None:
|
|
"""Enable WAL mode for concurrent read/write support."""
|
|
cursor = dbapi_conn.cursor() # type: ignore[union-attr]
|
|
cursor.execute("PRAGMA journal_mode=WAL")
|
|
cursor.execute("PRAGMA synchronous=NORMAL")
|
|
cursor.execute("PRAGMA busy_timeout=5000")
|
|
cursor.close()
|
|
|
|
|
|
def get_engine(db_path: str | Path) -> Engine:
|
|
"""Get or create the SQLAlchemy engine."""
|
|
global _engine
|
|
if _engine is not None:
|
|
return _engine
|
|
|
|
db_path = Path(db_path)
|
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
url = f"sqlite:///{db_path}"
|
|
_engine = create_engine(url, echo=False, pool_pre_ping=True)
|
|
|
|
event.listen(_engine, "connect", _set_wal_mode)
|
|
|
|
log.info("Database engine created: %s", db_path)
|
|
return _engine
|
|
|
|
|
|
def init_db(db_path: str | Path) -> Engine:
|
|
"""Initialize the database: create engine, create all tables."""
|
|
engine = get_engine(db_path)
|
|
metadata.create_all(engine)
|
|
log.info("Database tables initialized")
|
|
return engine
|
|
|
|
|
|
def get_db_path(data_dir: str) -> Path:
|
|
"""Return the standard database file path."""
|
|
return Path(data_dir) / "vigilar.db"
|