This commit is contained in:
2025-04-19 14:51:11 +02:00
parent 16520043a9
commit 6a70895243
3 changed files with 276 additions and 21 deletions

171
main.py
View File

@@ -1,4 +1,5 @@
from PySide6.QtCore import QStandardPaths, QDataStream, QByteArray, QIODevice, Signal, Qt, QTimer, QCryptographicHash from PySide6.QtCore import QStandardPaths, QDataStream, QByteArray, QIODevice, Signal, Qt, QTimer, QCryptographicHash
from PySide6.QtGui import QPalette, QColor
from PySide6.QtNetwork import QLocalServer, QLocalSocket from PySide6.QtNetwork import QLocalServer, QLocalSocket
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
@@ -12,117 +13,230 @@ import hashlib
import random import random
import string import string
import base64 import base64
import logging
from pathlib import Path from pathlib import Path
from src.logs import configure_logging from src.logs import configure_logging
from windows.main_window import MainWindow from windows.main_window import MainWindow
def apply_dark_theme(app):
app.setStyle("Fusion")
dark_palette = QPalette()
# Utilisation de gris plus clairs pour les principaux éléments
dark_palette.setColor(QPalette.ColorRole.Window, QColor(90, 90, 95)) # Barre de titre plus claire
dark_palette.setColor(QPalette.ColorRole.WindowText, QColor(255, 255, 255))
dark_palette.setColor(QPalette.ColorRole.Base, QColor(60, 60, 65)) # Zone de texte plus claire
dark_palette.setColor(QPalette.ColorRole.AlternateBase, QColor(80, 80, 85))
dark_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(70, 70, 75))
dark_palette.setColor(QPalette.ColorRole.ToolTipText, QColor(255, 255, 255))
dark_palette.setColor(QPalette.ColorRole.Text, QColor(255, 255, 255))
dark_palette.setColor(QPalette.ColorRole.Button, QColor(75, 75, 80)) # Boutons plus clairs
dark_palette.setColor(QPalette.ColorRole.ButtonText, QColor(255, 255, 255))
dark_palette.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
# Les autres paramètres restent inchangés
dark_palette.setColor(QPalette.ColorRole.Highlight, QColor(61, 142, 201))
dark_palette.setColor(QPalette.ColorRole.HighlightedText, QColor(255, 255, 255))
dark_palette.setColor(QPalette.ColorRole.Link, QColor(77, 166, 230))
dark_palette.setColor(QPalette.ColorRole.LinkVisited, QColor(120, 120, 250))
dark_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor(150, 150, 150))
dark_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor(150, 150, 150))
dark_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor(150, 150, 150))
app.setPalette(dark_palette)
class SingleApplication(QApplication): class SingleApplication(QApplication):
# Signal émis lorsque des fichiers sont reçus d'une instance secondaire # Signal émis lorsque des fichiers sont reçus d'une instance secondaire
files_received = Signal(list) files_received = Signal(list)
def __init__(self, app_id, args): def __init__(self, app_id, args):
super().__init__(args) super().__init__(args)
# Configuration du logger
self.logger = logging.getLogger(__name__)
self.logger.debug("Initialisation de SingleApplication")
self.app_id = app_id self.app_id = app_id
self.logger.debug(f"ID de l'application: {app_id}")
self.shared_key = hashlib.sha256(app_id.encode()).hexdigest()[:16] self.shared_key = hashlib.sha256(app_id.encode()).hexdigest()[:16]
self.logger.debug(f"Clé partagée générée: {self.shared_key}")
self.server = None self.server = None
self.is_primary_instance = self.try_connect_to_primary() self.is_primary_instance = self.try_connect_to_primary()
if self.is_primary_instance: if self.is_primary_instance:
# C'est la première instance, on crée un serveur local # C'est la première instance, on crée un serveur local
self.logger.info("Instance primaire détectée, création du serveur local")
self.server = QLocalServer() self.server = QLocalServer()
self.server.newConnection.connect(self.handle_new_connection) self.server.newConnection.connect(self.handle_new_connection)
self.logger.debug("Signal newConnection connecté")
if not self.server.listen(self.app_id): if not self.server.listen(self.app_id):
self.logger.warning(f"Échec de l'écoute sur {self.app_id}, tentative de suppression du serveur existant")
# En cas d'erreur (serveur déjà existant mais zombie), on le supprime et on réessaie # En cas d'erreur (serveur déjà existant mais zombie), on le supprime et on réessaie
QLocalServer.removeServer(self.app_id) QLocalServer.removeServer(self.app_id)
self.server.listen(self.app_id) if self.server.listen(self.app_id):
self.logger.info(f"Serveur local créé avec succès après suppression de l'ancien")
else:
self.logger.error(f"Impossible de créer le serveur local même après suppression")
else:
self.logger.info(f"Serveur local créé avec succès")
else: else:
self.logger.info("Instance secondaire détectée, fermeture de l'application")
QTimer.singleShot(0, self.quit) QTimer.singleShot(0, self.quit)
def encrypt_data(self, data_str): def encrypt_data(self, data_str):
"""Méthode simple pour brouiller les données""" """
Méthode simple pour brouiller les données
Args:
data_str (str): Données à chiffrer
Returns:
str: Données chiffrées en base64
"""
self.logger.debug(f"Chiffrement des données (longueur: {len(data_str)})")
# Générer une "nonce" aléatoire pour éviter que les mêmes données produisent le même résultat # Générer une "nonce" aléatoire pour éviter que les mêmes données produisent le même résultat
nonce = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8)) nonce = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
self.logger.debug(f"Nonce générée: {nonce}")
# Combiner la nonce, la clé et les données # Combiner la nonce, la clé et les données
combined = nonce + self.shared_key + data_str combined = nonce + self.shared_key + data_str
self.logger.debug("Données combinées avec nonce et clé partagée")
# Utiliser SHA-256 pour obtenir un hash # Utiliser SHA-256 pour obtenir un hash
hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256) hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256)
hash_obj.addData(combined.encode()) hash_obj.addData(combined.encode())
signature = hash_obj.result().toHex().data().decode()[:16] signature = hash_obj.result().toHex().data().decode()[:16]
self.logger.debug(f"Signature générée: {signature}")
# Encoder le tout en base64 # Encoder le tout en base64
encoded = base64.b64encode((nonce + signature + data_str).encode()).decode() encoded = base64.b64encode((nonce + signature + data_str).encode()).decode()
self.logger.debug(f"Données encodées en base64 (longueur: {len(encoded)})")
return encoded return encoded
def decrypt_data(self, encoded_str): def decrypt_data(self, encoded_str):
"""Déchiffre les données et vérifie leur intégrité""" """
Déchiffre les données et vérifie leur intégrité
Args:
encoded_str (str): Données chiffrées en base64
Returns:
str ou None: Données déchiffrées ou None en cas d'erreur
"""
self.logger.debug(f"Déchiffrement des données (longueur: {len(encoded_str)})")
try: try:
# Décoder de base64 # Décoder de base64
decoded = base64.b64decode(encoded_str.encode()).decode() decoded = base64.b64decode(encoded_str.encode()).decode()
self.logger.debug("Données décodées de base64")
# Extraire nonce, signature et données # Extraire nonce, signature et données
nonce = decoded[:8] nonce = decoded[:8]
signature = decoded[8:24] signature = decoded[8:24]
data_str = decoded[24:] data_str = decoded[24:]
self.logger.debug(f"Nonce extraite: {nonce}, signature: {signature}")
# Vérifier la signature # Vérifier la signature
combined = nonce + self.shared_key + data_str combined = nonce + self.shared_key + data_str
hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256) hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256)
hash_obj.addData(combined.encode()) hash_obj.addData(combined.encode())
expected_signature = hash_obj.result().toHex().data().decode()[:16] expected_signature = hash_obj.result().toHex().data().decode()[:16]
self.logger.debug(f"Signature attendue: {expected_signature}")
if signature != expected_signature: if signature != expected_signature:
print("Signature invalide, données potentiellement corrompues ou falsifiées") self.logger.warning("Signature invalide, données potentiellement corrompues ou falsifiées")
return None return None
self.logger.debug(f"Données déchiffrées avec succès (longueur: {len(data_str)})")
return data_str return data_str
except Exception as e: except Exception as e:
print(f"Erreur lors du déchiffrement: {e}") self.logger.error(f"Erreur lors du déchiffrement: {e}", exc_info=True)
return None return None
def try_connect_to_primary(self): def try_connect_to_primary(self):
"""Essaie de se connecter à l'instance primaire de l'application""" """
Essaie de se connecter à l'instance primaire de l'application
Returns:
bool: True si c'est l'instance primaire, False sinon
"""
self.logger.debug(f"Tentative de connexion à l'instance primaire: {self.app_id}")
socket = QLocalSocket() socket = QLocalSocket()
socket.connectToServer(self.app_id, QIODevice.OpenModeFlag.WriteOnly) socket.connectToServer(self.app_id, QIODevice.OpenModeFlag.WriteOnly)
if socket.waitForConnected(500): if socket.waitForConnected(500):
self.logger.info("Connexion établie avec l'instance primaire")
# Récupérer les arguments pour les envoyer à l'instance primaire # Récupérer les arguments pour les envoyer à l'instance primaire
args = sys.argv[1:] if len(sys.argv) > 1 else [] args = sys.argv[1:] if len(sys.argv) > 1 else []
self.logger.debug(f"Arguments à transmettre: {args}")
encrypt_args = self.encrypt_data(";".join(args)) encrypt_args = self.encrypt_data(";".join(args))
self.logger.debug("Arguments chiffrés pour transmission")
# Envoyer les arguments à l'instance primaire # Envoyer les arguments à l'instance primaire
stream = QDataStream(socket) stream = QDataStream(socket)
stream.writeQString(encrypt_args) stream.writeQString(encrypt_args)
socket.flush() socket.flush()
socket.waitForBytesWritten(1000) self.logger.debug("Données envoyées à l'instance primaire")
if socket.waitForBytesWritten(1000):
self.logger.debug("Données écrites avec succès")
else:
self.logger.warning("Délai d'attente dépassé pour l'écriture des données")
socket.disconnectFromServer() socket.disconnectFromServer()
self.logger.debug("Déconnexion du serveur")
QTimer.singleShot(0, self.quit) QTimer.singleShot(0, self.quit)
self.logger.info("Instance secondaire, programmation de la fermeture")
return False # Ce n'est pas l'instance primaire return False # Ce n'est pas l'instance primaire
self.logger.info("Aucune instance primaire trouvée, devenant l'instance primaire")
return True # C'est l'instance primaire return True # C'est l'instance primaire
def handle_new_connection(self): def handle_new_connection(self):
"""Gère une nouvelle connexion d'une instance secondaire""" """
Gère une nouvelle connexion d'une instance secondaire
"""
self.logger.info("Nouvelle connexion d'une instance secondaire reçue")
socket = self.server.nextPendingConnection() socket = self.server.nextPendingConnection()
if socket.waitForReadyRead(2000): if socket.waitForReadyRead(2000):
self.logger.debug("Données disponibles pour lecture")
stream = QDataStream(socket) stream = QDataStream(socket)
encrypted_args = stream.readQString() encrypted_args = stream.readQString()
args_str = self.decrypt_data(encrypted_args) self.logger.debug(f"Arguments chiffrés reçus (longueur: {len(encrypted_args)})")
# Émettre un signal pour informer l'application des fichiers à ouvrir args_str = self.decrypt_data(encrypted_args)
if args_str: if args_str:
self.logger.debug(f"Arguments déchiffrés: {args_str}")
# Émettre un signal pour informer l'application des fichiers à ouvrir
args = args_str.split(";") if args_str else [] args = args_str.split(";") if args_str else []
if args: if args:
self.logger.info(f"Émission du signal files_received avec {len(args)} arguments")
self.files_received.emit(args) self.files_received.emit(args)
else:
self.logger.debug("Aucun argument à traiter")
else:
self.logger.warning("Échec du déchiffrement des arguments")
else:
self.logger.warning("Délai d'attente dépassé pour la lecture des données")
socket.disconnectFromServer() socket.disconnectFromServer()
self.logger.debug("Déconnexion du client")
if __name__ == "__main__": if __name__ == "__main__":
@@ -135,28 +249,59 @@ if __name__ == "__main__":
# Configurer le logging en fonction du mode # Configurer le logging en fonction du mode
configure_logging(args.dev) configure_logging(args.dev)
# Créer un logger pour le module principal
logger = logging.getLogger(__name__)
logger.info("Démarrage de l'application")
# Configuration des variables d'environnement pour QtWebEngine
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-gpu-rasterization --ignore-gpu-blocklist" os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-gpu-rasterization --ignore-gpu-blocklist"
logger.debug("Flags Chromium configurés pour l'accélération GPU")
if args.dev: if args.dev:
os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "4000" os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "4000"
logger.info("Mode développement activé avec débogage distant sur le port 4000")
# Création de l'application
app_id = "OxAPP25" app_id = "OxAPP25"
logger.debug(f"ID de l'application: {app_id}")
app = SingleApplication(app_id, sys.argv) app = SingleApplication(app_id, sys.argv)
apply_dark_theme(app)
logger.debug("Instance SingleApplication créée")
if not app.is_primary_instance: if not app.is_primary_instance:
logger.info("Instance secondaire détectée, fermeture de l'application")
sys.exit(0) sys.exit(0)
# Configuration de la boucle d'événements asyncio
logger.debug("Configuration de la boucle d'événements asyncio")
event_loop = qasync.QEventLoop(app) event_loop = qasync.QEventLoop(app)
asyncio.set_event_loop(event_loop) asyncio.set_event_loop(event_loop)
app_close_event = asyncio.Event() app_close_event = asyncio.Event()
app.aboutToQuit.connect(app_close_event.set) app.aboutToQuit.connect(app_close_event.set)
logger.debug("Signal aboutToQuit connecté à l'événement de fermeture")
# Création de la fenêtre principale
logger.info("Création de la fenêtre principale")
window = MainWindow() window = MainWindow()
# Connecter le signal de fichiers reçus à une méthode de traitement # Connecter le signal de fichiers reçus à une méthode de traitement
app.files_received.connect(window.handle_files) app.files_received.connect(window.handle_files)
if args.files: logger.debug("Signal files_received connecté")
window.handle_files(args.files)
window.show()
# Traitement des fichiers passés en arguments
if args.files:
logger.info(f"Traitement de {len(args.files)} fichiers passés en arguments")
window.handle_files(args.files)
# Affichage de la fenêtre
window.show()
logger.info("Fenêtre principale affichée")
# Exécution de la boucle d'événements
logger.debug("Démarrage de la boucle d'événements")
with event_loop: with event_loop:
event_loop.run_until_complete(app_close_event.wait()) event_loop.run_until_complete(app_close_event.wait())
logger.info("Application terminée")

View File

@@ -1,4 +1,5 @@
import asyncio import asyncio
import logging
from PySide6.QtCore import QObject, Slot, Signal, QTimer, Property from PySide6.QtCore import QObject, Slot, Signal, QTimer, Property
@@ -16,78 +17,125 @@ class WebHandler(QObject):
def __init__(self, download_manager, parent=None): def __init__(self, download_manager, parent=None):
super().__init__(parent) super().__init__(parent)
# Configuration du logger
self.logger = logging.getLogger(__name__)
self.logger.debug("Initialisation de WebHandler")
self.download_manager: "DownloadManager" = download_manager self.download_manager: "DownloadManager" = download_manager
self.conf = self.download_manager.conf self.conf = self.download_manager.conf
self.logger.debug("Références au gestionnaire de téléchargement et à la configuration établies")
self.site_loaded = False self.site_loaded = False
# Connexion des signaux du gestionnaire de téléchargement
self.download_manager.status_updated.connect(lambda data: self.on_message.emit({ self.download_manager.status_updated.connect(lambda data: self.on_message.emit({
"context": "status_updated", "context": "status_updated",
"content": data "content": data
})) }))
self.logger.debug("Signal status_updated connecté")
# self.download_manager.stats_updated.connect(lambda data: self.on_message.emit({ # self.download_manager.stats_updated.connect(lambda data: self.on_message.emit({
# "context": "stats_updated", # "context": "stats_updated",
# "content": data # "content": data
# })) # }))
# self.logger.debug("Signal stats_updated connecté")
self.download_manager.files_updated.connect(lambda: self.on_message.emit({ self.download_manager.files_updated.connect(lambda: self.on_message.emit({
"context": "files_updated", "context": "files_updated",
"content": {file_id: vars(file) for file_id, file in self.download_manager.files.items()} "content": {file_id: vars(file) for file_id, file in self.download_manager.files.items()}
})) }))
self.logger.debug("Signal files_updated connecté")
@Property(dict) @Property(dict)
def dm_status(self): def dm_status(self):
self.logger.debug("Accès à la propriété dm_status")
return self.download_manager.status return self.download_manager.status
@Property(dict) @Property(dict)
def dm_files(self): def dm_files(self):
self.logger.debug("Accès à la propriété dm_files")
return {file_id: vars(file) for file_id, file in self.download_manager.files.items()} return {file_id: vars(file) for file_id, file in self.download_manager.files.items()}
# @Property(dict) # @Property(dict)
# def dm_stats(self): # def dm_stats(self):
# self.logger.debug("Accès à la propriété dm_stats")
# return self.download_manager.dl_stats # return self.download_manager.dl_stats
@Property(str) @Property(str)
def dm_download_location(self): def dm_download_location(self):
return self.conf.get_value("download_location", "") self.logger.debug("Accès à la propriété dm_download_location")
location = self.conf.get_value("download_location", "")
return location
@Slot(result=str) @Slot(result=str)
def on_site_ready(self): def on_site_ready(self):
"""Appelé lorsque le site est prêt à interagir"""
self.logger.info("Site signalé comme prêt")
self.site_loaded = True self.site_loaded = True
self.site_ready.emit() self.site_ready.emit()
self.logger.debug("Signal site_ready émis")
@Slot(bool) @Slot(bool)
def set_pause(self, value): def set_pause(self, value):
"""Définit l'état de pause du gestionnaire de téléchargement"""
self.logger.info(f"Demande de changement d'état de pause: {value}")
asyncio.create_task(self.download_manager.set_pause(value)) asyncio.create_task(self.download_manager.set_pause(value))
self.logger.debug("Tâche asynchrone de changement d'état créée")
@Slot(list) @Slot(list)
@Slot(str) @Slot(str)
def add_files(self, files): def add_files(self, files):
"""Ajoute des fichiers au gestionnaire de téléchargement"""
self.logger.info(f"Demande d'ajout de fichiers reçue: type={type(files)}")
if isinstance(files, str): if isinstance(files, str):
self.logger.debug("Conversion de la chaîne JSON en liste")
files = json.loads(files) files = json.loads(files)
if not isinstance(files, list): if not isinstance(files, list):
self.logger.debug("Conversion de l'élément unique en liste")
files = [files] files = [files]
self.logger.info(f"Ajout de {len(files)} fichier(s) au gestionnaire de téléchargement")
self.download_manager.add_files(files) self.download_manager.add_files(files)
@Slot(list) @Slot(list)
def del_files(self, file_ids): def del_files(self, file_ids):
"""Supprime des fichiers du gestionnaire de téléchargement"""
self.logger.info(f"Demande de suppression de fichiers reçue: {file_ids}")
if isinstance(file_ids, str): if isinstance(file_ids, str):
self.logger.debug("Conversion de l'identifiant unique en liste")
file_ids = [file_ids] file_ids = [file_ids]
self.logger.info(f"Suppression de {len(file_ids)} fichier(s) du gestionnaire de téléchargement")
self.download_manager.del_files(file_ids) self.download_manager.del_files(file_ids)
@Slot() @Slot()
def change_path(self): def change_path(self):
"""Ouvre une boîte de dialogue pour changer le dossier de destination des téléchargements"""
self.logger.info("Ouverture de la boîte de dialogue de sélection de dossier")
# Récupérer le chemin actuel pour l'utiliser comme point de départ
current_path = self.conf.get_value("download_location", "")
self.logger.debug(f"Chemin actuel: {current_path}")
# Ouvrir la boîte de dialogue pour sélectionner un dossier # Ouvrir la boîte de dialogue pour sélectionner un dossier
new_path = QFileDialog.getExistingDirectory( new_path = QFileDialog.getExistingDirectory(
self.parent(), self.parent(),
"Sélectionner un dossier de destination", "Sélectionner un dossier de destination",
self.conf.get_value("download_location", "") current_path
) )
# Vérifier si l'utilisateur a bien sélectionné un dossier (et n'a pas annulé) # Vérifier si l'utilisateur a bien sélectionné un dossier (et n'a pas annulé)
if new_path: if new_path:
self.logger.info(f"Nouveau dossier sélectionné: {new_path}")
# Mettre à jour le chemin de téléchargement dans la configuration # Mettre à jour le chemin de téléchargement dans la configuration
self.conf.set_value("download_location", new_path) self.conf.set_value("download_location", new_path)
self.logger.debug("Chemin de téléchargement mis à jour dans la configuration")
# Sauvegarder la configuration si nécessaire # Sauvegarder la configuration si nécessaire
# self.download_manager.save_config() # Décommentez si vous avez une méthode pour sauvegarder la configuration # self.download_manager.save_config() # Décommentez si vous avez une méthode pour sauvegarder la configuration
@@ -96,3 +144,6 @@ class WebHandler(QObject):
"context": "download_location_updated", "context": "download_location_updated",
"content": new_path "content": new_path
}) })
self.logger.debug("Signal de mise à jour du chemin de téléchargement émis")
else:
self.logger.debug("Sélection de dossier annulée par l'utilisateur")

View File

@@ -1,6 +1,7 @@
import os import os
from pathlib import Path from pathlib import Path
import sqlite3 import sqlite3
import logging
from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEngineProfile, QWebEnginePage, QWebEngineCookieStore from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEngineProfile, QWebEnginePage, QWebEngineCookieStore
from PySide6.QtWidgets import QWidget from PySide6.QtWidgets import QWidget
@@ -22,54 +23,83 @@ class SiteWindow(QWebEngineView):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
# Configuration du logger
self.logger = logging.getLogger(__name__)
self.logger.debug("Initialisation de SiteWindow")
self.conf = parent.conf self.conf = parent.conf
self.web_handler = parent.web_handler self.web_handler = parent.web_handler
self.page().profile().cookieStore().deleteAllCookies() self.page().profile().cookieStore().deleteAllCookies()
self.logger.debug("Cookies supprimés du profil par défaut")
storage_path = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation)) storage_path = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation))
self.logger.debug(f"Chemin de stockage des données: {storage_path}")
if not storage_path.exists(): if not storage_path.exists():
storage_path.mkdir(parents=True) storage_path.mkdir(parents=True)
self.logger.debug(f"Création du répertoire de stockage: {storage_path}")
self.persistent_profile = QWebEngineProfile("OxAppProfile", parent=self) self.persistent_profile = QWebEngineProfile("OxAppProfile", parent=self)
self.logger.debug("Création du profil web persistant: OxAppProfile")
self.cookie_store = self.persistent_profile.cookieStore() self.cookie_store = self.persistent_profile.cookieStore()
self.cookie_store.cookieAdded.connect(self.on_cookie_added.emit) self.cookie_store.cookieAdded.connect(self.on_cookie_added.emit)
self.logger.debug("Signal cookieAdded connecté")
self.persistent_profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.MemoryHttpCache) self.persistent_profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.MemoryHttpCache)
self.persistent_profile.setPersistentStoragePath(str(self.conf.app_config_path / "web_cache")) self.logger.debug("Type de cache HTTP configuré: MemoryHttpCache")
cache_path = str(self.conf.app_config_path / "web_cache")
self.persistent_profile.setPersistentStoragePath(cache_path)
self.logger.debug(f"Chemin de stockage persistant configuré: {cache_path}")
self.persistent_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies) self.persistent_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies)
self.logger.debug("Politique de cookies persistants configurée: AllowPersistentCookies")
self.persistent_profile.setHttpUserAgent("oxapp25") self.persistent_profile.setHttpUserAgent("oxapp25")
self.logger.debug("User-Agent HTTP configuré: oxapp25")
custom_page = QWebEnginePage(self.persistent_profile, parent=self) custom_page = QWebEnginePage(self.persistent_profile, parent=self)
self.setPage(custom_page) self.setPage(custom_page)
self.logger.debug("Page web personnalisée créée et configurée")
# Configuration des attributs de la page web
self.settings().setAttribute(QWebEngineSettings.WebAttribute.Accelerated2dCanvasEnabled, True) self.settings().setAttribute(QWebEngineSettings.WebAttribute.Accelerated2dCanvasEnabled, True)
self.settings().setAttribute(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, True) self.settings().setAttribute(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, True)
self.settings().setAttribute(QWebEngineSettings.WebAttribute.WebGLEnabled, True) self.settings().setAttribute(QWebEngineSettings.WebAttribute.WebGLEnabled, True)
# self.settings().setAttribute(QWebEngineSettin gs.WebAttribute.LocalStorageEnabled, False) # self.settings().setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, False)
# self.settings().setAttribute(QWebEngineSettings.WebAttribute.AutoLoadImages, True) # self.settings().setAttribute(QWebEngineSettings.WebAttribute.AutoLoadImages, True)
# self.settings().setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, False) # self.settings().setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, False)
self.settings().setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, True) self.settings().setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, True)
self.page().setBackgroundColor(Qt.GlobalColor.white) self.page().setBackgroundColor(Qt.GlobalColor.white)
self.logger.debug("Attributs de la page web configurés")
self.web_channel = QWebChannel(self) self.web_channel = QWebChannel(self)
self.web_channel.registerObject("handler", self.web_handler) self.web_channel.registerObject("handler", self.web_handler)
self.page().setWebChannel(self.web_channel) self.page().setWebChannel(self.web_channel)
self.logger.debug("Canal web configuré avec le gestionnaire")
self.loadFinished.connect(self.on_load_finished) self.loadFinished.connect(self.on_load_finished)
self.logger.debug("Signal loadFinished connecté")
self.logger.info(f"Chargement de l'URL: {parent.url}")
self.load(parent.url) self.load(parent.url)
@Slot(bool) @Slot(bool)
def on_load_finished(self, is_success): def on_load_finished(self, is_success):
if is_success: if is_success:
self.logger.info("Page chargée avec succès")
# Injection de l'API WebChannel
api_file = QFile(":/qtwebchannel/qwebchannel.js") api_file = QFile(":/qtwebchannel/qwebchannel.js")
api_file.open(QIODevice.OpenModeFlag.ReadOnly) api_file.open(QIODevice.OpenModeFlag.ReadOnly)
api_content = api_file.readAll().data().decode("utf-8") api_content = api_file.readAll().data().decode("utf-8")
api_file.close() api_file.close()
self.page().runJavaScript(api_content) self.page().runJavaScript(api_content)
self.logger.debug("API WebChannel injectée dans la page")
# fix main scrollbar # Correction de la barre de défilement principale
css = """ css = """
html::-webkit-scrollbar, html::-webkit-scrollbar,
body::-webkit-scrollbar { body::-webkit-scrollbar {
@@ -83,13 +113,24 @@ class SiteWindow(QWebEngineView):
""" """
js_code = f"var style = document.createElement('style'); style.textContent = `{css}`; document.head.appendChild(style);" js_code = f"var style = document.createElement('style'); style.textContent = `{css}`; document.head.appendChild(style);"
self.page().runJavaScript(js_code) self.page().runJavaScript(js_code)
self.logger.debug("CSS personnalisé injecté pour masquer les barres de défilement")
# Recréation du cookie de session
self.recreate_sessionid_cookie() self.recreate_sessionid_cookie()
else:
self.logger.error("Échec du chargement de la page")
def sessionid_from_cookie_store(self) -> None|sqlite3.Row: def sessionid_from_cookie_store(self) -> None|sqlite3.Row:
# Cette approche est expérimentale et dépend de l'implémentation interne """
Récupère le cookie de session depuis le stockage de cookies.
Cette approche est expérimentale et dépend de l'implémentation interne.
Returns:
sqlite3.Row ou None: Les informations du cookie de session ou None si non trouvé
"""
profile_path = self.persistent_profile.persistentStoragePath() profile_path = self.persistent_profile.persistentStoragePath()
cookies_db_path = os.path.join(profile_path, "Cookies") cookies_db_path = os.path.join(profile_path, "Cookies")
self.logger.debug(f"Recherche du cookie de session dans: {cookies_db_path}")
if os.path.exists(cookies_db_path): if os.path.exists(cookies_db_path):
try: try:
@@ -101,21 +142,35 @@ class SiteWindow(QWebEngineView):
conn.close() conn.close()
if result: if result:
self.logger.debug("Cookie de session trouvé dans la base de données")
return result # La valeur du cookie return result # La valeur du cookie
else:
self.logger.debug("Aucun cookie de session trouvé dans la base de données")
except Exception as e: except Exception as e:
print(f"Erreur lors de l'accès au fichier de cookies: {e}") self.logger.error(f"Erreur lors de l'accès au fichier de cookies: {e}", exc_info=True)
else:
self.logger.debug(f"Fichier de cookies non trouvé: {cookies_db_path}")
return None return None
def recreate_sessionid_cookie(self): def recreate_sessionid_cookie(self):
"""
Recrée le cookie de session à partir des informations stockées dans la base de données.
"""
self.logger.debug("Tentative de recréation du cookie de session")
cookie_info = self.sessionid_from_cookie_store() cookie_info = self.sessionid_from_cookie_store()
if cookie_info is None: if cookie_info is None:
self.logger.debug("Aucune information de cookie de session disponible, abandon")
return return
# Créer un nouveau cookie avec le nom et la valeur # Créer un nouveau cookie avec le nom et la valeur
cookie = QNetworkCookie(b"sessionid", cookie_info['value'].encode()) cookie = QNetworkCookie(b"sessionid", cookie_info['value'].encode())
self.logger.debug(f"Création d'un nouveau cookie 'sessionid' avec la valeur récupérée")
# Configurer toutes les propriétés disponibles # Configurer toutes les propriétés disponibles
cookie.setDomain(cookie_info['host_key']) cookie.setDomain(cookie_info['host_key'])
cookie.setPath(cookie_info['path']) cookie.setPath(cookie_info['path'])
self.logger.debug(f"Configuration du domaine: {cookie_info['host_key']} et du chemin: {cookie_info['path']}")
# Gérer les dates d'expiration (conversion du format si nécessaire) # Gérer les dates d'expiration (conversion du format si nécessaire)
if cookie_info['has_expires']: if cookie_info['has_expires']:
@@ -124,10 +179,12 @@ class SiteWindow(QWebEngineView):
# Vous devrez peut-être ajuster cette conversion selon le format exact # Vous devrez peut-être ajuster cette conversion selon le format exact
expires = QDateTime.fromSecsSinceEpoch(int(cookie_info['expires_utc'] / 1000000) - 11644473600) expires = QDateTime.fromSecsSinceEpoch(int(cookie_info['expires_utc'] / 1000000) - 11644473600)
cookie.setExpirationDate(expires) cookie.setExpirationDate(expires)
self.logger.debug(f"Date d'expiration configurée: {expires.toString()}")
# Configurer les attributs de sécurité # Configurer les attributs de sécurité
cookie.setSecure(bool(cookie_info['is_secure'])) cookie.setSecure(bool(cookie_info['is_secure']))
cookie.setHttpOnly(bool(cookie_info['is_httponly'])) cookie.setHttpOnly(bool(cookie_info['is_httponly']))
self.logger.debug(f"Attributs de sécurité configurés - Secure: {bool(cookie_info['is_secure'])}, HttpOnly: {bool(cookie_info['is_httponly'])}")
# Configurer SameSite si disponible dans votre version de PySide6 # Configurer SameSite si disponible dans votre version de PySide6
# Vérifier si l'attribut est disponible (PySide6 ≥ 6.2.0) # Vérifier si l'attribut est disponible (PySide6 ≥ 6.2.0)
@@ -141,11 +198,13 @@ class SiteWindow(QWebEngineView):
} }
if samesite_value in samesite_map: if samesite_value in samesite_map:
cookie.setSameSitePolicy(samesite_map[samesite_value]) cookie.setSameSitePolicy(samesite_map[samesite_value])
self.logger.debug(f"Politique SameSite configurée: {samesite_map[samesite_value]}")
# Ajouter le cookie au store - cela va déclencher le signal cookieAdded # Ajouter le cookie au store - cela va déclencher le signal cookieAdded
self.cookie_store.setCookie(cookie) self.cookie_store.setCookie(cookie)
self.logger.info("Cookie de session recréé et ajouté au store")
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
# Ignorer l'événement pour empêcher l'affichage du menu contextuel # Ignorer l'événement pour empêcher l'affichage du menu contextuel
self.logger.debug("Menu contextuel désactivé")
event.ignore() event.ignore()