diff --git a/src/webapp.py b/src/webapp.py index c82cee5..08f5086 100755 --- a/src/webapp.py +++ b/src/webapp.py @@ -1,12 +1,13 @@ #!/usr/bin/env python from __future__ import annotations -from typing import Optional, Annotated +from typing import Optional, Annotated, Dict from datetime import timedelta, datetime, timezone import re import asyncio import logging import subprocess +import json from pathlib import Path from fastapi import FastAPI, Depends, HTTPException, status, Request, Form, Query, BackgroundTasks, WebSocket @@ -23,6 +24,7 @@ from sqlalchemy.exc import OperationalError from alembic.config import Config as AlembicConfig from alembic import command from websockets.asyncio.client import connect +from pydantic import BaseModel try: # Try relative imports first (when run as module) @@ -52,6 +54,10 @@ engine = create_engine( ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +class GameData(BaseModel): + filename: str + url: str + # Enable WAL mode for better concurrency during startup try: with engine.connect() as conn: @@ -627,31 +633,20 @@ async def toggle_favorite( db.commit() return {"message": f"Game {action} from favorites"} -@app.post("/run/{game_id}") -async def run_game(request: Request, game_id: int, db: Session = Depends(get_db), current_user: User_table = Depends(require_auth)): - logging.info(f"Run request for game ID {game_id} from user {current_user.username}") + +@app.post("/run") +async def run_game( + request: Request, + game_data: GameData, + current_user: User_table = Depends(require_auth) + ): + breakpoint() + logging.info(f"Run request for game ID {game_data.filename} from user {current_user.username}") + game_json = json.dumps(game_data.dict()) if current_user.role == UserRole.DEMO.value: raise HTTPException(status_code=403, detail="Demo users cannot download games") - game = db.get(Game_table, game_id) - if not game: - raise HTTPException(status_code=404, detail="Game not found") - - if not game.path.exists(): - raise HTTPException(status_code=404, detail="Game file not found") - forwarded_for = request.headers.get("x-forwarded-for") - if forwarded_for: - ip_address = forwarded_for.split(",")[0].strip() - elif real_ip := request.headers.get("x-real-ip"): - ip_address = real_ip.strip() - elif request.client: - ip_address = request.client.host - else: - ip_address = "unknown" - logging.warning("Could not determine client IP address") - return HTTPException(status_code=400, detail="Could not determine client IP address") web_socket_port: int = config.websocket_port - breakpoint() try: ws = connect(f"ws://{ip_address}:{web_socket_port}") except Exception as e: @@ -659,17 +654,17 @@ async def run_game(request: Request, game_id: int, db: Session = Depends(get_db) raise HTTPException(status_code=400, detail="Could not connect to client WebSocket") try: async with ws as websocket: - await websocket.send(f"RUN::{game.path}") + await websocket.send(f"RUN::{game_json}") response = await websocket.recv() if response != "OK": logging.error(f"Client returned error: {response}") raise HTTPException(status_code=400, detail=f"Client error: {response}") else: - logging.info(f"Sent RUN command for game {game.title} to {ip_address}") + logging.info(f"Sent RUN command for game ") except Exception as e: logging.error(f"WebSocket communication error: {e}") raise HTTPException(status_code=400, detail="Error communicating with client WebSocket") - return {"web_socket_port": web_socket_port, "game_path": str(game.path)} + return {"web_socket_port": web_socket_port, "game_path": str(game_data.filename)} @app.get("/download/{game_id}") diff --git a/templates/index.html b/templates/index.html index 5c871d2..cd14ba9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -663,6 +663,42 @@ console.error('Download error:', error); alert('Download failed. Please try again.'); } + } + async function downloadGameStorage(gameId) { + const token = localStorage.getItem('authToken'); + if (!token) { + showLogin(); + return; + } + + try { + const response = await fetch(`/download/${gameId}`, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (response.ok) { + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + // Get filename from Content-Disposition header, removing quotes and underscores + let filename = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip'; + filename = filename.replace(/^["\_]+|["\_]+$/g, ''); // Remove quotes and underscores from start and end + a.download = filename; + return { url: url, filename: filename }; + } else if (response.status === 401) { + localStorage.removeItem('authToken'); + showLogin(); + } else { + alert('Download failed. Please try again.'); + } + } catch (error) { + console.error('Download error:', error); + alert('Download failed. Please try again.'); + } } async function runGame(gameId) { const token = localStorage.getItem('authToken'); @@ -670,13 +706,26 @@ showLogin(); return; } - + const file = await downloadGameStorage(gameId); + const response = await fetch(file.url) + const arrayBuffer = await response.arrayBuffer(); + const blob = new Blob([arrayBuffer], + { type: response.headers.get( + 'content-type') || 'application/octet-stream' + } + ); + const _data = new FormData(); + debugger; + _data.append('file', blob, ".zip"); try { - const response = await fetch(`/run/${gameId}`, { + const response = await fetch(`/run`, { method: 'POST', headers: { - 'Authorization': `Bearer ${token}` - } + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + //body: JSON.stringify(file) + body: _data }); if (response.ok) {