Writing the doc
This commit is contained in:
@@ -13,6 +13,22 @@ Validator = Callable[[Any], bool]
|
||||
Normalizer = Callable[[Any], Any]
|
||||
|
||||
class ConfigField(TypedDict):
|
||||
"""
|
||||
Representation of a configuration field with associated properties.
|
||||
|
||||
The `ConfigField` class specifies the structure of a configuration field
|
||||
used in system configuration settings. It defines the default value,
|
||||
validation logic, and normalization process for a configuration field.
|
||||
This ensures that any provided value complies with predefined rules
|
||||
and converts to a consistent format if necessary.
|
||||
|
||||
:ivar default: The default value assigned to the configuration field.
|
||||
:type default: Any
|
||||
:ivar validator: A callable used to validate the value of the configuration field.
|
||||
:type validator: Validator
|
||||
:ivar normalizer: A callable used to normalize the value of the configuration field.
|
||||
:type normalizer: Normalizer
|
||||
"""
|
||||
default: Any
|
||||
validator: Validator
|
||||
normalizer: Normalizer
|
||||
@@ -36,6 +52,18 @@ CONFIG_SCHEMA: dict[str, ConfigField] = {
|
||||
}
|
||||
|
||||
class ConfigManager:
|
||||
"""
|
||||
Manages application configuration by providing methods to retrieve, update, and store
|
||||
configuration settings. Ensures configuration values comply with predefined schema and
|
||||
validations.
|
||||
|
||||
This class is designed to handle configuration files efficiently, providing streamlined
|
||||
methods for interaction with the application's configuration schema and default values.
|
||||
|
||||
:ivar path: Path to the configuration file. Defaults to a predefined `CONFIG_PATH`
|
||||
if no path is provided.
|
||||
:type path: Path
|
||||
"""
|
||||
def __init__(self, path: Path | None = None) -> None:
|
||||
self.path = path or CONFIG_PATH
|
||||
self._data: ConfigData = self._load()
|
||||
@@ -43,6 +71,18 @@ class ConfigManager:
|
||||
|
||||
# Lecture du fichier de configuration
|
||||
def _load(self) -> ConfigData:
|
||||
"""
|
||||
Loads configuration data from the file at the specified path. If the file does
|
||||
not exist, contains invalid JSON, or is not a dictionary, an empty configuration
|
||||
dictionary is returned.
|
||||
|
||||
:raises OSError: If an error occurs while trying to open the file.
|
||||
:raises json.JSONDecodeError: If the file contains invalid JSON.
|
||||
|
||||
:return: The loaded configuration data as a dictionary, or an empty dictionary
|
||||
if the file does not exist or contains invalid data.
|
||||
:rtype: ConfigData
|
||||
"""
|
||||
if not self.path.exists():
|
||||
return {}
|
||||
|
||||
@@ -57,8 +97,17 @@ class ConfigManager:
|
||||
|
||||
return cast(ConfigData, data)
|
||||
|
||||
# Sauvegarde du fichier de configuration
|
||||
def save(self) -> None:
|
||||
"""
|
||||
Saves the current state to the specified file path in JSON format.
|
||||
|
||||
The method ensures the target directory exists before attempting to save the
|
||||
data. If the object is already in a clean state (not dirty), the method exits
|
||||
without performing any action.
|
||||
|
||||
:raises OSError: If there is an error creating the directory for the file path
|
||||
or writing to the file.
|
||||
"""
|
||||
if not self._dirty:
|
||||
return
|
||||
|
||||
@@ -70,11 +119,35 @@ class ConfigManager:
|
||||
self._dirty = False
|
||||
|
||||
def _get_field(self, key: str) -> ConfigField:
|
||||
"""
|
||||
Fetches the configuration field associated with the provided key.
|
||||
|
||||
This method retrieves the corresponding `ConfigField` object for a given key
|
||||
from the predefined configuration schema (`CONFIG_SCHEMA`). If the key does
|
||||
not exist in the schema, a `KeyError` is raised.
|
||||
|
||||
:param key: The key corresponding to the configuration field to be retrieved.
|
||||
:type key: str
|
||||
:return: The configuration field object associated with the provided key.
|
||||
:rtype: ConfigField
|
||||
:raises KeyError: Raised if the provided key does not exist in the configuration schema.
|
||||
"""
|
||||
if key not in CONFIG_SCHEMA:
|
||||
raise KeyError(f"Unknown config key: {key}")
|
||||
return CONFIG_SCHEMA[key]
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
"""
|
||||
Retrieves the value associated with the specified key from the internal data storage. If the key is
|
||||
not present, a default value specified in the field definition is used. If the value does not pass
|
||||
the validation function defined in the field, the default value is returned instead.
|
||||
|
||||
:param key: The key whose associated value needs to be retrieved.
|
||||
:type key: str
|
||||
:return: The value associated with the specified key. If the key is not present or the value fails
|
||||
validation, the default value for the field is returned.
|
||||
:rtype: Any
|
||||
"""
|
||||
field = self._get_field(key)
|
||||
value = self._data.get(key, field["default"])
|
||||
|
||||
@@ -84,6 +157,19 @@ class ConfigManager:
|
||||
return value
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
Sets a value for the given key, normalizing the value and validating it
|
||||
before storing it. If the value for the key is already normalized and hasn't
|
||||
changed, the operation is skipped. Marks the underlying data as dirty after
|
||||
a successful update.
|
||||
|
||||
:param key: The key for which the value is being set.
|
||||
:type key: str
|
||||
:param value: The value to be set for the specified key. The type is flexible,
|
||||
and it is processed by the key's defined normalizer and validator.
|
||||
:type value: Any
|
||||
:return: None
|
||||
"""
|
||||
field = self._get_field(key)
|
||||
|
||||
normalized = field["normalizer"](value)
|
||||
@@ -98,6 +184,15 @@ class ConfigManager:
|
||||
self._dirty = True
|
||||
|
||||
def reset_all(self) -> None:
|
||||
"""
|
||||
Resets all configuration settings to their default values as defined
|
||||
in the configuration schema, and saves the updated configuration.
|
||||
|
||||
This method retrieves the default values from the configuration schema
|
||||
(CONFIG_SCHEMA) and replaces the current configuration with the defaults.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
defaults: ConfigData = cast(
|
||||
ConfigData,
|
||||
{key: field["default"] for key, field in CONFIG_SCHEMA.items()},
|
||||
@@ -105,6 +200,16 @@ class ConfigManager:
|
||||
self.save(defaults)
|
||||
|
||||
def get_all(self) -> ConfigData:
|
||||
"""
|
||||
Return all configuration data.
|
||||
|
||||
This method retrieves all configuration values based on the defined
|
||||
`CONFIG_SCHEMA` and returns them as a dictionary-like object.
|
||||
|
||||
:return: A dictionary-like object containing all configuration data mapped by
|
||||
their respective keys.
|
||||
:rtype: ConfigData
|
||||
"""
|
||||
return cast(
|
||||
ConfigData,
|
||||
{key: self.get(key) for key in CONFIG_SCHEMA},
|
||||
@@ -114,25 +219,72 @@ class ConfigManager:
|
||||
# SETTERS MÉTIER
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Set Discord ID
|
||||
def set_discord_user(self, user_id: str) -> None:
|
||||
"""
|
||||
Sets the Discord user ID in the application storage.
|
||||
|
||||
:param user_id: The unique identifier of the Discord user to be stored.
|
||||
:type user_id: str
|
||||
:return: None
|
||||
"""
|
||||
self.set(DISCORD_USER_KEY, user_id)
|
||||
|
||||
# Set volume
|
||||
def set_volume(self, volume: int) -> None:
|
||||
"""
|
||||
Sets the volume to a specific value.
|
||||
|
||||
This method updates the volume by setting it to the provided value.
|
||||
It is used to control the volume level in the application.
|
||||
|
||||
:param volume: The desired volume level to be set.
|
||||
:type volume: int
|
||||
:return: None
|
||||
"""
|
||||
self.set(VOLUME_KEY, volume)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GETTERS MÉTIER
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Get discord ID
|
||||
def get_default(self, key: str):
|
||||
"""
|
||||
Retrieve the default value associated with a specified key from the
|
||||
configuration schema.
|
||||
|
||||
This method fetches the default setting for a specific configuration
|
||||
key as defined in the CONFIG_SCHEMA dictionary.
|
||||
|
||||
:param key: The key representing the configuration for which the
|
||||
default value is being retrieved.
|
||||
:type key: str
|
||||
:return: The default value associated with the given key in
|
||||
CONFIG_SCHEMA.
|
||||
:rtype: Any
|
||||
"""
|
||||
return CONFIG_SCHEMA[key]["default"]
|
||||
|
||||
# Get volume value
|
||||
def get_discord_user(self) -> str:
|
||||
"""
|
||||
Gets the Discord username associated with a specific key.
|
||||
|
||||
This method retrieves and casts the stored value associated with the
|
||||
DISCORD_USER_KEY to a string.
|
||||
|
||||
:return: The Discord username corresponding to the given key.
|
||||
:rtype: str
|
||||
"""
|
||||
return cast(str, self.get(DISCORD_USER_KEY))
|
||||
|
||||
def get_volume(self) -> int:
|
||||
"""
|
||||
Calculates and retrieves the volume value as an integer.
|
||||
|
||||
The method accesses the constant `VOLUME_KEY` and fetches
|
||||
the corresponding value from an internal storage. It ensures
|
||||
the value is returned as an integer.
|
||||
|
||||
:return: The volume value retrieved as an integer.
|
||||
:rtype: int
|
||||
"""
|
||||
return cast(int, self.get(VOLUME_KEY))
|
||||
|
||||
@@ -22,10 +22,44 @@ AUTENTICATION_SUCCESS_MESSAGE = """
|
||||
# ENUMS
|
||||
# ---------------------------------------------------------------------------
|
||||
class Resources(Enum):
|
||||
"""
|
||||
Enumeration for resource identifiers.
|
||||
|
||||
Represents resource paths for various assets used in an application.
|
||||
These resources can include file paths for audio, fonts, and other
|
||||
assets. The purpose of the class is to centralize and standardize
|
||||
resource identifiers for easier management and usage in software
|
||||
development.
|
||||
|
||||
:cvar MP3: Path to the audio resource file.
|
||||
:cvar FONT: Path to the font resource file.
|
||||
"""
|
||||
MP3 = ':/assets/the-beat-of-nature.mp3'
|
||||
FONT = ':/assets/Avocado-Cake-Demo.otf'
|
||||
|
||||
class Urls(Enum):
|
||||
"""
|
||||
Enumeration of various URLs and constants used within the application.
|
||||
|
||||
This class provides a centralized location for managing static URLs and
|
||||
related constants required for functionality such as Discord OAuth callback,
|
||||
API connections, and server communication.
|
||||
|
||||
:cvar DISCORD: URL for joining the Discord server.
|
||||
:type DISCORD: str
|
||||
:cvar INTRANET: URL for accessing the website of La Tanière.
|
||||
:type INTRANET: str
|
||||
:cvar API_URL: URL for the API endpoint of La Tanière.
|
||||
:type API_URL: str
|
||||
:cvar FIVEMURL: Connection URL for connecting to the FiveM server.
|
||||
:type FIVEMURL: str
|
||||
:cvar LOCAL_CALLBACK_PORT: Port number used for local callback.
|
||||
:type LOCAL_CALLBACK_PORT: int
|
||||
:cvar LOCAL_CALLBACK_HOST: Hostname of the local callback.
|
||||
:type LOCAL_CALLBACK_HOST: str
|
||||
:cvar LOCAL_CALLBACK_URL: Complete URL for the local OAuth Discord callback.
|
||||
:type LOCAL_CALLBACK_URL: str
|
||||
"""
|
||||
DISCORD = 'https://discord.gg/A7eanmSkp2' # <- La Tanière invitation Discord
|
||||
INTRANET = 'https://la-taniere.fun/connexion/' # <- La Tanière site web
|
||||
API_URL = 'https://prod.la-taniere.fun:30121' # <- La Tanière Api url
|
||||
@@ -35,6 +69,33 @@ class Urls(Enum):
|
||||
LOCAL_CALLBACK_URL = f'http://{LOCAL_CALLBACK_HOST}:{LOCAL_CALLBACK_PORT}/callback' # <- Url de Callback OAuth DiscordA
|
||||
|
||||
class ApiEndPoints(Enum):
|
||||
"""
|
||||
Enumeration representing various API endpoints.
|
||||
|
||||
This class contains a collection of constant values that represent the
|
||||
available API endpoints used for accessing specific functionalities within an
|
||||
application. Each enumeration member corresponds to a distinct API endpoint
|
||||
and its value represents the endpoint URL.
|
||||
|
||||
:ivar API_VERSION: Version of the API being utilized.
|
||||
:type API_VERSION: str
|
||||
:ivar QUEUE_STATUS: Endpoint for retrieving the status of the queue.
|
||||
:type QUEUE_STATUS: str
|
||||
:ivar QUEUE_LEAVE: Endpoint for leaving the queue.
|
||||
:type QUEUE_LEAVE: str
|
||||
:ivar QUEUE_JOIN: Endpoint for joining the queue.
|
||||
:type QUEUE_JOIN: str
|
||||
:ivar QUEUE_REFRESH: Endpoint for refreshing queue information.
|
||||
:type QUEUE_REFRESH: str
|
||||
:ivar WHITELIST_URL_ENDPOINT: Endpoint for checking whitelist status.
|
||||
:type WHITELIST_URL_ENDPOINT: str
|
||||
:ivar REGISTER_USER: Endpoint for registering a new user.
|
||||
:type REGISTER_USER: str
|
||||
:ivar TOKEN_AUTH: Endpoint for token-based authentication.
|
||||
:type TOKEN_AUTH: str
|
||||
:ivar AUTHENTICATION: Endpoint for user authentication.
|
||||
:type AUTHENTICATION: str
|
||||
"""
|
||||
API_VERSION = 'api_v2'
|
||||
QUEUE_STATUS = 'queue/status/'
|
||||
QUEUE_LEAVE = 'queue/leave'
|
||||
@@ -46,10 +107,39 @@ class ApiEndPoints(Enum):
|
||||
AUTHENTICATION = f'{API_VERSION}/auth'
|
||||
|
||||
class DiscordApplicationReferences(Enum):
|
||||
"""
|
||||
Contains references and constants specific to the Discord application.
|
||||
|
||||
This class serves as a collection of constants and configurations
|
||||
related to the Discord application, such as predefined scopes and
|
||||
client identifiers. It is primarily used for maintaining consistency
|
||||
and avoiding hardcoding these values in multiple places.
|
||||
|
||||
:ivar SCOPES: List of OAuth2 scopes used for Discord application authorization.
|
||||
:type SCOPES: list
|
||||
:ivar CLIENT_ID: Unique identifier of the Discord application.
|
||||
:type CLIENT_ID: str
|
||||
"""
|
||||
SCOPES = ["identify"]
|
||||
CLIENT_ID = "1240007913175781508"
|
||||
|
||||
class Glow(Enum):
|
||||
"""
|
||||
Enumeration for representing glow effect constants.
|
||||
|
||||
This class provides constants related to the glow effect, such as color, blur
|
||||
intensity, and animation duration. These constants are utilized for configuring
|
||||
visual effects involving glowing elements in an application.
|
||||
|
||||
:cvar COLOR: Default color used for the glow effect.
|
||||
:type COLOR: QColor
|
||||
:cvar BLUR_BASE: The base blur intensity for the glow effect.
|
||||
:type BLUR_BASE: int
|
||||
:cvar BLUR_PEAK: The peak blur intensity for the glow effect when fully animated.
|
||||
:type BLUR_PEAK: int
|
||||
:cvar ANIM_DURATION: Duration of the glow animation in milliseconds.
|
||||
:type ANIM_DURATION: int
|
||||
"""
|
||||
COLOR = QColor(255, 140, 0, 255)
|
||||
BLUR_BASE = 15
|
||||
BLUR_PEAK = 70
|
||||
@@ -60,6 +150,20 @@ class Glow(Enum):
|
||||
# ---------------------------------------------------------------------------
|
||||
@dataclass
|
||||
class PlayerServerInfo:
|
||||
"""
|
||||
Represents server-related information about a player.
|
||||
|
||||
This class is used to store and manage information about a player's server-related
|
||||
permissions and session details. It keeps track of whether the player has staff privileges,
|
||||
whether they are whitelisted, and their current session ID.
|
||||
|
||||
:ivar is_staff: Indicates if the player has staff permissions.
|
||||
:type is_staff: bool
|
||||
:ivar is_whitelist: Indicates if the player is whitelisted.
|
||||
:type is_whitelist: bool
|
||||
:ivar session_id: The session ID of the player for the current server session.
|
||||
:type session_id: str
|
||||
"""
|
||||
is_staff: bool = False
|
||||
is_whitelist: bool = False
|
||||
session_id: str = None
|
||||
|
||||
@@ -8,6 +8,22 @@ from config.constants import Resources
|
||||
|
||||
|
||||
class AudioController:
|
||||
"""
|
||||
Summary of what the class does.
|
||||
|
||||
This class encapsulates all audio-related logic, including playback, volume
|
||||
management, and mute functionality. It interfaces with configuration management,
|
||||
slider controls, and mute buttons to provide a comprehensive audio control system.
|
||||
|
||||
:ivar tmp: Temporary file created to store the audio resource. This file is
|
||||
used by the audio playback engine during runtime.
|
||||
:type tmp: tempfile._TemporaryFileWrapper
|
||||
:ivar _audio_engine: Underlying audio engine responsible for playback management.
|
||||
:type _audio_engine: cma.Engine
|
||||
:ivar _sound: Object representing the currently playing sound, with properties
|
||||
like volume and looping enabled.
|
||||
:type _sound: cma.Sound
|
||||
"""
|
||||
# Encapsule toute la logique audio : lecture, volume, mute.
|
||||
|
||||
def __init__(self, config: ConfigManager, slider, mute_btn):
|
||||
@@ -51,6 +67,18 @@ class AudioController:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def toggle_mute(self) -> None:
|
||||
"""
|
||||
Toggle the mute state of the audio system.
|
||||
|
||||
This method toggles between muted and unmuted states. When muting, it saves
|
||||
the current volume level for restoration upon unmuting. When unmuting, it
|
||||
restores the volume to the previously saved level.
|
||||
|
||||
:raises AttributeError: If internal attributes required for volume handling
|
||||
are missing or incorrectly configured.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if not self._is_muted:
|
||||
self._previous_volume = self._slider.value()
|
||||
self._apply_volume(0)
|
||||
@@ -64,6 +92,15 @@ class AudioController:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _on_slider_changed(self, value: int) -> None:
|
||||
"""
|
||||
Handles the changes in the slider value and updates the appropriate
|
||||
attributes and state accordingly. This method adjusts the volume
|
||||
whenever the slider value changes and toggles the mute button if
|
||||
necessary.
|
||||
|
||||
:param value: The new slider value, representing the volume level as
|
||||
an integer between 0 and 100.
|
||||
"""
|
||||
self._is_muted = value == 0
|
||||
if hasattr(self, '_sound'):
|
||||
# cyminiaudio attend souvent un float entre 0.0 et 1.0
|
||||
@@ -72,6 +109,21 @@ class AudioController:
|
||||
self._refresh_mute_btn()
|
||||
|
||||
def _apply_volume(self, value: int, save: bool = True) -> None:
|
||||
"""
|
||||
Adjusts and applies the volume value for a sound object and UI interaction.
|
||||
|
||||
This method modifies the volume of a given sound object if it exists, updates the slider UI
|
||||
to reflect the new volume value, and optionally saves this volume value in the configuration.
|
||||
Mute button state is refreshed after applying the changes.
|
||||
|
||||
:param value: The new volume value to apply, as an integer percentage (0-100).
|
||||
:type value: int
|
||||
:param save: Boolean flag indicating whether the volume should be saved to the configuration.
|
||||
Defaults to True.
|
||||
:type save: bool, optional
|
||||
:return: This method does not return any value.
|
||||
:rtype: None
|
||||
"""
|
||||
self._slider.blockSignals(True)
|
||||
self._slider.setValue(value)
|
||||
self._slider.blockSignals(False)
|
||||
@@ -86,6 +138,14 @@ class AudioController:
|
||||
self._refresh_mute_btn()
|
||||
|
||||
def _refresh_mute_btn(self) -> None:
|
||||
"""
|
||||
Updates the state of the mute button based on the current slider value. If the
|
||||
slider value is 0, the button is marked as muted; otherwise, it is marked
|
||||
as unmuted. The visual appearance of the button is refreshed to reflect
|
||||
the updated state.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
muted = self._slider.value() == 0
|
||||
self._mute_btn.setProperty("muted", muted)
|
||||
self._mute_btn.style().unpolish(self._mute_btn)
|
||||
|
||||
@@ -5,6 +5,15 @@ from config.constants import Glow
|
||||
|
||||
|
||||
class GlowAnimator:
|
||||
"""
|
||||
Manages the pulsing glow effect for a widget.
|
||||
|
||||
Provides functionality to animate a glowing effect with adjustable blur
|
||||
radii and enables starting and stopping the animation on the target widget.
|
||||
|
||||
:ivar widget: The target widget for the glow effect.
|
||||
:type widget: QWidget
|
||||
"""
|
||||
# Gère l'effet de lueur pulsée sur un widget.
|
||||
|
||||
def __init__(self, widget):
|
||||
@@ -24,9 +33,22 @@ class GlowAnimator:
|
||||
self._anim.setLoopCount(-1)
|
||||
|
||||
def start(self) -> None:
|
||||
"""
|
||||
Starts the animation and applies a graphical effect to the associated widget.
|
||||
|
||||
This function sets a previously defined graphical effect on the widget and
|
||||
starts the associated animation.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self._widget.setGraphicsEffect(self._effect)
|
||||
self._anim.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Stops the animation and removes the graphics effect from the associated widget.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self._anim.stop()
|
||||
self._widget.setGraphicsEffect(None)
|
||||
|
||||
@@ -4,22 +4,65 @@ from PySide6.QtWidgets import QMainWindow
|
||||
|
||||
|
||||
class WindowDragger:
|
||||
"""
|
||||
Manages window dragging behavior for a QMainWindow instance.
|
||||
|
||||
Provides methods to enable click-and-drag functionality, allowing the
|
||||
user to reposition a window by clicking and dragging with the left mouse
|
||||
button. This class requires the use of Qt framework's event system.
|
||||
|
||||
:ivar window: The QMainWindow instance that will be associated with the
|
||||
dragging behavior.
|
||||
:type window: QMainWindow
|
||||
"""
|
||||
def __init__(self, window: QMainWindow):
|
||||
self._window = window
|
||||
self._drag_pos = None
|
||||
|
||||
def mouse_press(self, event: QtGui.QMouseEvent) -> None:
|
||||
"""
|
||||
Handle mouse press events and determine the position offset between
|
||||
the click location and the top-left corner of the window. This is
|
||||
used to facilitate window dragging operations.
|
||||
|
||||
:param event: The mouse event containing information about the
|
||||
mouse button pressed and its position.
|
||||
:type event: QtGui.QMouseEvent
|
||||
:return: None
|
||||
"""
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
# On stocke le vecteur entre le clic et le coin haut-gauche de la fenêtre
|
||||
self._drag_pos = event.globalPosition().toPoint() - self._window.frameGeometry().topLeft()
|
||||
event.accept() # On informe Qt que l'event est géré
|
||||
|
||||
def mouse_move(self, event: QtGui.QMouseEvent) -> None:
|
||||
"""
|
||||
Handles the mouse move event for repositioning a window.
|
||||
|
||||
This method is used to move a window when the left mouse button is held
|
||||
down, and the dragging position has been initialized. The window is moved
|
||||
based on the global mouse position offset by the initial drag position.
|
||||
|
||||
:param event: The mouse event containing the current state of the
|
||||
mouse, including its buttons and global position.
|
||||
:type event: QtGui.QMouseEvent
|
||||
:return: None
|
||||
"""
|
||||
# Vérification stricte du bouton gauche ET de l'existence du point d'ancrage
|
||||
if (event.buttons() & Qt.MouseButton.LeftButton) and self._drag_pos is not None:
|
||||
self._window.move(event.globalPosition().toPoint() - self._drag_pos)
|
||||
event.accept()
|
||||
|
||||
def mouse_release(self, event: QtGui.QMouseEvent) -> None:
|
||||
"""
|
||||
Handles the mouse release event for the widget.
|
||||
|
||||
This method is triggered when the mouse button is released,
|
||||
handling necessary cleanup and accepting the event.
|
||||
|
||||
:param event: The mouse release event containing details of the mouse action.
|
||||
:type event: QtGui.QMouseEvent
|
||||
:return: None
|
||||
"""
|
||||
self._drag_pos = None
|
||||
event.accept()
|
||||
|
||||
@@ -26,7 +26,16 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
"""
|
||||
callback pour discord auth
|
||||
Handles incoming GET requests and processes the callback from an OAuth authorization flow.
|
||||
|
||||
Parses the URL path and verifies that it matches the expected callback endpoint. If the path does
|
||||
not match "/callback", a 404 error is sent to the client. Extracts the authorization code from
|
||||
query parameters if it exists, stores it, and responds to the client with a success message
|
||||
rendered with the company logo.
|
||||
|
||||
:raises HTTPError: Sends a 404 HTTP error if the requested path does not match "/callback".
|
||||
:param self: Reference to the current instance of the class.
|
||||
:return: None
|
||||
"""
|
||||
parsed_url = urlparse(self.path)
|
||||
if parsed_url.path != "/callback":
|
||||
@@ -51,7 +60,19 @@ def get_discord_client_id() -> str:
|
||||
# return discord user id
|
||||
def get_discord_user_id() -> tuple[str, str]:
|
||||
"""
|
||||
Retourne l'id du compte discord de l'utilisateur via l'oauh discord.
|
||||
Authenticate a user via Discord OAuth2 and retrieve their Discord user ID along with
|
||||
the session ID. This function manages the entire OAuth flow, including opening the
|
||||
authorization URL in the user's browser, handling the callback, exchanging the
|
||||
authorization code for an access token, and retrieving the user's profile information.
|
||||
|
||||
:raises ApiError: Raised in the following scenarios:
|
||||
|
||||
- If no authorization code is received after the OAuth callback.
|
||||
- If the token endpoint returns invalid JSON or if the `access_token` is missing.
|
||||
- If the Discord profile endpoint returns invalid JSON or if the user's ID is missing.
|
||||
|
||||
:return: A tuple containing the user's Discord ID and the associated session ID.
|
||||
:rtype: tuple[str, str]
|
||||
"""
|
||||
|
||||
# récupération des infos serveur la tanière
|
||||
|
||||
@@ -7,10 +7,25 @@ from fivemserver.get_server_token import GetServerTokenForDiscord
|
||||
|
||||
class DiscordToken:
|
||||
"""
|
||||
Décode le token discord
|
||||
Provides functionality to decode a Discord token by utilizing an authentication
|
||||
process with a predefined API URL.
|
||||
|
||||
This class is designed to offer a method for securely retrieving and decoding
|
||||
the Discord token after successful authentication. It ensures the proper
|
||||
handling of the token and raises relevant exceptions in case of authentication
|
||||
or retrieval failures.
|
||||
"""
|
||||
@staticmethod
|
||||
def decode_discord_token():
|
||||
"""
|
||||
Decodes the Discord token by authenticating through a specified API URL and retrieving
|
||||
the token value.
|
||||
|
||||
:raises AuthenticationError: If the authentication with the API URL fails.
|
||||
:raises TokenRetrievalError: If there is an issue retrieving the Discord token.
|
||||
:return: The decoded Discord token.
|
||||
:rtype: str
|
||||
"""
|
||||
discord_token = GetServerTokenForDiscord.get_token(
|
||||
GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
|
||||
)
|
||||
@@ -21,7 +36,18 @@ class CheckDiscord:
|
||||
@staticmethod
|
||||
def isdiscordrunning() -> bool:
|
||||
"""
|
||||
Vérifie si Discord est en cours d'exécution sur l'ordinateur. (Vérifie aussi pour Linux)
|
||||
Checks if Discord is running on the system.
|
||||
|
||||
This method iterates through the running processes on the system and
|
||||
compares their names to identify whether the Discord application processes
|
||||
are actively running. It checks for different variations of Discord
|
||||
process names, including "discord.exe", "discordcanary.exe", "discord",
|
||||
and "discord canary".
|
||||
|
||||
:return: A boolean value indicating whether a Discord process is
|
||||
currently running. Returns True if Discord is identified; otherwise,
|
||||
returns False.
|
||||
:rtype: bool
|
||||
"""
|
||||
for process in psutil.process_iter(["name"]):
|
||||
if (
|
||||
@@ -36,8 +62,16 @@ class CheckDiscord:
|
||||
@staticmethod
|
||||
def isuserconnected() -> bool:
|
||||
"""
|
||||
Vérifie si l'utilisateur Discord est connecté.
|
||||
⚠️ne vérifie pas le user id discord.
|
||||
Determines whether a user is successfully connected to the Discord Presence service.
|
||||
|
||||
This static method attempts to establish a connection with the Discord Presence
|
||||
service using the provided client ID. If successful, it returns True. In case
|
||||
of any exceptions during the connection attempt, the method safely handles
|
||||
them and ensures the connection is closed before returning False.
|
||||
|
||||
:returns: Boolean value indicating whether the user is connected to the
|
||||
Discord Presence service.
|
||||
:rtype: bool
|
||||
"""
|
||||
rpc = Presence(DiscordApplicationReferences.CLIENT_ID.value)
|
||||
try:
|
||||
|
||||
@@ -7,6 +7,22 @@ from tools.http_client import ApiError # Importe ton exception personnalisée
|
||||
|
||||
|
||||
class AuthWorker(QThread):
|
||||
"""
|
||||
Handles authentication of a Discord user using a separate thread.
|
||||
|
||||
This class is designed to manage the authentication process for a Discord
|
||||
user in a non-blocking manner by utilizing a QThread. It emits signals to
|
||||
communicate the authentication results back to the main UI thread. It
|
||||
provides proper control mechanisms to start, stop, and manage the
|
||||
lifecycle of the thread while performing secure API calls and handling
|
||||
exceptions during the process.
|
||||
|
||||
:ivar discord_user_id: The Discord user ID used for authentication.
|
||||
:type discord_user_id: str
|
||||
:ivar finished: Signal emitted upon completion, passing the success status,
|
||||
session ID, and error message.
|
||||
:type finished: pyqtSignal(bool, str, str)
|
||||
"""
|
||||
# Signaux pour communiquer avec l'UI
|
||||
# finished(success, session_id, error_message)
|
||||
finished = Signal(bool, str, str)
|
||||
@@ -17,7 +33,24 @@ class AuthWorker(QThread):
|
||||
self._is_running = True
|
||||
|
||||
def run(self):
|
||||
"""Exécuté dans un thread séparé."""
|
||||
"""
|
||||
Executes the authentication and user registration process for a Discord user.
|
||||
This method handles obtaining an authentication token from the server, registering
|
||||
the Discord user with the retrieved token, and communicates the result through
|
||||
a signal.
|
||||
|
||||
:raises ApiError: If an error occurs during API-related operations, like failing
|
||||
to fetch a valid token or registration issues.
|
||||
:raises Exception: For critical errors such as SSL or networking issues encountered
|
||||
during the process.
|
||||
:param self: The instance of the current object executing the method.
|
||||
|
||||
:return: Emits a signal indicating the completion of authentication and registration
|
||||
process with the following values:
|
||||
- A boolean indicating success (True) or failure (False).
|
||||
- The session ID if the authentication is successful, otherwise an empty string.
|
||||
- An error message explaining the reason for failure, if any.
|
||||
"""
|
||||
session_id = ""
|
||||
error_msg = ""
|
||||
success = False
|
||||
@@ -61,6 +94,13 @@ class AuthWorker(QThread):
|
||||
self.finished.emit(success, session_id, error_msg)
|
||||
|
||||
def stop(self):
|
||||
"""Permet d'annuler le thread proprement."""
|
||||
"""
|
||||
Stops the running thread with a request for interruption.
|
||||
|
||||
This method sets the thread state to not running and requests an
|
||||
interruption of the thread execution.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self._is_running = False
|
||||
self.requestInterruption() # Demande l'interruption à QThread
|
||||
|
||||
@@ -5,16 +5,34 @@ from config.constants import Urls
|
||||
|
||||
|
||||
class FiveMLauncher:
|
||||
"""
|
||||
A class to manage the launching of the FiveM application.
|
||||
|
||||
This class provides functionality to launch the FiveM executable if the
|
||||
specified path exists or to open the default FiveM URL if no valid executable
|
||||
is found. It is designed to streamline the process of launching the FiveM
|
||||
application for end-users with minimal configuration required.
|
||||
|
||||
:ivar fivem_path: Path to the FiveM executable on the system.
|
||||
:type fivem_path: str
|
||||
"""
|
||||
def __init__(self, fivem_path: str):
|
||||
self.fivem_path = os.path.expandvars(fivem_path)
|
||||
|
||||
@staticmethod
|
||||
def launch():
|
||||
"""
|
||||
if not os.path.exists(self.fivem_path):
|
||||
raise FileNotFoundError("❌ FiveM.exe introuvable")
|
||||
Launches an external application by opening the specified URL using the subprocess module.
|
||||
|
||||
subprocess.Popen(self.fivem_path, shell=True)
|
||||
This static method uses the `subprocess.Popen` function to open a URL, effectively launching
|
||||
an external application (e.g., a browser or a platform-specific client) associated with the
|
||||
given URL scheme. It does not return any value and does not handle failures directly.
|
||||
|
||||
Raises:
|
||||
Any exceptions raised by `subprocess.Popen`, such as `FileNotFoundError` or `PermissionError`,
|
||||
will propagate unless handled by the caller.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
subprocess.Popen(f"explorer {Urls.FIVEMURL.value}")
|
||||
|
||||
@@ -10,10 +10,35 @@ from tools.http_client import http_post, ApiError
|
||||
|
||||
|
||||
class GetServerTokenForDiscord:
|
||||
"""
|
||||
Provides functionalities for authenticating a client with a server, retrieving
|
||||
session tokens, and registering a Discord user. This class utilizes ECDH key
|
||||
exchange for secure communication, and is used to interact with specific server
|
||||
endpoints required for authentication and token handling.
|
||||
|
||||
:ivar derived_key: The derived shared key generated during the ECDH key exchange
|
||||
in the authentication process. Holds the derived key or None if the
|
||||
authentication process hasn't been completed.
|
||||
:type derived_key: bytes | None
|
||||
"""
|
||||
derived_key: bytes | None = None
|
||||
|
||||
@staticmethod
|
||||
def authenticate(server: str | None = Urls.API_URL.value) -> str:
|
||||
"""
|
||||
Authenticate with a server by performing Elliptic Curve Diffie-Hellman (ECDH) key exchange and retrieve a session ID.
|
||||
|
||||
This method uses ECDH to generate a shared key between the client and server. The client generates a private/public key
|
||||
pair, then sends its public key to the server. The server responds with its public key, which is used to establish a
|
||||
shared secret. The shared key is derived using HKDF for further secure communication. A session ID is returned
|
||||
upon successful authentication.
|
||||
|
||||
:param server: The URL of the server to authenticate with. If not provided, the default API URL is used.
|
||||
:type server: str | None
|
||||
:return: The session ID provided by the server upon successful authentication.
|
||||
:rtype: str
|
||||
:raises ApiError: If an error occurs during authentication or if the server response is invalid.
|
||||
"""
|
||||
if server is None:
|
||||
server = Urls.API_URL.value
|
||||
|
||||
@@ -60,6 +85,22 @@ class GetServerTokenForDiscord:
|
||||
|
||||
@staticmethod
|
||||
def get_token(session_id: bytes, server: str | None = Urls.API_URL.value) -> bytes:
|
||||
"""
|
||||
Retrieve a Discord token using the provided session ID and server URL.
|
||||
|
||||
This method sends an HTTP POST request to the specified server to fetch
|
||||
an encrypted token, decrypts it using the previously established derived
|
||||
key, and returns the resulting token.
|
||||
|
||||
:param session_id: Session ID used for authentication.
|
||||
:type session_id: bytes
|
||||
:param server: Optional server URL for the token endpoint. If not provided, a default URL is used.
|
||||
:type server: str | None
|
||||
:return: The decrypted Discord token.
|
||||
:rtype: bytes
|
||||
:raises ApiError: Raised if the derived key is missing, if the server response contains invalid
|
||||
data, or if JSON parsing fails.
|
||||
"""
|
||||
# ==========================
|
||||
# DISCORD TOKEN
|
||||
# ==========================
|
||||
@@ -94,6 +135,24 @@ class GetServerTokenForDiscord:
|
||||
@staticmethod
|
||||
def register_discord_user(
|
||||
discord_user_id: str, session_id: str, server: str | None = Urls.API_URL.value) -> bool:
|
||||
"""
|
||||
Registers a Discord user to the external server using the provided session ID and server URL.
|
||||
|
||||
This method sends a POST request to the specific API endpoint to register the
|
||||
Discord user. If the `server` parameter is not provided, a default URL will be
|
||||
used as the server base URL. The registration is completed when the external
|
||||
server responds successfully, returning a boolean status indicating success.
|
||||
|
||||
:param discord_user_id: The unique identifier for the Discord user to register.
|
||||
:type discord_user_id: str
|
||||
:param session_id: The session ID to authenticate the registration request.
|
||||
:type session_id: str
|
||||
:param server: The base URL of the server. Defaults to the pre-configured API URL.
|
||||
:type server: Optional[str]
|
||||
:return: True if the user registration is successful, False otherwise.
|
||||
:rtype: bool
|
||||
:raises ApiError: If the API request fails or an invalid JSON response is returned.
|
||||
"""
|
||||
if server is None:
|
||||
server = Urls.API_URL.value
|
||||
|
||||
|
||||
@@ -8,6 +8,17 @@ from config.constants import Urls, ApiEndPoints
|
||||
|
||||
|
||||
class QueueManager:
|
||||
"""
|
||||
Manages user interaction with a queue system, including joining, checking status,
|
||||
leaving, and refreshing the session. This class provides mechanisms to handle
|
||||
user updates while maintaining the session's freshness and responsiveness.
|
||||
|
||||
:ivar user_id: Unique identifier for the user in the queue system.
|
||||
:type user_id: str
|
||||
:ivar on_update: Callback function invoked to provide updates on the
|
||||
queue status.
|
||||
:type on_update: Callable[[str], None
|
||||
"""
|
||||
def __init__(self, user_id: str, on_update: Callable[[str], None]):
|
||||
self.user_id = user_id
|
||||
self.on_update = on_update
|
||||
@@ -16,9 +27,22 @@ class QueueManager:
|
||||
self.REFRESH_INTERVAL = 30 # ← en secondes
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the running state of the current object or process. This method is
|
||||
typically used to change an internal state that determines whether a process
|
||||
is actively running or not.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self._running = False
|
||||
|
||||
def join_queue(self) -> dict:
|
||||
"""
|
||||
Joins the user to the queue by sending a POST request with the user's UUID.
|
||||
|
||||
:return: The response of the API in JSON format as a dictionary.
|
||||
:rtype: dict
|
||||
"""
|
||||
res = requests.post(
|
||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_JOIN.value}",
|
||||
json={"uuid": self.user_id},
|
||||
@@ -27,12 +51,27 @@ class QueueManager:
|
||||
return res.json()
|
||||
|
||||
def check_status(self) -> dict:
|
||||
"""
|
||||
Fetches the current status of the queue for the user.
|
||||
|
||||
This method sends a GET request to the specified API endpoint to retrieve the
|
||||
queue status for the given user and returns the response as a dictionary.
|
||||
|
||||
:return: A dictionary containing the response from the API.
|
||||
:rtype: dict
|
||||
"""
|
||||
res = requests.get(
|
||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_STATUS.value}/{self.user_id}"
|
||||
)
|
||||
return res.json()
|
||||
|
||||
def leave_queue(self):
|
||||
"""
|
||||
Removes the user identified by their UUID from the active queue.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
requests.post(
|
||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_LEAVE.value}",
|
||||
json={"uuid": self.user_id},
|
||||
@@ -40,7 +79,16 @@ class QueueManager:
|
||||
)
|
||||
|
||||
def refresh_session(self):
|
||||
"""Informe le serveur que le client est toujours présent, sans changer le session_id."""
|
||||
"""
|
||||
Refreshes the session by sending a POST request to the session refresh endpoint.
|
||||
|
||||
This function attempts to use the current session ID to send a request to refresh
|
||||
the user's session. If the session ID is unavailable or the request fails, the
|
||||
function exits without raising any exceptions, allowing the session on the server
|
||||
side to expire naturally.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
session_id = PlayerServerInfo.session_id
|
||||
if not session_id:
|
||||
return
|
||||
@@ -58,6 +106,19 @@ class QueueManager:
|
||||
pass # On ignore silencieusement, le serveur expirera de lui-même
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Executes the queue processing logic, manages the session refresh, and handles updates
|
||||
related to queue position.
|
||||
|
||||
This method starts by checking the user's initial position in the queue. If the position
|
||||
is at the front (position 0), it updates the status to "ok" and stops further execution.
|
||||
Otherwise, it enters a loop where it periodically refreshes the session and checks the
|
||||
queue position until the user's position reaches the front or the process is stopped.
|
||||
|
||||
:raises Exception: If there is an issue in checking the status or refreshing the session.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
join = self.join_queue()
|
||||
|
||||
if join.get("position") == 0:
|
||||
|
||||
@@ -4,8 +4,26 @@ from tools.http_client import ApiError, http_get
|
||||
|
||||
|
||||
class WhiteList:
|
||||
"""
|
||||
Handles whitelist checking functionality for a given user and URL.
|
||||
|
||||
Provides methods for communicating with an API endpoint to determine
|
||||
if a user is whitelisted and/or has staff permissions on a server.
|
||||
|
||||
"""
|
||||
@staticmethod
|
||||
def check_whitelist(url: str, discord_user_id: str) -> None:
|
||||
"""
|
||||
Checks if a Discord user is in the whitelist of a specific server by making a request to
|
||||
a given API endpoint. Also updates class attributes `is_whitelist` and `is_staff` with
|
||||
the results from the API.
|
||||
|
||||
:param url: The base URL of the API endpoint to check the whitelist.
|
||||
:type url: str
|
||||
:param discord_user_id: The Discord user ID to check against the whitelist.
|
||||
:type discord_user_id: str
|
||||
:raises ApiError: If a network-related issue occurs or if the JSON response is invalid.
|
||||
"""
|
||||
try:
|
||||
api_data = http_get(f"{url}/{ApiEndPoints.WHITELIST_URL_ENDPOINT.value}/{discord_user_id}").json()
|
||||
except ApiError:
|
||||
|
||||
+21
-1
@@ -38,7 +38,17 @@ if sys.platform.startswith("win"):
|
||||
# Setup
|
||||
# ---------------------------------------------------------------------------
|
||||
def setup_environment(app, bundle_dir):
|
||||
# Utilisation de pathlib pour la robustesse
|
||||
"""
|
||||
Sets up the environment for the given application by loading a custom stylesheet and font. It attempts to apply
|
||||
a stylesheet from a specified bundle directory and set a custom font. If either action fails, the function uses
|
||||
fallback options.
|
||||
|
||||
:param app: The application instance where the environment setup is to be applied.
|
||||
:type app: QApplication
|
||||
:param bundle_dir: The directory path containing the resources like stylesheets and fonts.
|
||||
:type bundle_dir: str
|
||||
:return: None
|
||||
"""
|
||||
style_path = Path(bundle_dir) / "styles" / "styles.qss"
|
||||
|
||||
# Tentative de chargement du style
|
||||
@@ -60,6 +70,16 @@ def setup_environment(app, bundle_dir):
|
||||
# Font helper
|
||||
# ---------------------------------------------------------------------------
|
||||
def load_custom_font() -> str:
|
||||
"""
|
||||
Loads a custom font resource into the application and retrieves its primary font family.
|
||||
This function ensures that a font specified in the application's resources is properly loaded
|
||||
and ready for use. If the font cannot be loaded or no font families are found, a runtime error is raised.
|
||||
|
||||
:raises RuntimeError: If the font cannot be loaded from resources or if no font families are found
|
||||
within the loaded font.
|
||||
:return: The primary font family name of the loaded custom font.
|
||||
:rtype: str
|
||||
"""
|
||||
font_id = QFontDatabase.addApplicationFont(Resources.FONT.value)
|
||||
if font_id == -1:
|
||||
raise RuntimeError("Failed to load font from resources.")
|
||||
|
||||
@@ -11,6 +11,23 @@ HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ApiError(RuntimeError):
|
||||
"""
|
||||
Represents an error that occurs during API operations.
|
||||
|
||||
This class is used to handle API-related errors that include additional
|
||||
information such as the URL being accessed and the corresponding status
|
||||
code. It extends the RuntimeError class and provides a structured format
|
||||
for encapsulating these details.
|
||||
|
||||
:ivar url: The URL associated with the API operation where the error
|
||||
occurred. This attribute is optional and may be None if the URL is
|
||||
not available.
|
||||
:type url: str | None
|
||||
:ivar status_code: The HTTP status code returned by the API that
|
||||
indicates the cause of the error. This attribute is optional and may
|
||||
be None if the status code is not available.
|
||||
:type status_code: int | None
|
||||
"""
|
||||
def __init__(self, message: str, *, url: str | None = None, status_code: int | None = None) -> None:
|
||||
super().__init__(message)
|
||||
self.url = url
|
||||
@@ -27,6 +44,23 @@ def http_request(
|
||||
json: dict[str, Any] | None = None,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
) -> requests.Response:
|
||||
"""
|
||||
Executes an HTTP request using the specified parameters and returns the server's response. This
|
||||
method is a wrapper around the ``requests`` library's ``request`` function, including error
|
||||
handling and logging for failed requests.
|
||||
|
||||
:param method: The HTTP method to use for the request, such as GET, POST, PUT, DELETE, etc.
|
||||
:param url: The URL to which the HTTP request is sent.
|
||||
:param headers: An optional dictionary of headers to include in the request.
|
||||
:param params: Optional URL parameters to append to the request.
|
||||
:param data: Optional form data to include in the request body.
|
||||
:param json: Optional JSON payload to include in the request body.
|
||||
:param timeout: The maximum time, in seconds, to wait for the request to complete before raising
|
||||
a timeout exception. Defaults to `DEFAULT_TIMEOUT`.
|
||||
:return: The HTTP response object returned by the server.
|
||||
:raises ApiError: If the HTTP request fails or an exception occurs, an ApiError is raised with
|
||||
details of the failure and the corresponding HTTP status code if available.
|
||||
"""
|
||||
try:
|
||||
response = requests.request(
|
||||
method=method,
|
||||
@@ -58,6 +92,23 @@ def http_get(
|
||||
params: dict[str, Any] | None = None,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
) -> requests.Response:
|
||||
"""
|
||||
Makes an HTTP GET request to the given URL with optional headers, query parameters,
|
||||
and a timeout value. Returns the response object upon successful execution of the
|
||||
request. This function internally utilizes the `http_request` method to perform
|
||||
the actual HTTP operation.
|
||||
|
||||
:param url: The URL to which the GET request is sent.
|
||||
:type url: str
|
||||
:param headers: Optional headers to include with the HTTP request.
|
||||
:type headers: dict[str, str] | None
|
||||
:param params: Optional query parameters to include in the URL of the HTTP request.
|
||||
:type params: dict[str, Any] | None
|
||||
:param timeout: The timeout value (in seconds) for the request.
|
||||
:type timeout: int
|
||||
:return: The response object containing the HTTP response data.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
return http_request("GET", url, headers=headers, params=params, timeout=timeout)
|
||||
|
||||
|
||||
@@ -69,4 +120,25 @@ def http_post(
|
||||
json: dict[str, Any] | None = None,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
) -> requests.Response:
|
||||
"""
|
||||
Sends an HTTP POST request to the specified URL with optional headers, data, JSON,
|
||||
and a customizable timeout value. Simplifies making POST requests through a
|
||||
user-friendly interface.
|
||||
|
||||
:param url: The target URL where the POST request will be sent.
|
||||
:type url: str
|
||||
:param headers: Optional dictionary of HTTP headers to include in the request.
|
||||
:type headers: dict[str, str] | None
|
||||
:param data: Optional dictionary representing form-encoded data to be sent in the
|
||||
request body.
|
||||
:type data: dict[str, Any] | None
|
||||
:param json: Optional dictionary representing JSON data to be serialized and sent in
|
||||
the request body.
|
||||
:type json: dict[str, Any] | None
|
||||
:param timeout: The maximum amount of time, in seconds, to wait for the request to
|
||||
be completed. Uses a default timeout value if unspecified.
|
||||
:type timeout: int
|
||||
:return: The HTTP response received after the POST request is completed.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
return http_request("POST", url, headers=headers, data=data, json=json, timeout=timeout)
|
||||
|
||||
+31
-4
@@ -6,21 +6,48 @@ from PySide6.QtWidgets import QApplication
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
def get_internal_dir() -> Path:
|
||||
# Retourne le chemin vers les ressources figées à l'intérieur de l'EXE (_MEIPASS).
|
||||
# En mode script, retourne le dossier du fichier .py.
|
||||
"""
|
||||
Returns the internal directory path for the application.
|
||||
|
||||
This function determines the internal directory path depending on whether the
|
||||
application is running in a frozen state (e.g., bundled by tools like PyInstaller) or
|
||||
in a standard Python environment. In a frozen state, it retrieves the path from the
|
||||
frozen application's temporary directory. Otherwise, it computes the directory path
|
||||
from the current file's location.
|
||||
|
||||
:return: The resolved internal directory path for the application.
|
||||
:rtype: Path
|
||||
"""
|
||||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||
return Path(sys._MEIPASS).resolve()
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
def get_executable_dir() -> Path:
|
||||
# Retourne le chemin du dossier contenant réellement le fichier .exe.
|
||||
# C'est ici que se trouve votre 'config.json'.
|
||||
"""
|
||||
Determines the directory of the current executable or script.
|
||||
|
||||
This function identifies the directory where the running executable or script
|
||||
is located. If the program is frozen (e.g., bundled into an executable using
|
||||
tools like PyInstaller), it returns the directory of the executable. Otherwise,
|
||||
it resolves the parent directory of the script two levels higher.
|
||||
|
||||
:return: The directory path of the executable or script.
|
||||
:rtype: Path
|
||||
"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# sys.executable est le chemin complet vers l'application .exe
|
||||
return Path(sys.executable).parent.resolve()
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
def quit_application(exit_code: int = 0) -> None:
|
||||
"""
|
||||
Terminates the application by closing all windows, exiting the QApplication instance
|
||||
(if it exists), and finally calling sys.exit with the provided exit code.
|
||||
|
||||
:param exit_code: Integer specifying the exit code to terminate the application.
|
||||
Default is 0.
|
||||
:return: None
|
||||
"""
|
||||
app = QApplication.instance()
|
||||
if app is not None:
|
||||
app.closeAllWindows()
|
||||
|
||||
@@ -5,6 +5,20 @@ from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout,
|
||||
|
||||
|
||||
class CustomMessageBox(QDialog):
|
||||
"""
|
||||
A custom message box dialog class for displaying notifications with configurable title, message,
|
||||
icon type, and button layout. The class provides a visually enhanced, frameless message box with
|
||||
fade-in animation and supports drag movement by clicking on the title bar or any non-button area.
|
||||
|
||||
:ivar INFO: Constant representing the informational message type.
|
||||
:type INFO: str
|
||||
:ivar WARNING: Constant representing the warning message type.
|
||||
:type WARNING: str
|
||||
:ivar OK: Constant representing the "OK" button configuration.
|
||||
:type OK: str
|
||||
:ivar OK_CANCEL: Constant representing the "OK" and "Cancel" button configuration.
|
||||
:type OK_CANCEL: str
|
||||
"""
|
||||
# Enums pour la configuration
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
@@ -114,20 +128,58 @@ class CustomMessageBox(QDialog):
|
||||
self.old_pos = None
|
||||
|
||||
def showEvent(self, event):
|
||||
"""
|
||||
Handles the show event and triggers the fade animation start.
|
||||
|
||||
:param event: The show event that occurs when the widget is shown. It is
|
||||
passed to the method automatically as an argument.
|
||||
:type event: QShowEvent
|
||||
|
||||
:return: None
|
||||
"""
|
||||
super().showEvent(event)
|
||||
self.fade_anim.start()
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
"""
|
||||
Handles the mouse press event for the widget. Enables movement
|
||||
of the widget only when the left mouse button is clicked and
|
||||
the click occurs on the title bar or anywhere except on buttons.
|
||||
|
||||
:param e: The mouse event object containing information about the
|
||||
mouse press, such as the clicked button and position.
|
||||
Expected to be an instance of QMouseEvent.
|
||||
"""
|
||||
# On permet le déplacement uniquement si on clique sur la barre de titre
|
||||
# ou n'importe où sauf sur les boutons
|
||||
if e.button() == Qt.LeftButton:
|
||||
self.old_pos = e.globalPosition().toPoint()
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
"""
|
||||
Handles the mouse move event, allowing the object to move along with the mouse drag.
|
||||
|
||||
This method calculates the difference between the current global position of the mouse
|
||||
and the previously recorded position, then adjusts the position of the object
|
||||
accordingly. It updates the stored position to the current global position at the end
|
||||
of the move.
|
||||
|
||||
:param e: The mouse event containing the current position of the mouse.
|
||||
:type e: QMouseEvent
|
||||
"""
|
||||
if self.old_pos:
|
||||
delta = e.globalPosition().toPoint() - self.old_pos
|
||||
self.move(self.x() + delta.x(), self.y() + delta.y())
|
||||
self.old_pos = e.globalPosition().toPoint()
|
||||
|
||||
def mouseReleaseEvent(self, e):
|
||||
"""
|
||||
Handles the mouse release event for the associated widget.
|
||||
|
||||
This method is triggered when a mouse button is released while interacting
|
||||
with the widget. It resets any stored state related to the mouse press.
|
||||
|
||||
:param e: The mouse release event received.
|
||||
:type e: QMouseEvent
|
||||
"""
|
||||
self.old_pos = None
|
||||
|
||||
@@ -4,6 +4,20 @@ from ui.custom_message_box import CustomMessageBox
|
||||
|
||||
|
||||
def show_qt_error(parent: QWidget | None, title: str, message: str) -> None:
|
||||
"""
|
||||
Displays an error message dialog with a given title and message. The dialog is
|
||||
displayed as a warning type with an "OK" button for user acknowledgment.
|
||||
|
||||
:param parent: The parent QWidget to associate the error dialog with. Can be set
|
||||
to None to display the dialog without an explicit parent.
|
||||
:type parent: QWidget | None
|
||||
:param title: The title to display in the error dialog window.
|
||||
:type title: str
|
||||
:param message: The message content to display in the error dialog.
|
||||
:type message: str
|
||||
:return: This function does not return a value.
|
||||
:rtype: None
|
||||
"""
|
||||
msg = CustomMessageBox(
|
||||
title=title,
|
||||
message=message,
|
||||
|
||||
+262
-9
@@ -29,6 +29,31 @@ 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.
|
||||
|
||||
:ivar ui: The main UI object for managing visual components and layouts.
|
||||
:type ui: Ui_MainWindow
|
||||
:ivar config: Configuration manager instance for handling application settings.
|
||||
:type config: ConfigManager
|
||||
: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]
|
||||
: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):
|
||||
@@ -146,6 +171,13 @@ class MainWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.ui.close_btn.clicked.connect(self.close)
|
||||
self.ui.minimize_btn.clicked.connect(self.showMinimized)
|
||||
self.ui.connexion_btn.clicked.connect(self._on_connexion)
|
||||
@@ -155,6 +187,13 @@ class MainWindow(QMainWindow):
|
||||
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())
|
||||
@@ -169,8 +208,15 @@ class MainWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _on_connexion(self) -> None:
|
||||
"""Appelée lors du clic sur le bouton connexion."""
|
||||
"""
|
||||
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.
|
||||
|
||||
: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.")
|
||||
@@ -195,13 +241,38 @@ class MainWindow(QMainWindow):
|
||||
|
||||
@staticmethod
|
||||
def _on_discord() -> None:
|
||||
"""
|
||||
Opens the Discord URL in the default web browser.
|
||||
|
||||
This method is a static utility function that launches the Discord URL defined
|
||||
in the Urls enumeration. It uses the `webbrowser.open` method to handle the
|
||||
opening process.
|
||||
|
||||
:return: Nothing is returned
|
||||
:rtype: None
|
||||
"""
|
||||
webbrowser.open(Urls.DISCORD.value)
|
||||
|
||||
def _on_intranet(self) -> None:
|
||||
"""
|
||||
Opens the intranet URL using the default web browser and starts the glow process.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
webbrowser.open(Urls.INTRANET.value)
|
||||
self._glow.start()
|
||||
|
||||
def _on_discord_auth_btn(self) -> None:
|
||||
"""
|
||||
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()
|
||||
@@ -231,7 +302,22 @@ class MainWindow(QMainWindow):
|
||||
show_qt_error(self, "Erreur", f"Une erreur inattendue est survenue : {exc}")
|
||||
|
||||
def _on_discord_auth_finished(self, success: bool, session_id: str, error_message: str):
|
||||
"""Callback après l'enregistrement suite à une auth Discord."""
|
||||
"""
|
||||
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()
|
||||
self.ui.discord_auth_btn.setEnabled(True)
|
||||
|
||||
@@ -258,18 +344,49 @@ class MainWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||
# On délègue au dragger
|
||||
"""
|
||||
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)
|
||||
@@ -279,6 +396,18 @@ class MainWindow(QMainWindow):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
"""
|
||||
Handles the close event of the application.
|
||||
|
||||
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
|
||||
@@ -287,7 +416,15 @@ class MainWindow(QMainWindow):
|
||||
super().closeEvent(event)
|
||||
|
||||
def cleanup(self):
|
||||
"""À appeler lors de la fermeture de la fenêtre principale"""
|
||||
"""
|
||||
Stops all active timers and performs cleanup operations.
|
||||
|
||||
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.
|
||||
"""
|
||||
if self.close_timer:
|
||||
self.close_timer.stop()
|
||||
|
||||
@@ -315,7 +452,17 @@ class MainWindow(QMainWindow):
|
||||
# Schedule de fermeture du launcher
|
||||
# ------------------------------------------------------------------
|
||||
def schedule_close(self, delay_ms: int = 60000):
|
||||
"""Lance ou redémarre le compte à rebours de fermeture."""
|
||||
"""
|
||||
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.
|
||||
|
||||
:param delay_ms: The delay in milliseconds after which the process should
|
||||
be closed. Defaults to 60000 milliseconds (1 minute) if not specified
|
||||
or an invalid value is provided.
|
||||
: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
|
||||
@@ -335,7 +482,17 @@ class MainWindow(QMainWindow):
|
||||
self._update_countdown_display()
|
||||
|
||||
def _update_countdown(self):
|
||||
"""Appelée toutes les secondes par le countdown_timer."""
|
||||
"""
|
||||
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:
|
||||
@@ -345,7 +502,13 @@ class MainWindow(QMainWindow):
|
||||
self._update_countdown_display()
|
||||
|
||||
def _update_countdown_display(self):
|
||||
"""Met à jour le texte dans l'interface."""
|
||||
"""
|
||||
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}")
|
||||
@@ -354,6 +517,17 @@ class MainWindow(QMainWindow):
|
||||
# Queue managment
|
||||
# ------------------------------------------------------------------
|
||||
def start_queue(self):
|
||||
"""
|
||||
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.
|
||||
|
||||
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.update.connect(self.handle_update)
|
||||
self.queue_thread.start()
|
||||
@@ -362,6 +536,17 @@ class MainWindow(QMainWindow):
|
||||
# self.handle_update("position:3:10")
|
||||
|
||||
def handle_update(self, message: str):
|
||||
"""
|
||||
Handles updates based on a given message and performs relevant UI actions.
|
||||
|
||||
: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.
|
||||
:return: None
|
||||
"""
|
||||
if message == "ok":
|
||||
self.ui.queue_lbl.setVisible(True)
|
||||
self.ui.queue_position.setVisible(False)
|
||||
@@ -385,6 +570,26 @@ class MainWindow(QMainWindow):
|
||||
pass
|
||||
|
||||
def _ensure_server_session(self) -> bool:
|
||||
"""
|
||||
Ensures the establishment of a server session, handling authentication
|
||||
and session registration for a Discord user.
|
||||
|
||||
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.
|
||||
:rtype: bool
|
||||
"""
|
||||
if not self.stored_user_id or self.stored_user_id.isspace():
|
||||
return False
|
||||
|
||||
@@ -417,7 +622,16 @@ class MainWindow(QMainWindow):
|
||||
return False
|
||||
|
||||
def _on_auth_finished(self, success: bool, session_id: str, error_message: str):
|
||||
"""Appelée quand le AuthWorker a terminé."""
|
||||
"""
|
||||
Handles the completion of the authentication process by updating the UI,
|
||||
cleaning up the worker, and proceeding with subsequent logic based on the
|
||||
authentication result.
|
||||
|
||||
:param success: A boolean indicating whether the authentication was successful.
|
||||
:param session_id: The session identifier returned upon successful authentication.
|
||||
:param error_message: A descriptive error message in case authentication failed.
|
||||
:return: None
|
||||
"""
|
||||
# 1. Restauration de l'UI
|
||||
QGuiApplication.restoreOverrideCursor()
|
||||
self.ui.connexion_btn.setEnabled(True)
|
||||
@@ -439,7 +653,19 @@ class MainWindow(QMainWindow):
|
||||
f"Impossible de se connecter au serveur.\n\n{error_message}")
|
||||
|
||||
def _proceed_to_queue_or_launch(self):
|
||||
"""Continuation de la logique après une session valide."""
|
||||
"""
|
||||
Handles the process of either starting the queue, updating the user's
|
||||
position in the queue, or launching the FiveM application if all conditions
|
||||
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.
|
||||
|
||||
: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:
|
||||
@@ -460,6 +686,16 @@ class MainWindow(QMainWindow):
|
||||
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
|
||||
@@ -470,7 +706,24 @@ class QueueThread(QThread):
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
+8
-1
@@ -2,7 +2,14 @@ import ctypes
|
||||
from sys import platform
|
||||
|
||||
def enable_blur_behind(hwnd: int) -> None:
|
||||
"""Force DWM à activer la composition derrière la fenêtre."""
|
||||
"""
|
||||
Enable the blur-behind effect on the specified window handle (HWND). This function enables
|
||||
the glass/blur effect on supported Windows environments, enhancing the UI appearance.
|
||||
|
||||
:param hwnd: The handle of the window (HWND) where the blur-behind effect will be applied.
|
||||
:type hwnd: int
|
||||
:return: None
|
||||
"""
|
||||
if not platform.startswith('win'):
|
||||
return
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user