Working on socket communications

This commit is contained in:
2025-09-21 10:20:18 -04:00
parent daaa0b5fee
commit 3951794ba9
2 changed files with 73 additions and 29 deletions

View File

@@ -1,12 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import annotations from __future__ import annotations
from typing import Optional, Annotated from typing import Optional, Annotated, Dict
from datetime import timedelta, datetime, timezone from datetime import timedelta, datetime, timezone
import re import re
import asyncio import asyncio
import logging import logging
import subprocess import subprocess
import json
from pathlib import Path from pathlib import Path
from fastapi import FastAPI, Depends, HTTPException, status, Request, Form, Query, BackgroundTasks, WebSocket 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.config import Config as AlembicConfig
from alembic import command from alembic import command
from websockets.asyncio.client import connect from websockets.asyncio.client import connect
from pydantic import BaseModel
try: try:
# Try relative imports first (when run as module) # Try relative imports first (when run as module)
@@ -52,6 +54,10 @@ engine = create_engine(
) )
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class GameData(BaseModel):
filename: str
url: str
# Enable WAL mode for better concurrency during startup # Enable WAL mode for better concurrency during startup
try: try:
with engine.connect() as conn: with engine.connect() as conn:
@@ -627,31 +633,20 @@ async def toggle_favorite(
db.commit() db.commit()
return {"message": f"Game {action} from favorites"} 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)): @app.post("/run")
logging.info(f"Run request for game ID {game_id} from user {current_user.username}") 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: if current_user.role == UserRole.DEMO.value:
raise HTTPException(status_code=403, detail="Demo users cannot download games") 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 web_socket_port: int = config.websocket_port
breakpoint()
try: try:
ws = connect(f"ws://{ip_address}:{web_socket_port}") ws = connect(f"ws://{ip_address}:{web_socket_port}")
except Exception as e: 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") raise HTTPException(status_code=400, detail="Could not connect to client WebSocket")
try: try:
async with ws as websocket: async with ws as websocket:
await websocket.send(f"RUN::{game.path}") await websocket.send(f"RUN::{game_json}")
response = await websocket.recv() response = await websocket.recv()
if response != "OK": if response != "OK":
logging.error(f"Client returned error: {response}") logging.error(f"Client returned error: {response}")
raise HTTPException(status_code=400, detail=f"Client error: {response}") raise HTTPException(status_code=400, detail=f"Client error: {response}")
else: 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: except Exception as e:
logging.error(f"WebSocket communication error: {e}") logging.error(f"WebSocket communication error: {e}")
raise HTTPException(status_code=400, detail="Error communicating with client WebSocket") 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}") @app.get("/download/{game_id}")

View File

@@ -663,6 +663,42 @@
console.error('Download error:', error); console.error('Download error:', error);
alert('Download failed. Please try again.'); 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) { async function runGame(gameId) {
const token = localStorage.getItem('authToken'); const token = localStorage.getItem('authToken');
@@ -670,13 +706,26 @@
showLogin(); showLogin();
return; 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 { try {
const response = await fetch(`/run/${gameId}`, { const response = await fetch(`/run`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`,
} 'Content-Type': 'application/json'
},
//body: JSON.stringify(file)
body: _data
}); });
if (response.ok) { if (response.ok) {