Init
This commit is contained in:
289
src/conf.py
Normal file
289
src/conf.py
Normal file
@@ -0,0 +1,289 @@
|
||||
from PySide6.QtCore import QObject, QStandardPaths, Signal
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
import datetime
|
||||
from copy import deepcopy
|
||||
import pickle
|
||||
|
||||
from src.datatypes import ConfType
|
||||
from src.utils import RestrictedUnpickler
|
||||
|
||||
|
||||
|
||||
class ConfManager(QObject):
|
||||
"""
|
||||
Gestionnaire de configuration avec persistance automatique via pickle sécurisé.
|
||||
Maintient un haut niveau de journalisation pour le suivi et le débogage.
|
||||
"""
|
||||
conf_changed = Signal(ConfType)
|
||||
download_location_changed = Signal(str)
|
||||
files_changed = Signal(list)
|
||||
workers_changed = Signal(int)
|
||||
token_changed = Signal(dict)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# Configuration du logger avec niveau détaillé
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.setLevel(logging.DEBUG) # Niveau DEBUG pour capturer tous les messages
|
||||
|
||||
# S'assurer qu'un gestionnaire de log est présent
|
||||
if not self.logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
self.logger.info("Initialisation de ConfManager")
|
||||
|
||||
# Verrou pour les opérations de fichier (thread-safety)
|
||||
self._lock = threading.RLock()
|
||||
self.logger.debug("Verrou RLock initialisé")
|
||||
|
||||
# Préparation du répertoire de configuration
|
||||
self.app_config_path = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppConfigLocation))
|
||||
self.logger.debug(f"Répertoire de configuration: {self.app_config_path}")
|
||||
|
||||
# cache web
|
||||
self.web_cache_path = self.app_config_path / "web_cache"
|
||||
|
||||
if not self.app_config_path.exists():
|
||||
try:
|
||||
self.app_config_path.mkdir(parents=True, exist_ok=True)
|
||||
self.logger.info(f"Répertoire de configuration créé: {self.app_config_path}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Erreur lors de la création du répertoire de configuration: {e}", exc_info=True)
|
||||
|
||||
# Configuration du fichier de sauvegarde
|
||||
self.conf_file = self.app_config_path / "config.pickle"
|
||||
self.logger.info(f"Fichier de configuration défini: {self.conf_file}")
|
||||
|
||||
# Initialisation avec les valeurs par défaut
|
||||
self.conf = ConfType()
|
||||
self.logger.debug("Objet ConfType initialisé avec valeurs par défaut")
|
||||
|
||||
# Chargement de la configuration
|
||||
if self.load_conf():
|
||||
self.logger.info("Configuration chargée avec succès")
|
||||
else:
|
||||
self.logger.warning("Échec du chargement de la configuration, utilisation des valeurs par défaut")
|
||||
|
||||
def load_conf(self) -> bool:
|
||||
"""
|
||||
Charge la configuration depuis le fichier pickle en utilisant un désérialiseur sécurisé.
|
||||
|
||||
Returns:
|
||||
bool: True si la configuration a été chargée avec succès, False sinon.
|
||||
"""
|
||||
with self._lock:
|
||||
self.logger.debug("Début du chargement de la configuration")
|
||||
|
||||
try:
|
||||
if self.conf_file.exists():
|
||||
self.logger.debug(f"Le fichier de configuration existe: {self.conf_file}")
|
||||
|
||||
with open(self.conf_file, "rb") as f:
|
||||
self.logger.debug("Fichier de configuration ouvert pour lecture")
|
||||
|
||||
# Utilisation du RestrictedUnpickler pour la sécurité
|
||||
unpickler = RestrictedUnpickler(f)
|
||||
self.logger.debug("Désérialisation avec RestrictedUnpickler")
|
||||
|
||||
# Désérialisation avec gestion d'erreurs spécifiques
|
||||
try:
|
||||
loaded_conf = unpickler.load()
|
||||
self.logger.debug("Données désérialisées avec succès")
|
||||
|
||||
# Vérification du type pour s'assurer de la conformité
|
||||
if isinstance(loaded_conf, ConfType):
|
||||
self.conf = loaded_conf
|
||||
self.logger.info("Configuration chargée et vérifiée avec succès")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Le type chargé n'est pas valide: {type(loaded_conf)}")
|
||||
return False
|
||||
|
||||
except pickle.UnpicklingError as ue:
|
||||
self.logger.error(f"Erreur de désérialisation restrictive: {ue}", exc_info=True)
|
||||
self._backup_corrupted_config()
|
||||
return False
|
||||
else:
|
||||
self.logger.warning(f"Fichier de configuration non trouvé, création par défaut: {self.conf_file}")
|
||||
self.save_conf()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Erreur inattendue lors du chargement de la configuration: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def save_conf(self) -> bool:
|
||||
"""
|
||||
Sauvegarde la configuration actuelle dans le fichier pickle.
|
||||
|
||||
Returns:
|
||||
bool: True si la sauvegarde a réussi, False sinon.
|
||||
"""
|
||||
with self._lock:
|
||||
self.logger.debug("Début de la sauvegarde de la configuration")
|
||||
|
||||
try:
|
||||
# Création d'une copie pour éviter les modifications pendant la sérialisation
|
||||
conf_to_save = deepcopy(self.conf)
|
||||
self.logger.debug("Copie profonde de la configuration créée pour la sauvegarde")
|
||||
|
||||
# Sérialisation et écriture du fichier
|
||||
with open(self.conf_file, 'wb') as f:
|
||||
self.logger.debug("Fichier de configuration ouvert pour écriture")
|
||||
pickle.dump(conf_to_save, f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
self.logger.debug(f"Sérialisation effectuée avec le protocole {pickle.HIGHEST_PROTOCOL}")
|
||||
|
||||
self.logger.info("Configuration sauvegardée avec succès")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Erreur lors de la sauvegarde de la configuration: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def _backup_corrupted_config(self):
|
||||
"""
|
||||
Crée une sauvegarde horodatée du fichier de configuration corrompu.
|
||||
"""
|
||||
if self.conf_file.exists():
|
||||
try:
|
||||
# Création d'un nom de fichier avec date et heure
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = self.conf_file.with_name(f"{self.conf_file.stem}_{timestamp}_corrupted.bak")
|
||||
self.logger.debug(f"Préparation de la sauvegarde du fichier corrompu vers: {backup_path}")
|
||||
|
||||
# Copie du fichier
|
||||
with open(self.conf_file, 'rb') as src_file:
|
||||
corrupted_data = src_file.read()
|
||||
|
||||
with open(backup_path, 'wb') as backup_file:
|
||||
backup_file.write(corrupted_data)
|
||||
|
||||
self.logger.warning(f"Configuration corrompue sauvegardée dans {backup_path}")
|
||||
|
||||
# Journaliser les détails du fichier corrompu
|
||||
self.logger.debug(f"Taille du fichier corrompu: {os.path.getsize(backup_path)} octets")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Échec de la sauvegarde du fichier corrompu: {e}", exc_info=True)
|
||||
|
||||
def get_value(self, key, default=None):
|
||||
"""
|
||||
Récupère une valeur de la configuration par son nom de clé.
|
||||
|
||||
Args :
|
||||
key : Nom de la propriété à récupérer
|
||||
|
||||
Returns :
|
||||
La valeur associée à la clé ou None si la clé n'existe pas
|
||||
|
||||
"""
|
||||
with self._lock:
|
||||
self.logger.debug(f"Récupération de la valeur pour la clé: {key}")
|
||||
if hasattr(self.conf, key):
|
||||
value = getattr(self.conf, key)
|
||||
self.logger.debug(f"Valeur récupérée pour {key}: {value}")
|
||||
return value
|
||||
else:
|
||||
self.logger.warning(f"Tentative d'accès à une clé inexistante: {key}")
|
||||
return default
|
||||
|
||||
def set_value(self, key, value):
|
||||
"""
|
||||
Définit une valeur dans la configuration et émet le signal approprié.
|
||||
|
||||
Args:
|
||||
key: Nom de la propriété à définir
|
||||
value: Valeur à affecter
|
||||
|
||||
Returns:
|
||||
bool: True si la valeur a été définie avec succès, False sinon
|
||||
"""
|
||||
with self._lock:
|
||||
self.logger.debug(f"Tentative de définition de la valeur pour la clé: {key}")
|
||||
if hasattr(self.conf, key):
|
||||
try:
|
||||
# Stockage de l'ancienne valeur pour la journalisation
|
||||
old_value = getattr(self.conf, key)
|
||||
|
||||
# Définition de la nouvelle valeur
|
||||
setattr(self.conf, key, value)
|
||||
self.logger.info(f"Valeur de {key} modifiée: {old_value} -> {value}")
|
||||
|
||||
# Émission des signaux appropriés
|
||||
if key == "download_location":
|
||||
self.download_location_changed.emit(value)
|
||||
self.logger.debug(f"Signal download_location_changed émis avec {value}")
|
||||
elif key == "files":
|
||||
self.files_changed.emit(list(value.values()))
|
||||
self.logger.debug(f"Signal files_changed émis avec {len(value)} fichiers")
|
||||
elif key == "workers":
|
||||
self.workers_changed.emit(value)
|
||||
self.logger.debug(f"Signal workers_changed émis avec {value}")
|
||||
elif key == "token":
|
||||
self.token_changed.emit(value)
|
||||
self.logger.debug("Signal token_changed émis")
|
||||
|
||||
# Signal global indiquant que la configuration a changé
|
||||
self.conf_changed.emit(self.conf)
|
||||
self.logger.debug("Signal conf_changed émis")
|
||||
|
||||
# Sauvegarde automatique de la configuration
|
||||
self.save_conf()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Erreur lors de la définition de {key}: {e}", exc_info=True)
|
||||
return False
|
||||
else:
|
||||
self.logger.warning(f"Tentative de définition d'une clé inexistante: {key}")
|
||||
return False
|
||||
|
||||
def reset_to_defaults(self):
|
||||
"""
|
||||
Réinitialise la configuration aux valeurs par défaut.
|
||||
|
||||
Returns:
|
||||
bool: True si la réinitialisation a réussi, False sinon
|
||||
"""
|
||||
with self._lock:
|
||||
try:
|
||||
self.logger.info("Début de la réinitialisation de la configuration aux valeurs par défaut")
|
||||
old_conf = deepcopy(self.conf)
|
||||
|
||||
# Création d'une nouvelle instance avec les valeurs par défaut
|
||||
self.conf = ConfType()
|
||||
self.logger.debug("Configuration réinitialisée aux valeurs par défaut")
|
||||
|
||||
# Émission de tous les signaux
|
||||
self.download_location_changed.emit(self.conf.download_location)
|
||||
self.files_changed.emit(list(self.conf.files.values()))
|
||||
self.workers_changed.emit(self.conf.workers)
|
||||
self.token_changed.emit(self.conf.token)
|
||||
self.conf_changed.emit(self.conf)
|
||||
|
||||
self.logger.debug("Tous les signaux émis après réinitialisation")
|
||||
|
||||
# Sauvegarde des nouvelles valeurs
|
||||
if self.save_conf():
|
||||
self.logger.info("Réinitialisation terminée avec succès")
|
||||
return True
|
||||
else:
|
||||
# En cas d'échec, restauration de l'ancienne configuration
|
||||
self.conf = old_conf
|
||||
self.logger.error(
|
||||
"Échec de la sauvegarde après réinitialisation, restauration de l'ancienne configuration")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Erreur lors de la réinitialisation: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user