rollback checkpoint: hardened gacha, disabled ffmpeg, deleted rio, async db
This commit is contained in:
1 parent
05c0599d1c
commit
30efb9105f
9 files changed
+367
-260
No files matched your search
+92
-21
@@ -1,9 +1,11 @@
|
||||
#this is the sqlite database layer for gacha inventory, spark, and eligma##
|
||||
"""SQLite database for Blue Archive gacha persistence."""
|
||||
"""SQLite database for Blue Archive gacha persistence — async wrapper layer."""
|
||||
######################################################################
|
||||
import asyncio
|
||||
import sqlite3
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .constants import ELIGMA_YIELD
|
||||
|
||||
@@ -14,16 +16,16 @@ DB_PATH = Path("data/ba_gacha.db")
|
||||
|
||||
def _connect() -> sqlite3.Connection:
|
||||
"""Open a connection with WAL mode for concurrent reads."""
|
||||
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(DB_PATH))
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA foreign_keys=ON")
|
||||
return conn
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
"""Create tables if they don't exist."""
|
||||
def _init_db_sync() -> None:
|
||||
"""Create tables if they don't exist (runs in thread)."""
|
||||
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with _connect() as conn:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA foreign_keys=ON")
|
||||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS inventory (
|
||||
user_id INTEGER NOT NULL,
|
||||
@@ -47,10 +49,15 @@ def init_db() -> None:
|
||||
log.info("Database initialised at %s", DB_PATH)
|
||||
|
||||
|
||||
async def init_db() -> None:
|
||||
"""Async wrapper — initialises the database off the event loop."""
|
||||
await asyncio.to_thread(_init_db_sync)
|
||||
|
||||
|
||||
# ── Inventory ──────────────────────────────────────────────────────────────
|
||||
|
||||
def add_pull(user_id: int, student_id: int, rarity: int) -> int:
|
||||
"""Record a pull. If student already owned, increment count and return eligma earned."""
|
||||
def _add_pull_sync(user_id: int, student_id: int, rarity: int) -> int:
|
||||
"""Record a pull. Returns eligma earned from duplicates."""
|
||||
with _connect() as conn:
|
||||
cur = conn.execute(
|
||||
"SELECT count FROM inventory WHERE user_id = ? AND student_id = ?",
|
||||
@@ -58,12 +65,12 @@ def add_pull(user_id: int, student_id: int, rarity: int) -> int:
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
# Duplicate — convert to eligma only, keep count at 1
|
||||
conn.execute("UPDATE inventory SET count = count + 1 WHERE user_id = ? AND student_id = ?",
|
||||
(user_id, student_id))
|
||||
eligma = ELIGMA_YIELD.get(rarity, 0)
|
||||
_add_eligma(conn, user_id, eligma)
|
||||
_add_eligma_sync(conn, user_id, eligma)
|
||||
return eligma
|
||||
else:
|
||||
# New student
|
||||
conn.execute(
|
||||
"INSERT INTO inventory (user_id, student_id, count) VALUES (?, ?, 1)",
|
||||
(user_id, student_id),
|
||||
@@ -71,8 +78,11 @@ def add_pull(user_id: int, student_id: int, rarity: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def get_inventory(user_id: int) -> list[tuple[int, int]]:
|
||||
"""Return [(student_id, count), ...] for a user."""
|
||||
async def add_pull(user_id: int, student_id: int, rarity: int) -> int:
|
||||
return await asyncio.to_thread(_add_pull_sync, user_id, student_id, rarity)
|
||||
|
||||
|
||||
def _get_inventory_sync(user_id: int) -> list[tuple[int, int]]:
|
||||
with _connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT student_id, count FROM inventory WHERE user_id = ? ORDER BY student_id",
|
||||
@@ -81,8 +91,24 @@ def get_inventory(user_id: int) -> list[tuple[int, int]]:
|
||||
return rows
|
||||
|
||||
|
||||
def get_inventory_stats(user_id: int) -> dict:
|
||||
"""Return {unique_total, total_pulls, by_rarity: {1: n, 2: n, 3: n}}."""
|
||||
async def get_inventory(user_id: int) -> list[tuple[int, int]]:
|
||||
return await asyncio.to_thread(_get_inventory_sync, user_id)
|
||||
|
||||
|
||||
def _has_student_sync(user_id: int, student_id: int) -> bool:
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT 1 FROM inventory WHERE user_id = ? AND student_id = ?",
|
||||
(user_id, student_id),
|
||||
).fetchone()
|
||||
return row is not None
|
||||
|
||||
|
||||
async def has_student(user_id: int, student_id: int) -> bool:
|
||||
return await asyncio.to_thread(_has_student_sync, user_id, student_id)
|
||||
|
||||
|
||||
def _get_inventory_stats_sync(user_id: int) -> dict:
|
||||
with _connect() as conn:
|
||||
total_pulls = conn.execute(
|
||||
"SELECT COALESCE(SUM(count), 0) FROM inventory WHERE user_id = ?",
|
||||
@@ -95,9 +121,13 @@ def get_inventory_stats(user_id: int) -> dict:
|
||||
return {"total_pulls": total_pulls, "unique": unique}
|
||||
|
||||
|
||||
async def get_inventory_stats(user_id: int) -> dict:
|
||||
return await asyncio.to_thread(_get_inventory_stats_sync, user_id)
|
||||
|
||||
|
||||
# ── Spark ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_spark(user_id: int, banner_id: str) -> int:
|
||||
def _get_spark_sync(user_id: int, banner_id: str) -> int:
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT points FROM spark WHERE user_id = ? AND banner_id = ?",
|
||||
@@ -106,7 +136,11 @@ def get_spark(user_id: int, banner_id: str) -> int:
|
||||
return row[0] if row else 0
|
||||
|
||||
|
||||
def add_spark(user_id: int, banner_id: str, points: int) -> int:
|
||||
async def get_spark(user_id: int, banner_id: str) -> int:
|
||||
return await asyncio.to_thread(_get_spark_sync, user_id, banner_id)
|
||||
|
||||
|
||||
def _add_spark_sync(user_id: int, banner_id: str, points: int) -> int:
|
||||
"""Add points to a banner's spark counter. Returns new total."""
|
||||
with _connect() as conn:
|
||||
conn.execute(
|
||||
@@ -114,7 +148,6 @@ def add_spark(user_id: int, banner_id: str, points: int) -> int:
|
||||
"ON CONFLICT(user_id, banner_id) DO UPDATE SET points = points + ?",
|
||||
(user_id, banner_id, points, points),
|
||||
)
|
||||
conn.commit()
|
||||
row = conn.execute(
|
||||
"SELECT points FROM spark WHERE user_id = ? AND banner_id = ?",
|
||||
(user_id, banner_id),
|
||||
@@ -122,7 +155,11 @@ def add_spark(user_id: int, banner_id: str, points: int) -> int:
|
||||
return row[0] if row else 0
|
||||
|
||||
|
||||
def spend_spark(user_id: int, banner_id: str, cost: int = 200) -> bool:
|
||||
async def add_spark(user_id: int, banner_id: str, points: int) -> int:
|
||||
return await asyncio.to_thread(_add_spark_sync, user_id, banner_id, points)
|
||||
|
||||
|
||||
def _spend_spark_sync(user_id: int, banner_id: str, cost: int = 200) -> bool:
|
||||
"""Deduct spark points. Returns True if successful (had enough points)."""
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
@@ -138,9 +175,39 @@ def spend_spark(user_id: int, banner_id: str, cost: int = 200) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def spend_spark(user_id: int, banner_id: str, cost: int = 200) -> bool:
|
||||
return await asyncio.to_thread(_spend_spark_sync, user_id, banner_id, cost)
|
||||
|
||||
|
||||
def _spark_claim_sync(user_id: int, banner_id: str, student_id: int, rarity: int, cost: int = 200) -> tuple[bool, str]:
|
||||
"""Atomically claim a spark student. Returns (success, message)."""
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT points FROM spark WHERE user_id = ? AND banner_id = ?",
|
||||
(user_id, banner_id),
|
||||
).fetchone()
|
||||
if not row or row[0] < cost:
|
||||
return False, "Not enough Recruitment Points."
|
||||
owned = conn.execute(
|
||||
"SELECT 1 FROM inventory WHERE user_id = ? AND student_id = ?",
|
||||
(user_id, student_id),
|
||||
).fetchone()
|
||||
if owned:
|
||||
return False, "already_owned"
|
||||
conn.execute("INSERT INTO inventory (user_id, student_id, count) VALUES (?, ?, 1)",
|
||||
(user_id, student_id))
|
||||
conn.execute("UPDATE spark SET points = points - ? WHERE user_id = ? AND banner_id = ?",
|
||||
(cost, user_id, banner_id))
|
||||
return True, ""
|
||||
|
||||
|
||||
async def spark_claim(user_id: int, banner_id: str, student_id: int, rarity: int, cost: int = 200) -> tuple[bool, str]:
|
||||
return await asyncio.to_thread(_spark_claim_sync, user_id, banner_id, student_id, rarity, cost)
|
||||
|
||||
|
||||
# ── Eligma ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def _add_eligma(conn: sqlite3.Connection, user_id: int, amount: int) -> None:
|
||||
def _add_eligma_sync(conn: sqlite3.Connection, user_id: int, amount: int) -> None:
|
||||
conn.execute(
|
||||
"INSERT INTO eligma (user_id, amount) VALUES (?, ?) "
|
||||
"ON CONFLICT(user_id) DO UPDATE SET amount = amount + ?",
|
||||
@@ -148,9 +215,13 @@ def _add_eligma(conn: sqlite3.Connection, user_id: int, amount: int) -> None:
|
||||
)
|
||||
|
||||
|
||||
def get_eligma(user_id: int) -> int:
|
||||
def _get_eligma_sync(user_id: int) -> int:
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT amount FROM eligma WHERE user_id = ?", (user_id,)
|
||||
).fetchone()
|
||||
return row[0] if row else 0
|
||||
|
||||
|
||||
async def get_eligma(user_id: int) -> int:
|
||||
return await asyncio.to_thread(_get_eligma_sync, user_id)
|
||||
Reference in new issue
Block a user