Files
Lycostorrent/app/plugins/torrent_clients/__init__.py
2026-03-23 20:59:26 +01:00

262 lines
7.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Gestionnaire de plugins pour les clients torrent.
Découvre automatiquement les plugins disponibles.
"""
import os
import importlib
import logging
from typing import Dict, List, Optional, Type, Any
from .base import TorrentClientPlugin, TorrentClientConfig
logger = logging.getLogger(__name__)
# Registre des plugins disponibles
_plugins: Dict[str, Type[TorrentClientPlugin]] = {}
# Instance active du client
_active_client: Optional[TorrentClientPlugin] = None
_active_config: Optional[Dict[str, Any]] = None
def discover_plugins() -> Dict[str, Type[TorrentClientPlugin]]:
"""
Découvre automatiquement tous les plugins dans le dossier.
Returns:
Dictionnaire {nom_plugin: classe_plugin}
"""
global _plugins
_plugins = {}
plugins_dir = os.path.dirname(__file__)
for filename in os.listdir(plugins_dir):
if filename.endswith('.py') and filename not in ('__init__.py', 'base.py'):
module_name = filename[:-3]
try:
module = importlib.import_module(f'.{module_name}', package=__package__)
# Chercher PLUGIN_CLASS ou une classe héritant de TorrentClientPlugin
if hasattr(module, 'PLUGIN_CLASS'):
plugin_class = module.PLUGIN_CLASS
_plugins[plugin_class.PLUGIN_NAME.lower()] = plugin_class
logger.info(f"✅ Plugin chargé: {plugin_class.PLUGIN_NAME} v{plugin_class.PLUGIN_VERSION}")
except Exception as e:
logger.warning(f"⚠️ Impossible de charger le plugin {module_name}: {e}")
return _plugins
def get_available_plugins() -> List[Dict[str, str]]:
"""
Retourne la liste des plugins disponibles.
Returns:
Liste de dictionnaires avec les infos de chaque plugin
"""
if not _plugins:
discover_plugins()
return [
{
"id": name,
"name": plugin.PLUGIN_NAME,
"description": plugin.PLUGIN_DESCRIPTION,
"version": plugin.PLUGIN_VERSION,
"author": plugin.PLUGIN_AUTHOR
}
for name, plugin in _plugins.items()
]
def get_plugin(name: str) -> Optional[Type[TorrentClientPlugin]]:
"""
Récupère une classe de plugin par son nom.
Args:
name: Nom du plugin (insensible à la casse)
Returns:
Classe du plugin ou None
"""
if not _plugins:
discover_plugins()
return _plugins.get(name.lower())
def create_client(plugin_name: str, config: Dict[str, Any]) -> Optional[TorrentClientPlugin]:
"""
Crée une instance de client torrent.
Args:
plugin_name: Nom du plugin à utiliser
config: Configuration (host, port, username, password, use_ssl, path)
Returns:
Instance du client ou None
"""
plugin_class = get_plugin(plugin_name)
if not plugin_class:
logger.error(f"❌ Plugin non trouvé: {plugin_name}")
return None
try:
from urllib.parse import urlparse
# Nettoyer le host (enlever http:// ou https:// si présent)
host = config.get('host', 'localhost')
path = config.get('path', '')
use_ssl = config.get('use_ssl', False)
port = config.get('port', None) # None = pas spécifié
# Si l'utilisateur a entré une URL complète, l'analyser
if host.startswith('http://') or host.startswith('https://'):
parsed = urlparse(host)
host = parsed.netloc or parsed.path.split('/')[0]
use_ssl = parsed.scheme == 'https'
# Extraire le port de l'URL si présent (ex: host:8080)
if ':' in host:
host_part, port_part = host.rsplit(':', 1)
if port_part.isdigit():
host = host_part
if port is None: # Seulement si pas déjà spécifié
port = int(port_part)
# Extraire le chemin de l'URL si pas déjà fourni
if parsed.path and not path:
path = parsed.path.rstrip('/')
# Enlever le trailing slash du host
host = host.rstrip('/')
# Gérer le port
# - Si port est None ou vide: pas de port explicite (0)
# - Si port est un nombre valide: l'utiliser
# - Rétrocompatibilité: les anciennes configs avec port=8080 fonctionnent
if port is None or port == '':
port = 0
elif isinstance(port, str):
port = int(port) if port.strip().isdigit() else 0
else:
port = int(port) if port else 0
client_config = TorrentClientConfig(
host=host,
port=port,
username=config.get('username', ''),
password=config.get('password', ''),
use_ssl=use_ssl,
path=path
)
logger.info(f"🔧 Configuration client: {client_config.base_url}")
client = plugin_class(client_config)
return client
except Exception as e:
logger.error(f"❌ Erreur création client {plugin_name}: {e}")
return None
def get_active_client() -> Optional[TorrentClientPlugin]:
"""Retourne le client actif ou None."""
global _active_client
return _active_client
def set_active_client(client: Optional[TorrentClientPlugin], config: Optional[Dict[str, Any]] = None) -> None:
"""Définit le client actif."""
global _active_client, _active_config
# Déconnecter l'ancien client
if _active_client:
try:
_active_client.disconnect()
except:
pass
_active_client = client
_active_config = config
def get_active_config() -> Optional[Dict[str, Any]]:
"""Retourne la configuration du client actif."""
return _active_config
def load_client_from_config(config_path: str = '/app/config/torrent_client.json') -> Optional[TorrentClientPlugin]:
"""
Charge le client depuis un fichier de configuration.
Args:
config_path: Chemin du fichier de configuration
Returns:
Instance du client ou None
"""
import json
try:
if not os.path.exists(config_path):
return None
with open(config_path, 'r') as f:
config = json.load(f)
if not config.get('enabled', False):
logger.info(" Client torrent désactivé")
return None
plugin_name = config.get('plugin', '')
if not plugin_name:
return None
client = create_client(plugin_name, config)
if client and client.connect():
set_active_client(client, config)
logger.info(f"✅ Client torrent actif: {plugin_name}")
return client
return None
except Exception as e:
logger.error(f"❌ Erreur chargement config client torrent: {e}")
return None
def save_client_config(config: Dict[str, Any], config_path: str = '/app/config/torrent_client.json') -> bool:
"""
Sauvegarde la configuration du client torrent.
Args:
config: Configuration à sauvegarder
config_path: Chemin du fichier
Returns:
True si succès
"""
import json
try:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
return True
except Exception as e:
logger.error(f"❌ Erreur sauvegarde config client torrent: {e}")
return False
# Découvrir les plugins au chargement du module
discover_plugins()