feat: add mod editing, revision merges and secure game storage

Add immutable revisions, PGVN editor, asset replacement, merge and rebase workflows, authenticated media delivery and reachability GC. Harden the script interpreter, pin unrpyc and remove tracked build metadata. Include current compatibility checks, Naninovel import and regression tests.
This commit is contained in:
mrleo1nid committed 2026-10-25 04:42:48 +03:00
1 parent 06ff19ac4a
commit c8d83b3fe7
65 files changed
+3825 -68

No files matched your search

+17 -2
View File
@@ -10,6 +10,7 @@ from sqlalchemy import select, update
from app.blobs import read_bytes
from app.config import get_settings
from app.converter.pipeline import convert_archive
from app.converter.compatibility import CompatibilityError
from app.db import SessionLocal
from app.models import Game, GameVersion
from app.routers.games import make_cover
@@ -36,7 +37,7 @@ async def claim() -> GameVersion | None:
return None
v = await db.get(GameVersion, version_id)
v.status = "converting"
v.progress = "Начало конвертации"
v.progress = "Проверка совместимости игры"
await db.commit()
return v
@@ -48,6 +49,18 @@ async def process(v: GameVersion) -> None:
async def convert(version_id: int, source: Path, filename: str) -> bool:
from app.maintenance import storage_access
async with storage_access():
task = asyncio.create_task(_convert(version_id, source, filename))
try:
return await asyncio.shield(task)
except asyncio.CancelledError:
# Conversion threads cannot be cancelled. Keep the lease until commit.
await task
raise
async def _convert(version_id: int, source: Path, filename: str) -> bool:
"""Run the converter for a version and store the outcome. Returns success."""
loop = asyncio.get_running_loop()
work = get_settings().work_dir / str(version_id)
@@ -65,7 +78,7 @@ async def convert(version_id: int, source: Path, filename: str) -> bool:
if version:
version.status = "failed"
version.progress = f"Ошибка: {e}"[:255]
version.report = {"error": str(e)}
version.report = e.report() if isinstance(e, CompatibilityError) else {"error": str(e)}
await db.commit()
return False
finally:
@@ -81,6 +94,8 @@ async def convert(version_id: int, source: Path, filename: str) -> bool:
version.report = result.report
version.size_bytes = result.size
game = await db.get(Game, version.game_id)
if game:
game.engine = result.report.get("engine", "renpy")
if game and not game.cover_hash and result.cover_source:
try:
game.cover_hash = await asyncio.to_thread(make_cover,