Init
This commit is contained in:
171
main.py
171
main.py
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user