File size: 7,924 Bytes
0821095
 
 
 
 
 
 
 
 
3bfd040
0821095
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bfd040
0821095
3bfd040
 
 
 
0821095
 
3bfd040
0821095
3bfd040
 
 
 
 
 
7e0c218
 
 
 
 
 
3bfd040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e0c218
3bfd040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e0c218
 
 
 
 
 
 
 
3bfd040
 
 
 
7e0c218
3bfd040
 
7e0c218
 
 
 
 
3bfd040
7e0c218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bfd040
 
 
 
0821095
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b8ed2c
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
"""
Module de serveur API pour le leaderboard parser.
Ce module contient la configuration FastAPI et les endpoints pour le mode serveur.
"""
import datetime
import threading
import logging
import os
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, HTMLResponse
from src.file_utils import format_datetime

# Initialiser le logger
logger = logging.getLogger("leaderboard-parser")

# Variables globales pour suivre l'état du serveur
processing_status = "idle"
processing_error = None
last_run_time = None

# Initialiser l'application FastAPI
app = FastAPI(title="Leaderboard Parser API")

# Cette fonction sera importée depuis main.py
process_leaderboards = None

def initialize_server(process_function):
    """
    Initialise le serveur avec la fonction de traitement des leaderboards.
    Cette fonction doit être appelée avant de démarrer le serveur.
    
    Args:
        process_function: Fonction qui traite les leaderboards
    """
    global process_leaderboards, last_run_time
    process_leaderboards = process_function
    
    # Initialiser last_run_time à la date actuelle
    last_run_time = datetime.datetime.now()
    logger.info(f"Serveur initialisé avec la fonction de traitement. last_run_time initialisé à {last_run_time.isoformat()}")

# Endpoints API
@app.get("/", response_class=HTMLResponse)
async def root():
    """Root endpoint serving a simple HTML page with status info"""
    global processing_status, last_run_time, processing_error
    
    next_run = format_datetime(last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24)))) if last_run_time else "Not yet scheduled"
    last_run = format_datetime(last_run_time) if last_run_time else "Never"
    
    # Calculate timestamp for next run to use in JavaScript
    next_run_timestamp = ""
    if last_run_time:
        next_run_time = last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24)))
        next_run_timestamp = next_run_time.timestamp() * 1000  # Convert to milliseconds for JavaScript
    
    html_content = f"""
    <!DOCTYPE html>
    <html>
        <head>
            <title>Leaderboard Parser Status</title>
            <style>
                body {{ 
                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                    display: flex; 
                    justify-content: center; 
                    align-items: center; 
                    height: 100vh; 
                    margin: 0; 
                    background-color: #f7f7f7;
                }}
                .status-box {{ 
                    padding: 30px; 
                    text-align: center; 
                    background-color: white;
                    border-radius: 12px;
                    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
                    max-width: 500px;
                }}
                h1 {{ 
                    margin-top: 0;
                    color: #333;
                    font-weight: 600;
                }}
                p {{ 
                    margin: 12px 0;
                    font-size: 16px;
                    line-height: 1.5;
                }}
                .status-label {{
                    font-weight: bold;
                    display: inline-block;
                    min-width: 80px;
                }}
                .status-running {{ color: #1a73e8; font-weight: bold; }}
                .status-idle {{ color: #188038; font-weight: bold; }}
                .status-failed {{ color: #d93025; font-weight: bold; }}
                .countdown {{
                    margin-top: 10px;
                    padding: 8px;
                    background-color: #f5f5f5;
                    border-radius: 6px;
                    display: inline-block;
                    min-width: 250px;
                }}
            </style>
        </head>
        <body>
            <div class="status-box">
                <h1>Leaderboard Parser</h1>
                <p><span class="status-label">Status</span> <span class="status-{processing_status}">{processing_status}</span></p>
                <p><span class="status-label">Last run</span> {last_run}</p>
                {f'<p style="color: #d93025; margin-top: 20px;"><span class="status-label">Error</span> {processing_error}</p>' if processing_error else ''}
                
                <div class="countdown" id="countdown">
                    Calculating time remaining...
                </div>
            </div>
            
            <script>
                // Set the date we're counting down to (from server)
                const nextRunTimestamp = {next_run_timestamp};
                
                // Function to format time remaining in a human-readable way
                function formatTimeRemaining(distance) {{
                    const hours = Math.floor(distance / (1000 * 60 * 60));
                    const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
                    const seconds = Math.floor((distance % (1000 * 60)) / 1000);
                    
                    return `Next run in <b>${{hours}}h ${{minutes}}m ${{seconds}}s</b>`;
                }}
                
                // Update the countdown every 1 second
                function updateCountdown() {{
                    // Get current timestamp
                    const now = new Date().getTime();
                    
                    // If no next run is scheduled
                    if (!nextRunTimestamp) {{
                        document.getElementById("countdown").innerHTML = "No next run scheduled";
                        return;
                    }}
                    
                    // Find the time remaining
                    const distance = nextRunTimestamp - now;
                    
                    // If the countdown is over
                    if (distance < 0) {{
                        document.getElementById("countdown").innerHTML = "Scheduled run in progress or overdue";
                    }} else {{
                        // Display the result
                        document.getElementById("countdown").innerHTML = formatTimeRemaining(distance);
                    }}
                }}
                
                // Initial call to display right away
                updateCountdown();
                
                // Set up interval to update every second
                setInterval(updateCountdown, 1000);
            </script>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content)

@app.get("/status")
async def get_status():
    """Get the current status of the parser"""
    global processing_status, last_run_time, processing_error
    
    return {
        "status": processing_status,
        "last_run": format_datetime(last_run_time) if last_run_time else None,
        "next_run": format_datetime(last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24)))) if last_run_time else None,
        "error": processing_error
    }

@app.post("/run")
async def trigger_run():
    """Manually trigger a leaderboard processing run"""
    global processing_status, process_leaderboards
    
    if not process_leaderboards:
        raise HTTPException(status_code=500, detail="Server not properly initialized")
    
    if processing_status == "running":
        raise HTTPException(status_code=409, detail="Processing is already running")
    
    # Start processing in a separate thread
    threading.Thread(target=lambda: process_leaderboards()).start()
    
    return {"status": "started", "message": "Processing started"}

@app.get("/health")
async def health_check():
    """Health check endpoint for HF Spaces to verify the app is running"""
    return {"status": "ok"}