@@ -2,10 +2,10 @@ import webbrowser
from os import environ , unlink
from sys import platform
from PySide6 . QtCore import QThread , Signal
from PySide6 . QtCore import Qt , QTimer
from PySide6 . QtGui import QIcon , QMouseEvent , QCursor , QGuiApplication
from PySide6 . QtWidgets import QMainWindow , QSizePolicy
from PySide6 . QtCore import QTimer
from PySide6 . QtCore import Qt
from PySide6 . QtGui import QIcon , QMouseEvent , QGuiApplication
from PySide6 . QtWidgets import QMainWindow
from config . config_manager import ConfigManager
from config . constants import PlayerServerInfo , Urls
@@ -13,29 +13,33 @@ from controllers.audio_controller import AudioController
from controllers . glow_animator import GlowAnimator
from controllers . window_dragger import WindowDragger
from discord import discord_oauth
from fake_patch_notes import patch_note
from fake_patch_notes import patch_note # <- TO REMOVE USE API
from fivemserver . auth_worker import AuthWorker
from fivemserver . fivemlauncher import FiveMLauncher
from fivemserver . get_server_token import GetServerTokenForDiscord
from fivemserver . queuemanager import QueueManager
from fivemserver . whitelistmanager import WhiteList
from tools . http_client import ApiError
from ui . error_dialog import show_qt_error
from ui . hazard_stripes import HazardButton
from ui . ui_mainwindow_vertical_pager import Ui_MainWindow
from uitools . countdown_manager import CountdownManager
from uitools . queue_thread import QueueThread
from uitools . ui_builder import set_en_chantier , replace_with_hazard_button , hide_staff_btn_and_recenter
from uitools . window_utility import center_window
# For Linux Wayland to authorize moving window
if platform . startswith ( ' linux ' ) :
environ [ " QT_QPA_PLATFORM " ] = " xcb "
class MainWindow ( QMainWindow ) :
"""
MainWindow class for managing the main application window and its functionalities.
This class is responsible for managing the UI, user interactions, and application state.
It provides mechanisms to handle user authentication, connection to external services, and queued tasks.
The class is designed to be used with the PyQt framework and provides logic for dynamically updating
the UI elements and interacting with other components.
It provides mechanisms to handle user authentication, connection to external services,
and queued tasks. The class is designed to be used with the PySide6 framework and
provides logic for dynamically updating the UI elements and interacting with other
components.
:ivar ui: The main UI object for managing visual components and layouts.
:type ui: Ui_MainWindow
@@ -44,17 +48,10 @@ class MainWindow(QMainWindow):
:ivar stored_user_id: The Discord user ID stored in the configuration.
:type stored_user_id: str
:ivar queue_thread: The thread handling queue operations.
:type queue_thread: Optional[Thread]
:type queue_thread: Optional[Queue Thread]
:ivar queue_position_value: The current queue position.
:type queue_position_value: Optional[int]
:ivar close_timer: Timer object for handling the final closure of the application.
:type close_timer: QTimer
:ivar countdown_timer: Timer object for updating visual countdown timers.
:type countdown_timer: QTimer
:ivar remaining_time: The remaining time for queue or countdown operations, in seconds.
:type remaining_time: int
"""
#update = Signal(str) # Reçoit les callbacks de QueueManager
def __init__ ( self , bundle_dir : str , config_manager : ConfigManager ) :
super ( ) . __init__ ( )
@@ -62,53 +59,51 @@ class MainWindow(QMainWindow):
self . ui = Ui_MainWindow ( )
self . ui . setupUi ( self )
self . auth_worker = None # <--- Pour stocker l'instance du thread
self . auth_worker = None
self . config = config_manager
self . stored_user_id = self . config . get_discord_user ( )
self . queue_thread = None
self . queue_position_value = None
self . close_timer = None
self . countdown_timer = None
self . remaining_time = 0 # en secondes
# Préparation du timer de fermeture finale
self . close_timer = QTimer ( self )
self . close_timer . setSingleShot ( True )
self . close_timer . timeout . connect ( self . close )
# ------------------------------------------------------------------
# Timers (owned here so they share the QObject tree of MainWindow)
# ------------------------------------------------------------------
close_timer = QTimer ( self )
close_timer . setSingleShot ( True )
close_timer . timeout . connect ( self . close )
# Préparation du timer de mise à jour visuelle (1s)
self . countdown_timer = QTimer ( self )
self . countdown_timer . timeout . connect ( self . _update_countdown )
countdown_timer = QTimer ( self )
# UI
# self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow_vertical_pager.ui", self)
#self.setCentralWidget(self.ui.centralWidget())
self . _countdown = CountdownManager (
close_timer = close_timer ,
countdown_timer = countdown_timer ,
label = self . ui . queue_lbl ,
)
# ------------------------------------------------------------------
# Window flags
# ------------------------------------------------------------------
self . setWindowFlags ( Qt . WindowType . FramelessWindowHint | Qt . WindowType . Window )
self . setAttribute ( Qt . WidgetAttribute . WA_TranslucentBackground )
self . setWindowIcon ( QIcon ( str ( bundle_dir / " assets " / " Icon.ico " ) ) )
# Par défaut on affiche la page normal pour la connexion au serveur
# ------------------------------------------------------------------
# Initial page / whitelist check
# ------------------------------------------------------------------
self . ui . stackedWidget . setCurrentIndex ( 0 )
# On cache par défaut les infos liste d'attente
self . ui . queue_lbl . hide ( )
self . ui . queue_position . hide ( )
# Si l'id discord = "" ou des espace, alors on affiche la page comme quoi faut être connecté à discord.
if self . stored_user_id == " " or self . stored_user_id . isspace ( ) :
self . ui . stackedWidget . setCurrentIndex ( 1 )
else :
try :
# on vérifie si le joueur est whitelisté
WhiteList . check_whitelist ( Urls . API_URL . value , self . stored_user_id )
except ApiError as exc :
show_qt_error ( self , " La Tanière " , f " Impossible de vérifier la whitelist. \n \n { exc } " )
PlayerServerInfo . is_whitelist = False
PlayerServerInfo . is_staff = False
# si on est whitelisté, on démarre la file d'attente
if PlayerServerInfo . is_whitelist :
self . start_queue ( )
self . ui . queue_lbl . show ( )
@@ -116,65 +111,50 @@ class MainWindow(QMainWindow):
else :
self . ui . stackedWidget . setCurrentIndex ( 2 )
# Test bouton en contruction
# ------------------------------------------------------------------
# Under-construction button (delegated to ui_builder)
# ------------------------------------------------------------------
en_chantier = False
# on set la css du bouton en fonction de la valeur de la variable en_chantier
self . set_en_chantier ( en_chantier )
set_en_chantier ( self . ui . connexion_btn , en_chantier )
if en_chantier :
old_btn = self . ui . connexion_btn
parent_layout = self . ui . verticalLayout_6 # layout direct du bouton dans le .ui
index = parent_layout . indexOf ( old_btn )
new_btn = HazardButton ( old_btn . parentWidget ( ) )
new_btn . setObjectName ( " connexion_btn " )
new_btn . setText ( " EN MAINTENANCE " )
new_btn . setIcon ( old_btn . icon ( ) )
new_btn . setIconSize ( old_btn . iconSize ( ) )
new_btn . setMinimumSize ( old_btn . minimumSize ( ) )
new_btn . set_hazard ( True )
parent_layout . takeAt ( index )
old_btn . deleteLater ( )
parent_layout . insertWidget ( index , new_btn )
new_btn = replace_with_hazard_button (
self . ui . connexion_btn ,
self . ui . verticalLayout_6 ,
)
self . ui . connexion_btn = new_btn
self . ui . connexion_btn . clicked . connect ( self . _on_connexion )
# centrage vertical du bouton connexion
# ------------------------------------------------------------------
# Staff button / spacer adjustment (delegated to ui_builder)
# ------------------------------------------------------------------
if not PlayerServerInfo . is_staff :
self . ui . staff_btn . hide ( )
layout = self . ui . verticalLayout_6
# Trouver et modifier le spacer item
for i in range ( layout . count ( ) ) :
item = layout . itemAt ( i )
if item . spacerItem ( ) : # C'est un spacer
item . spacerItem ( ) . changeSize ( 20 , 15 , QSizePolicy . Policy . Fixed , QSizePolicy . Policy . Fixed )
layout . invalidate ( ) # Forcer le recalcul du layout
break
hide_staff_btn_and_recenter ( self . ui . staff_btn , self . ui . verticalLayout_6 )
# ------------------------------------------------------------------
# Misc UI
# ------------------------------------------------------------------
self . ui . info_text . setMarkdown ( patch_note )
# Sous-systèmes
# ------------------------------------------------------------------
# Sub-systems
# ------------------------------------------------------------------
self . _audio = AudioController ( self . config , self . ui . audio_volume_adjust , self . ui . mute_btn )
self . _glow = GlowAnimator ( self . ui . connexion_btn )
self . _dragger = WindowDragger ( self )
self . _connect_signals ( )
self . _center_window ( )
center_window ( self ) # delegated to window_utils
self . show ( )
# ------------------------------------------------------------------
# Setup
# ------------------------------------------------------------------
def _connect_signals ( self ) - > None :
"""
Establishes connections between UI elements and their corresponding
functionality. Each signal emitted by a user interaction with a specific
UI button is linked to its respective handler method.
Establishes connections between UI elements and their corresponding handler methods.
:return: None
"""
@@ -186,23 +166,6 @@ class MainWindow(QMainWindow):
self . ui . discord_auth_btn . clicked . connect ( self . _on_discord_auth_btn )
self . ui . no_whitelist_btn . clicked . connect ( self . close )
def _center_window ( self ) - > None :
"""
Centers the window on the screen that the cursor is currently located on.
If the cursor is not on any screen, it uses the primary screen for centering.
The window ' s geometry is adjusted and moved accordingly.
:return: None
"""
self . adjustSize ( )
screen = (
QGuiApplication . screenAt ( QCursor . pos ( ) )
or QGuiApplication . primaryScreen ( )
)
rect = self . frameGeometry ( )
rect . moveCenter ( screen . availableGeometry ( ) . center ( ) )
self . move ( rect . topLeft ( ) )
# ------------------------------------------------------------------
# Button handlers
# ------------------------------------------------------------------
@@ -210,33 +173,25 @@ class MainWindow(QMainWindow):
def _on_connexion ( self ) - > None :
"""
Handles the user connection process, including verification of prerequisites,
session validation, and background authentication for the current user. This
method ensures a smooth user experience by implementing UI locking during
authentication and proceeding with appropriate actions if a valid session
already exists.
session validation, and background authentication for the current user.
:return: None
"""
# 1. Sécurités de base
if not self . stored_user_id or self . stored_user_id . isspace ( ) :
show_qt_error ( self , " Connexion impossible " , " Identifiant Discord absent. " )
return
# 2. Si on a déjà une session valide, on passe directement à la suite
if PlayerServerInfo . session_id :
self . _proceed_to_queue_or_launch ( )
return
# 3. Sinon, on lance l'authentification en arrière-plan
# Verrouillage de l'UI pour éviter le double-clic
self . ui . connexion_btn . setEnabled ( False )
self . ui . connexion_btn . setText ( " Authentification... " )
QGuiApplication . setOverrideCursor ( Qt . WaitCursor ) # Curseur de chargement
QGuiApplication . setOverrideCursor ( Qt . WaitCursor )
# Création et lancement du worker
self . auth_worker = AuthWorker ( self . stored_user_id )
self . auth_worker . finished . connect ( self . _on_auth_finished )
self . auth_worker . finished . connect ( self . auth_worker . deleteLater ) # Nettoyage automatique
self . auth_worker . finished . connect ( self . auth_worker . deleteLater )
self . auth_worker . start ( )
@staticmethod
@@ -244,18 +199,13 @@ class MainWindow(QMainWindow):
"""
Opens the Discord URL in the default web browser.
This method is a static utility function that launches the Discord URL defi ned
in the Urls enumeration. It uses the `webbrowser.open` method to handle the
opening process.
:return: Nothing is returned
:rtype: None
:return: No ne
"""
webbrowser . open ( Urls . DISCORD . value )
def _on_intranet ( self ) - > None :
"""
Opens the intranet URL using the default web browser and starts the glow process .
Opens the intranet URL and starts the glow animation .
:return: None
"""
@@ -266,32 +216,22 @@ class MainWindow(QMainWindow):
"""
Handles the authentication process for Discord when the auth button is clicked.
This method retrieves the Discord user ID and token via OAuth, updates the
local configuration, and initiates a background worker to handle server
registration. The UI button is disabled and the cursor is set to a waiting
state during the network operations to prevent multiple attempts.
:return: None
"""
try :
# 1. Récupération OAuth (souvent via navigateur, assez rapide)
test = discord_oauth . get_discord_user_id ( )
user_id = test [ 0 ]
token = test [ 1 ]
# 2. Mise à jour locale immédiate
self . config . set_discord_user ( user_id )
self . stored_user_id = user_id
PlayerServerInfo . session_id = token
self . config . save ( )
# 3. Lancement du Worker pour la partie réseau (Enregistrement serveur)
# On verrouille l'UI pour éviter les clics multiples
self . ui . discord_auth_btn . setEnabled ( False )
QGuiApplication . setOverrideCursor ( Qt . WaitCursor )
self . auth_worker = AuthWorker ( self . stored_user_id )
# On connecte le signal de fin à une nouvelle méthode dédiée
self . auth_worker . finished . connect ( self . _on_discord_auth_finished )
self . auth_worker . finished . connect ( self . auth_worker . deleteLater )
self . auth_worker . start ( )
@@ -305,17 +245,9 @@ class MainWindow(QMainWindow):
"""
Handles the completion of the Discord authentication process.
This method is triggered once the Discord authentication flow is completed.
It determines the success or failure state of the process and accordingly
triggers user interface updates and calls associated actions, such as verifying
the whitelist status.
:param success: Indicates whether the authentication was successful.
:type success: bool
:param session_id: The session ID obtained after the authentication process.
:type session_id: str
:param error_message: The error message returned if the process failed.
:type error_message: str
:return: None
"""
QGuiApplication . restoreOverrideCursor ( )
@@ -323,16 +255,15 @@ class MainWindow(QMainWindow):
if success :
try :
# Maintenant que la session est OK côté serveur, on vérifie la Whitelist
WhiteList . check_whitelist ( Urls . API_URL . value , self . stored_user_id )
if PlayerServerInfo . is_whitelist :
self . start_queue ( )
self . ui . queue_lbl . show ( )
self . ui . queue_position . show ( )
self . ui . stackedWidget . setCurrentIndex ( 0 ) # Retour page principale
self . ui . stackedWidget . setCurrentIndex ( 0 )
else :
self . ui . stackedWidget . setCurrentIndex ( 2 ) # Page non-whitelisté
self . ui . stackedWidget . setCurrentIndex ( 2 )
except ApiError as exc :
show_qt_error ( self , " La Tanière " , f " Impossible de vérifier la whitelist. \n \n { exc } " )
else :
@@ -340,53 +271,20 @@ class MainWindow(QMainWindow):
f " L ' authentification a réussi mais l ' enregistrement a échoué. \n \n { error_message } " )
# ------------------------------------------------------------------
# Mouse events → délégués au WindowDragger
# Mouse events → delegated to WindowDragger
# ------------------------------------------------------------------
def mousePressEvent ( self , event : QMouseEvent ) - > None :
"""
Handles mouse press event for the widget.
This method is invoked when a mouse button is pressed over the widget. It
delegates the event to an internal dragger object for handling. If the event
is not marked as accepted by the dragger, the parent class implementation
of the mouse press event is called.
:param event: The QMouseEvent instance representing the mouse press event.
:type event: QMouseEvent
:return: None
"""
self . _dragger . mouse_press ( event )
# On ne remonte pas au parent si on a déjà "accepté" l'event
if not event . isAccepted ( ) :
super ( ) . mousePressEvent ( event )
def mouseMoveEvent ( self , event : QMouseEvent ) - > None :
"""
Handle the mouse move event.
This method processes the mouse movement event and delegates the functionality
to the associated dragger. If the event is not accepted after processing, it
passes the event up to the superclass implementation.
:param event: The mouse event being handled.
:type event: QMouseEvent
:return: None
"""
self . _dragger . mouse_move ( event )
if not event . isAccepted ( ) :
super ( ) . mouseMoveEvent ( event )
def mouseReleaseEvent ( self , event : QMouseEvent ) - > None :
"""
Handles the mouse release event. This function processes the event, allowing
custom handling logic through a dragger object, and then delegates to the parent
class ' s handler if the event is not already accepted.
:param event: The mouse release event to be processed.
:type event: QMouseEvent
:return: None
"""
self . _dragger . mouse_release ( event )
if not event . isAccepted ( ) :
super ( ) . mouseReleaseEvent ( event )
@@ -397,39 +295,24 @@ class MainWindow(QMainWindow):
def closeEvent ( self , event ) - > None :
"""
Handl es the close event of the application .
Ensur es the background thread is stopped and configuration is saved before closing .
This method is executed when the application window is being closed. It ensures
the termination of any running threads, saves the application configuration, and
performs necessary cleanup routines before invoking the superclass ' s close event
handler.
:param event: The close event instance that provides event-related data and allows
control over the closing process.
:return: None
"""
if self . queue_thread and self . queue_thread . isRunning ( ) :
self . queue_thread . stop ( )
self . queue_thread . wait ( ) # Attend que le thread se termine proprement
self . queue_thread . wait ( )
self . config . save ( )
self . cleanup ( )
super ( ) . closeEvent ( event )
def cleanup ( self ) :
def cleanup ( self ) - > None :
"""
Stops all active timers and performs cleanup operation s.
Stops all active timers and removes temporary file s.
This method ensures that any active resources, such as timers or temporary
files, are properly stopped or removed to avoid resource leaks. It is a
utility to clean up and reset components associated with the instance.
:raises OSError: If an error occurs during the removal of the temporary file.
:return: None
"""
if self . close_timer :
self . close_timer . stop ( )
if self . countdown_timer :
self . countdown_timer . stop ( )
self . _countdown . stop ( )
if hasattr ( self , ' _sound ' ) :
self . _sound . stop ( )
@@ -440,120 +323,44 @@ class MainWindow(QMainWindow):
pass
# ------------------------------------------------------------------
# Change ui on runtime
# Scheduled close (delegated to CountdownManager)
# ------------------------------------------------------------------
def set_en_chantier ( self , valeur : bool ) :
self . en_chantier = valeur # ta variable Python
self . ui . connexion_btn . setProperty ( " en_chantier " , valeur ) # propriété Qt
self . ui . connexion_btn . style ( ) . unpolish ( self . ui . connexion_btn )
self . ui . connexion_btn . style ( ) . polish ( self . ui . connexion_btn )
# ------------------------------------------------------------------
# Schedule de fermeture du launcher
# ------------------------------------------------------------------
def schedule_close ( self , delay_ms : int = 60000 ) :
def schedule_close ( self , delay_ms : int = 60_000 ) - > None :
"""
Schedules a process to close after a specified delay in milliseconds. The countdown
timer also starts to reflect the remaining time in seconds. Timers already running
are reset to prevent duplicate behavior, and the display is updated immediately.
Schedules the launcher to close after * delay_ms* milliseconds and starts
the visual countdown.
:param delay_ms: The d elay in milliseconds after which the process should
be closed. Defaults to 60000 milliseconds (1 minute) if not specified
or an invalid value is provided.
:param delay_ms: D elay in milliseconds (default: 60 000).
:type delay_ms: int
:return: None
"""
# 1. Sécurité sur les entrées
if not isinstance ( delay_ms , int ) or delay_ms < = 0 :
delay_ms = 60000
# 2. Reset du temps restant (conversion ms -> s)
self . remaining_time = delay_ms / / 1000
# 3. On stop les timers s'ils tournaient déjà (évite les doublons)
self . close_timer . stop ( )
self . countdown_timer . stop ( )
# 4. On lance
self . close_timer . start ( delay_ms )
self . countdown_timer . start ( 1000 )
# 5. Mise à jour immédiate de l'affichage (évite d'attendre 1s)
self . _update_countdown_display ( )
def _update_countdown ( self ) :
"""
Decreases the remaining time by one and updates the countdown display.
This method is responsible for decrementing the `remaining_time` attribute
on each invocation. If the remaining time reaches zero or below, the
`countdown_timer` is stopped immediately and no further action is
performed in this method. Otherwise, it proceeds to update the countdown
display to reflect the current remaining time.
:return: None
"""
self . remaining_time - = 1
if self . remaining_time < = 0 :
self . countdown_timer . stop ( )
return
self . _update_countdown_display ( )
def _update_countdown_display ( self ) :
"""
Updates the countdown display on the user interface with the remaining time
in minutes and seconds format. The remaining time will always be formatted
as a positive value, even if it is less than zero.
:return: None
"""
minutes = max ( 0 , self . remaining_time / / 60 )
seconds = max ( 0 , self . remaining_time % 60 )
self . ui . queue_lbl . setText ( f " ⏳ Fermeture dans { minutes : 02d } : { seconds : 02d } " )
self . _countdown . schedule ( delay_ms )
# ------------------------------------------------------------------
# Queue managment
# Queue manage ment
# ------------------------------------------------------------------
def start_queue ( self ) :
def start_queue ( self ) - > None :
"""
Starts a separate thread for managing a queue and connects it to the relevant
update handler. This method initializes and starts a `QueueThread` instance
using the stored user ID and links it with the update handling functionality
to ensure real-time queue updates are properly handled.
Initialises and starts the :class:`~tools.queue_thread.QueueThread`.
All threading and queue update mechanisms are encapsulated within this method.
:param self: Reference to the calling object.
:return: None
"""
self . queue_thread = QueueThread ( self . stored_user_id , parent = self ) # ← parent=self
self . queue_thread = QueueThread ( self . stored_user_id , parent = self )
self . queue_thread . update . connect ( self . handle_update )
self . queue_thread . start ( )
# 🧪 TEMP - Simule une position en queue pour tester l'UI
# self.handle_update("position:3:10")
def handle_update ( self , message : str ) :
def handle_update ( self , message : str ) - > None :
"""
Handles updates based on a given message and performs relevant UI action s.
Reacts to queue status messages and updates the relevant UI label s.
:param message: The message received, which determines the update logic. Accepts:
- " ok " : Indicates that it ' s the user ' s turn, updating the UI accordingly .
- " ready " : Equivalent to " ok " , indicating the user ' s turn.
- Messages starting with " position: " : Provide the current position and total
in the queue in the format " position:<current>:<total> " , updating the queue
information in the UI.
:param message: Status string – `` " ok " ``, `` " ready " ``, or
`` " position:<current>:<total> " `` .
:type message: str
:return: None
"""
if message == " ok " :
self . ui . queue_lbl . setVisible ( True )
self . ui . queue_position . setVisible ( False )
self . ui . queue_lbl . setText ( " 🚀 C ' est votre tour ! " )
self . queue_position_value = 0
elif message == " ready " :
if message in ( " ok " , " ready " ) :
self . ui . queue_lbl . setVisible ( True )
self . ui . queue_position . setVisible ( False )
self . ui . queue_lbl . setText ( " 🚀 C ' est votre tour ! " )
@@ -569,43 +376,28 @@ class MainWindow(QMainWindow):
def launch_fivem ( self ) :
pass
# ------------------------------------------------------------------
# Session helpers
# ------------------------------------------------------------------
def _ensure_server_session ( self ) - > bool :
"""
Ensures the establishment of a server session, handling authenticatio n
and session registration for a Discord user.
Ensures a valid server session exists, authenticating if necessary.
The method first checks if a valid stored user ID exists. If there is
no stored user ID or it is blank, the session creation will fail. If a
session token (`session_id`) is already present, it will return
immediately with success. Otherwise, it proceeds to authenticate with
the server to obtain a session token and registers the Discord user
against this session.
Errors during registration are caught and handled separately, and the
method logs any issues encountered during the process. If the
authentication and registration are successful, the session is
considered established.
:return: A boolean indicating whether the server session was successfully
established.
:return: ``True`` if the session was established successfully.
:rtype: bool
"""
if not self . stored_user_id or self . stored_user_id . isspace ( ) :
return False
try :
# 1. Si on a déjà la session, on ne fait rien
if PlayerServerInfo . session_id :
return True
# 2. Authentification (avec protection timeout)
# On suppose que authenticate renvoie le token
token = GetServerTokenForDiscord . authenticate ( Urls . API_URL . value )
if token :
PlayerServerInfo . session_id = token
# 3. L'enregistrement est souvent la partie qui bloque (SSL handshake)
# On l'entoure d'un try spécifique
try :
GetServerTokenForDiscord . register_discord_user (
self . stored_user_id ,
@@ -623,107 +415,47 @@ class MainWindow(QMainWindow):
def _on_auth_finished ( self , success : bool , session_id : str , error_message : str ) :
"""
Handl es the completion of the authentication process by updating the UI,
cleaning up the worker, and proceeding with subsequent logic based on the
authentication result.
Restor es the UI and proceeds after the background authentication completes.
:param success: A boolean indicating w hether the authentication was successful .
:param session_id: The s ession identifier returned up on successful authentication .
:param error_message: A descriptive error message in case authenticati on failed .
:param success: W hether authentication succeeded .
:param session_id: S ession token returned on success.
:param error_message: Human-readable error on failur e.
:return: None
"""
# 1. Restauration de l'UI
QGuiApplication . restoreOverrideCursor ( )
self . ui . connexion_btn . setEnabled ( True )
self . ui . connexion_btn . setText ( " SE CONNECTER " ) # Remettre le texte par défaut
self . ui . connexion_btn . setText ( " SE CONNECTER " )
# Nettoyage du worker
if self . auth_worker :
self . auth_worker . deleteLater ( )
self . auth_worker = None
if success :
# 2. Mise à jour des infos globales
PlayerServerInfo . session_id = session_id
# 3. On continue la logique normale
self . _proceed_to_queue_or_launch ( )
else :
# 4. Affichage de l'erreur propre (le handshake SSL a sûrement timeout)
show_qt_error ( self , " Erreur d ' Authentification " ,
f " Impossible de se connecter au serveur. \n \n { error_message } " )
def _proceed_to_queue_or_launch ( self ) :
def _proceed_to_queue_or_launch ( self ) - > None :
"""
Handles the process of either starting the queue, updating the user ' s
position in the queue, or launching the FiveM application if all cond itions
are met. This function first checks the queue position. If there is none,
the queue is started. If the current position in the queue is not at the
front (position 0), an error message is displayed. If the user is at the
front of the queue, the application launches, and further connections are
disabled.
:raises Exception: If an error occurs during the launching process.
Starts the queue if not yet entered, shows the position, or launches FiveM
when the user reaches pos ition 0.
:return: None
"""
try :
# Si on n'a pas encore de position en file d'attente, on la lance
if self . queue_position_value is None :
self . start_queue ( )
return
# Si on est en file d'attente mais pas au début
if self . queue_position_value != 0 :
show_qt_error ( self , " Attente " , f " Position actuelle : { self . queue_position_value } " )
return
# Si tout est OK (Position 0)
FiveMLauncher . launch ( )
self . ui . connexion_btn . setEnabled ( False )
self . schedule_close ( )
except Exception as exc :
show_qt_error ( self , " Erreur de Lancement " , f " Détails : { exc } " )
class QueueThread ( QThread ) :
"""
QueueThread class.
Implements a thread that manages a queue operation using a `QueueManager`. The
class emits a signal to update external components whenever a relevant change
occurs in the queue. Designed to run processing tasks in a separate thread.
:ivar update: Signal emitted with a string message to notify about updates.
:type update: Signal
"""
update = Signal ( str )
def __init__ ( self , user_id : str , parent = None ) : # ← parent=None
super ( ) . __init__ ( parent ) # ← passé à QThread
self . manager = QueueManager (
user_id = user_id ,
on_update = self . update . emit
)
def run ( self ) :
"""
Initiates the start operation for the associated manager. This method
is responsible for invoking the `start` method of the `manager` object,
ensuring that the necessary operations managed by the `manager` commence
execution.
:return: None
:rtype: None
"""
self . manager . start ( )
def stop ( self ) :
"""
Stops the operation managed by the associated manager instance.
This method delegates the stop operation to the `manager` object, ensuring
the termination of any ongoing processes or operations it controls.
:return: None
"""
self . manager . stop ( )