Remove proxy - load data directly from DSJ
This commit is contained in:
141
app.js
141
app.js
@@ -19,7 +19,7 @@ let roundPgns = {};
|
||||
let pollId = 0;
|
||||
|
||||
async function fetchRoundPGN(round) {
|
||||
const res = await fetch(`http://localhost:8111/pgn/${round}`);
|
||||
const res = await fetch(`https://www.deutsche-schachjugend.de/2026/odjm-d/partien/${round}.pgn`);
|
||||
if (!res.ok) return null;
|
||||
return await res.text();
|
||||
}
|
||||
@@ -28,7 +28,7 @@ async function loadPGN(showOverlay = true) {
|
||||
const currentPollId = pollId;
|
||||
if (showOverlay) showLoading(true);
|
||||
hideError();
|
||||
|
||||
|
||||
try {
|
||||
await updateStandings();
|
||||
|
||||
@@ -45,8 +45,6 @@ async function loadPGN(showOverlay = true) {
|
||||
}
|
||||
}
|
||||
|
||||
const statusResponse = await fetch('http://localhost:8111/status').catch(() => null);
|
||||
|
||||
// Aktuelle Runde immer frisch holen
|
||||
const pgnText = await fetchRoundPGN(currentRound);
|
||||
if (currentPollId !== pollId) return;
|
||||
@@ -59,21 +57,16 @@ async function loadPGN(showOverlay = true) {
|
||||
if (text) roundPgns[nextRound] = text;
|
||||
}
|
||||
|
||||
if (statusResponse && statusResponse.ok) {
|
||||
const status = await statusResponse.json();
|
||||
serverLastFetch = status.last_fetch ? status.last_fetch * 1000 : null;
|
||||
}
|
||||
|
||||
// Alle PGNs kombinieren
|
||||
const combinedPgn = Object.values(roundPgns).join('\n\n');
|
||||
const allGames = parsePGN(combinedPgn);
|
||||
allLaraGames = filterLaraGames(allGames);
|
||||
|
||||
|
||||
if (allLaraGames.length === 0) {
|
||||
showError('Keine Partien von Lara gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!userSelectedGame) {
|
||||
const liveGame = getLiveGame(allLaraGames);
|
||||
const newGame = liveGame || getLatestGame(allLaraGames);
|
||||
@@ -89,7 +82,7 @@ async function loadPGN(showOverlay = true) {
|
||||
updateTimestamp();
|
||||
|
||||
showLoading(false);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
if (currentPollId !== pollId) return;
|
||||
console.error('Fehler beim Laden:', error);
|
||||
@@ -137,6 +130,7 @@ function updateBoard() {
|
||||
if (board) {
|
||||
board.position(chess.fen(), true);
|
||||
board.orientation(orientation);
|
||||
highlightLastMove();
|
||||
} else {
|
||||
board = Chessboard('board', {
|
||||
position: chess.fen(),
|
||||
@@ -179,6 +173,7 @@ function goToMove(index) {
|
||||
|
||||
board.position(chess.fen(), true);
|
||||
highlightActivePlayer();
|
||||
highlightLastMove();
|
||||
updateClocks(index);
|
||||
|
||||
document.querySelectorAll('#moves-list .move').forEach(el => el.classList.remove('current'));
|
||||
@@ -279,6 +274,28 @@ function highlightActivePlayer() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight den letzten Zug auf dem Brett
|
||||
*/
|
||||
function highlightLastMove() {
|
||||
if (!board || !chess) return;
|
||||
|
||||
const nonResultMoves = currentGame.moves.filter(m => !m.isResult);
|
||||
|
||||
if (currentMoveIndex >= 0 && currentMoveIndex < nonResultMoves.length) {
|
||||
const lastMove = nonResultMoves[currentMoveIndex];
|
||||
|
||||
// Parse die SAN-Züge, um Start- und Zielfelder zu finden
|
||||
const moves = chess.history({ verbose: true });
|
||||
if (moves.length > 0) {
|
||||
const lastMoveData = moves[moves.length - 1];
|
||||
board.highlightSquare(lastMoveData.from, lastMoveData.to);
|
||||
}
|
||||
} else {
|
||||
board.clearHighlights();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Zugliste
|
||||
*/
|
||||
@@ -394,43 +411,73 @@ function updateAllGamesList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Turniertabelle vom Proxy und zeigt Laras Platzierung an
|
||||
* Lädt die Turniertabelle vom DSJ und zeigt Laras Platzierung an
|
||||
*/
|
||||
async function updateStandings() {
|
||||
try {
|
||||
const res = await fetch('http://localhost:8111/standings');
|
||||
const res = await fetch('https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/');
|
||||
if (!res.ok) throw new Error('Fehler beim Laden');
|
||||
const data = await res.json();
|
||||
if (data && data.round) currentRound = data.round;
|
||||
const container = document.getElementById('standings-content');
|
||||
if (!data || data.error) {
|
||||
container.innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||
return;
|
||||
const html = await res.text();
|
||||
|
||||
const roundMatch = html.match(/Tabellenstand\s+nach\s+der\s+(\d+)\.\s*Runde/);
|
||||
if (roundMatch) {
|
||||
currentRound = parseInt(roundMatch[1]);
|
||||
}
|
||||
container.innerHTML = `
|
||||
<div class="standings-rank">${data.rank}.</div>
|
||||
<div class="standings-rank-label">Tabellenplatz</div>
|
||||
<div class="standings-header">${data.round_info || 'nach Runde 1'}</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Punkte</span>
|
||||
<span class="standings-value">${data.points}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Siege</span>
|
||||
<span class="standings-value">${data.wins}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Unentschieden</span>
|
||||
<span class="standings-value">${data.draws}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Niederlagen</span>
|
||||
<span class="standings-value">${data.losses}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const rows = html.matchAll(/<tr[^>]*>(.*?)<\/tr>/gs);
|
||||
for (const row of rows) {
|
||||
if (!row[1].includes('Lara Kiesewetter')) continue;
|
||||
|
||||
const cells = row[1].matchAll(/<td[^>]*>(.*?)<\/td>/gs);
|
||||
const clean = [];
|
||||
for (const cell of cells) {
|
||||
clean.push(cell[1].replace(/<[^>]+>/g, '').trim());
|
||||
}
|
||||
|
||||
if (clean.length >= 9) {
|
||||
const data = {
|
||||
rank: clean[0],
|
||||
player: 'Lara Kiesewetter',
|
||||
wins: clean[5],
|
||||
draws: clean[6],
|
||||
losses: clean[7],
|
||||
points: clean[8],
|
||||
round_info: roundMatch ? `nach der ${roundMatch[1]}. Runde` : '',
|
||||
round: currentRound,
|
||||
};
|
||||
const container = document.getElementById('standings-content');
|
||||
if (!data || data.error) {
|
||||
container.innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<div class="standings-rank">${data.rank}.</div>
|
||||
<div class="standings-rank-label">Tabellenplatz</div>
|
||||
<div class="standings-header">${data.round_info || 'nach Runde 1'}</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Punkte</span>
|
||||
<span class="standings-value">${data.points}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Siege</span>
|
||||
<span class="standings-value">${data.wins}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Unentschieden</span>
|
||||
<span class="standings-value">${data.draws}</span>
|
||||
</div>
|
||||
<div class="standings-row">
|
||||
<span class="standings-label">Niederlagen</span>
|
||||
<span class="standings-value">${data.losses}</span>
|
||||
</div>
|
||||
`;
|
||||
serverLastFetch = Date.now();
|
||||
return;
|
||||
}
|
||||
}
|
||||
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||
} catch (err) {
|
||||
document.getElementById('standings-content').innerHTML =
|
||||
'<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||
document.getElementById('standings-content').innerHTML = '<div class="standings-loading">Daten nicht verfügbar</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,8 +494,8 @@ function formatClock(clockStr) {
|
||||
* Update timestamp
|
||||
*/
|
||||
function updateTimestamp() {
|
||||
const time = serverLastFetch ? new Date(serverLastFetch) : new Date();
|
||||
document.getElementById('last-update').textContent =
|
||||
const time = new Date();
|
||||
document.getElementById('last-update').textContent =
|
||||
`Letztes Update: ${time.toLocaleTimeString('de-DE')}`;
|
||||
}
|
||||
|
||||
@@ -473,7 +520,9 @@ function startAutoRefresh() {
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (pollId !== myId) { clearInterval(pollInterval); clearInterval(timer); return; }
|
||||
const s = Math.floor((Date.now() - lastUpdate) / 1000);
|
||||
const elapsed = Date.now() - lastUpdate;
|
||||
const remaining = Math.max(0, 30000 - elapsed);
|
||||
const s = Math.floor(remaining / 1000);
|
||||
document.getElementById('refresh-timer').textContent = `${s}s`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
310
server.py
310
server.py
@@ -1,310 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Lokal Proxy-Server für Lara's Schachturnier
|
||||
Lädt die PGN-Datei und stellt sie mit CORS-Headern bereit.
|
||||
Lokaler Server für Lara Schachturnier
|
||||
Serviert statische Dateien direkt.
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
STANDINGS_URL = "https://www.deutsche-schachjugend.de/2026/odjm-d/tabelle/"
|
||||
PORT = int(os.environ.get("PORT", 8111))
|
||||
CACHE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cache")
|
||||
STANDINGS_CACHE_FILE = os.path.join(CACHE_DIR, "standings.json")
|
||||
CACHE_TTL = int(os.environ.get("CACHE_TTL", 31))
|
||||
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
|
||||
last_standings_fetch_time = None
|
||||
current_round = 0 # wird aus der Tabelle ermittelt
|
||||
|
||||
|
||||
def get_pgn_url(round_num=None):
|
||||
if round_num is None:
|
||||
round_num = current_round
|
||||
return f"https://www.deutsche-schachjugend.de/2026/odjm-d/partien/{round_num}.pgn"
|
||||
|
||||
|
||||
def get_cache_file(round_num=None):
|
||||
if round_num is None:
|
||||
round_num = current_round
|
||||
return os.path.join(CACHE_DIR, f"runde-{round_num}.pgn")
|
||||
|
||||
|
||||
def fetch_pgn(round_num=None):
|
||||
"""Lädt die PGN-Datei von der URL als Bytes.
|
||||
Gibt (data, True) bei Erfolg oder (None, False) bei Fehler zurück."""
|
||||
if round_num is None:
|
||||
round_num = current_round
|
||||
if round_num == 0:
|
||||
return None, False
|
||||
url = get_pgn_url(round_num)
|
||||
try:
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=31) as response:
|
||||
data = response.read()
|
||||
return data, True
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] HTTP-Fehler (Runde {round_num}): {e}")
|
||||
return None, False
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Fehler beim Laden (Runde {round_num}): {e}")
|
||||
return None, False
|
||||
|
||||
|
||||
def get_round_pgn(round_num):
|
||||
"""Gibt PGN-Daten für eine Runde zurück.
|
||||
Bei der aktuellen Runde wird immer live von der DSJ geholt."""
|
||||
cache_file = get_cache_file(round_num)
|
||||
|
||||
if round_num == current_round:
|
||||
content, changed = fetch_pgn(round_num)
|
||||
if changed and content is not None:
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(content)
|
||||
return content, os.path.getmtime(cache_file)
|
||||
if os.path.exists(cache_file):
|
||||
with open(cache_file, "rb") as f:
|
||||
return f.read(), os.path.getmtime(cache_file)
|
||||
return None, 0
|
||||
|
||||
if os.path.exists(cache_file):
|
||||
with open(cache_file, "rb") as f:
|
||||
return f.read(), os.path.getmtime(cache_file)
|
||||
|
||||
content, changed = fetch_pgn(round_num)
|
||||
if changed and content is not None:
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(content)
|
||||
return content, os.path.getmtime(cache_file)
|
||||
return None, 0
|
||||
|
||||
|
||||
def fetch_standings():
|
||||
"""Lädt die Tabellenseite und parst Laras Platzierung.
|
||||
Ermittelt dabei auch die aktuelle Runde."""
|
||||
global last_standings_fetch_time, current_round
|
||||
try:
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
if last_standings_fetch_time:
|
||||
headers["modified_since"] = str(last_standings_fetch_time)
|
||||
req = urllib.request.Request(STANDINGS_URL, headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=31) as response:
|
||||
html = response.read().decode("utf-8", errors="replace")
|
||||
last_standings_fetch_time = time.time()
|
||||
|
||||
round_info = ""
|
||||
m = re.search(r"Tabellenstand\s+nach\s+der\s+(\d+)\.\s*Runde", html)
|
||||
if m:
|
||||
detected = int(m.group(1))
|
||||
if detected != current_round:
|
||||
old = current_round
|
||||
current_round = detected
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Runde erkannt: {current_round} (war {old})")
|
||||
round_info = f"nach der {m.group(1)}. Runde"
|
||||
|
||||
rows = re.findall(r"<tr[^>]*>(.*?)</tr>", html, re.DOTALL | re.IGNORECASE)
|
||||
for row in rows:
|
||||
if "Lara Kiesewetter" not in row:
|
||||
continue
|
||||
cells = re.findall(r"<td[^>]*>(.*?)</td>", row, re.DOTALL)
|
||||
clean = [re.sub(r"<[^>]+>", "", c).strip() for c in cells]
|
||||
if len(clean) >= 9:
|
||||
return {
|
||||
"rank": clean[0],
|
||||
"player": "Lara Kiesewetter",
|
||||
"wins": clean[5],
|
||||
"draws": clean[6],
|
||||
"losses": clean[7],
|
||||
"points": clean[8],
|
||||
"round_info": round_info,
|
||||
"round": current_round,
|
||||
}
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden der Tabelle: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_standings_content():
|
||||
"""Gibt Tabellenstand als Dict zurück, nutzt Cache."""
|
||||
now = time.time()
|
||||
|
||||
if os.path.exists(STANDINGS_CACHE_FILE):
|
||||
age = now - os.path.getmtime(STANDINGS_CACHE_FILE)
|
||||
if age < 300: # 5 Minuten Cache
|
||||
with open(STANDINGS_CACHE_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
data = fetch_standings()
|
||||
if data:
|
||||
with open(STANDINGS_CACHE_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Tabellenstand aktualisiert")
|
||||
return data
|
||||
return None
|
||||
|
||||
|
||||
class PGNHandler(http.server.BaseHTTPRequestHandler):
|
||||
class StaticHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
parsed_path = urllib.parse.urlparse(self.path)
|
||||
if self.path == "/":
|
||||
self.path = "/index.html"
|
||||
|
||||
parts = parsed_path.path.strip("/").split("/")
|
||||
if len(parts) == 2 and parts[0] == "pgn" and parts[1].isdigit():
|
||||
round_num = int(parts[1])
|
||||
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.path.lstrip("/"))
|
||||
|
||||
if round_num <= 0 or round_num > current_round + 1:
|
||||
self.send_response(404)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"error": "Runde nicht verf\u00fcgbar"}).encode())
|
||||
return
|
||||
|
||||
content, mtime = get_round_pgn(round_num)
|
||||
|
||||
if content:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"error": "Konnte PGN nicht laden"}).encode())
|
||||
|
||||
elif parsed_path.path == "/status":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
self.end_headers()
|
||||
status = {
|
||||
"status": "ok",
|
||||
"server_time": time.time()
|
||||
if os.path.isfile(filepath):
|
||||
content_types = {
|
||||
".html": "text/html",
|
||||
".css": "text/css",
|
||||
".js": "application/javascript",
|
||||
".json": "application/json",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".svg": "image/svg+xml",
|
||||
}
|
||||
self.wfile.write(json.dumps(status).encode())
|
||||
|
||||
elif parsed_path.path == "/standings":
|
||||
data = get_standings_content()
|
||||
if data:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Cache-Control", "max-age=300")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data).encode())
|
||||
else:
|
||||
self.send_response(502)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
err = {"error": "Tabellendaten nicht verf\u00fcgbar"}
|
||||
self.wfile.write(json.dumps(err).encode())
|
||||
|
||||
ext = os.path.splitext(filepath)[1]
|
||||
content_type = content_types.get(ext, "application/octet-stream")
|
||||
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
else:
|
||||
# Statische Dateien aus dem Verzeichnis
|
||||
if self.path == "/":
|
||||
self.path = "/index.html"
|
||||
|
||||
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.path.lstrip("/"))
|
||||
if os.path.isfile(filepath):
|
||||
content_types = {
|
||||
".html": "text/html",
|
||||
".css": "text/css",
|
||||
".js": "application/javascript",
|
||||
".json": "application/json",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".svg": "image/svg+xml",
|
||||
}
|
||||
ext = os.path.splitext(filepath)[1]
|
||||
content_type = content_types.get(ext, "application/octet-stream")
|
||||
|
||||
with open(filepath, "rb") as f:
|
||||
content = f.read()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] {args[0]}")
|
||||
|
||||
|
||||
def background_refresh():
|
||||
"""Aktualisiert den Cache im Hintergrund."""
|
||||
while True:
|
||||
time.sleep(CACHE_TTL)
|
||||
try:
|
||||
content, changed = fetch_pgn()
|
||||
if changed and content is not None:
|
||||
cache_file = get_cache_file()
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(content)
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Hintergrund-Refresh: PGN aktualisiert ({len(content)} Bytes)")
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Hintergrund-Refresh Fehler: {e}")
|
||||
|
||||
print(f"[{self.log_date_time_string()}] {args[0]}")
|
||||
|
||||
def main():
|
||||
global current_round
|
||||
print("=" * 50)
|
||||
print(" [TROPHY] Lara Kiesewetter – Live Schachturnier")
|
||||
print("=" * 50)
|
||||
print(f" Server läuft auf: http://localhost:{PORT}")
|
||||
print(f" Drücke Ctrl+C zum Beenden")
|
||||
print("=" * 50)
|
||||
|
||||
# Runde aus Tabelle ermitteln
|
||||
standings = fetch_standings()
|
||||
if standings and current_round > 0:
|
||||
print(f"[OK] Aktuelle Runde: {current_round}")
|
||||
else:
|
||||
current_round = 1
|
||||
print("[INFO] Starte mit Runde 1 (Tabelle noch nicht verfügbar)")
|
||||
|
||||
content, changed = fetch_pgn()
|
||||
if changed and content is not None:
|
||||
cache_file = get_cache_file()
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(content)
|
||||
print(f"[OK] PGN geladen ({len(content)} Bytes)")
|
||||
else:
|
||||
print("[WARN] Initialer Ladeversuch fehlgeschlagen, wird wiederholt...")
|
||||
|
||||
# Hintergrund-Refresh Thread starten
|
||||
refresh_thread = threading.Thread(target=background_refresh, daemon=True)
|
||||
refresh_thread.start()
|
||||
|
||||
# Server starten
|
||||
with socketserver.TCPServer(("", PORT), PGNHandler) as httpd:
|
||||
with socketserver.TCPServer(("", PORT), StaticHandler) as httpd:
|
||||
print(f"\n[SERVER] Server gestartet: http://localhost:{PORT}\n")
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n[BYE] Server gestoppt.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
Reference in New Issue
Block a user