Working on socket communications
This commit is contained in:
@@ -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}")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user