mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-23 17:42:19 +02:00
433 lines
17 KiB
Python
433 lines
17 KiB
Python
# streaming_providers/base/provider_registry.py
|
|
"""
|
|
Core provider registry handling discovery, metadata, and lifecycle management.
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from .provider import StreamingProvider
|
|
from .utils.logger import logger
|
|
|
|
|
|
class ProviderMetadata:
|
|
"""Metadata for a provider instance with lazy initialization."""
|
|
|
|
def __init__(self, plugin_class, country: str, enabled: bool = False):
|
|
self.plugin_class = plugin_class
|
|
self.country = country.lower()
|
|
self.enabled = enabled
|
|
self.instance: Optional[StreamingProvider] = None
|
|
self._extract_metadata()
|
|
|
|
def _extract_metadata(self):
|
|
"""Extract static metadata from provider class without instantiation"""
|
|
self.plugin_name = self.plugin_class.__name__.lower().replace("provider", "")
|
|
|
|
# Check if provider supports multiple countries
|
|
supports_multiple = self.plugin_class.supports_multiple_countries()
|
|
|
|
if supports_multiple:
|
|
# Multi-country provider: include country in name
|
|
self.name = f"{self.plugin_name}_{self.country}"
|
|
self.is_multi_country = True
|
|
else:
|
|
# Single-country provider
|
|
self.name = self.plugin_name
|
|
|
|
# Check if provider has an explicit single country
|
|
supported_countries = self.plugin_class.get_static_supported_countries()
|
|
if len(supported_countries) == 1:
|
|
# Provider has exactly one supported country (e.g., HRTi with ["HR"])
|
|
# Use that country instead of the passed-in country
|
|
self.country = supported_countries[0].upper()
|
|
self.is_multi_country = False
|
|
else:
|
|
# True single-country or country-agnostic (empty list)
|
|
self.is_multi_country = False
|
|
|
|
# Rest of the method remains the same...
|
|
self.label = self.plugin_class.get_static_label(self.country)
|
|
self.supported_auth_types = self.plugin_class.get_static_auth_types()
|
|
self.logo = self.plugin_class.get_static_logo(self.country)
|
|
self.supported_countries = self.plugin_class.get_static_supported_countries()
|
|
self.requires_credentials = any(
|
|
auth_type in ["user_credentials", "client_credentials"]
|
|
for auth_type in self.supported_auth_types
|
|
)
|
|
|
|
def create_instance(self) -> Optional[StreamingProvider]:
|
|
"""Lazily create provider instance if enabled"""
|
|
if not self.enabled:
|
|
return None
|
|
|
|
if self.instance is None:
|
|
try:
|
|
logger.info(f"Creating instance for provider: {self.name}")
|
|
self.instance = self.plugin_class(country=self.country)
|
|
logger.debug(f"Successfully created instance for {self.name}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to create instance for {self.name}: {e}")
|
|
self.instance = None
|
|
|
|
return self.instance
|
|
|
|
def destroy_instance(self):
|
|
"""Clean up provider instance"""
|
|
if self.instance:
|
|
logger.debug(f"Destroying instance for provider: {self.name}")
|
|
self.instance = None
|
|
|
|
def set_enabled(self, enabled: bool):
|
|
"""Update enabled status and manage instance accordingly"""
|
|
self.enabled = enabled
|
|
if enabled and self.instance is None:
|
|
self.create_instance()
|
|
elif not enabled and self.instance is not None:
|
|
self.destroy_instance()
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert metadata to dictionary for API response"""
|
|
return {
|
|
"name": self.name,
|
|
"label": self.label,
|
|
"plugin": self.plugin_name,
|
|
"country": self.country.upper(),
|
|
"enabled": self.enabled,
|
|
"instance_ready": self.instance is not None,
|
|
"requires_credentials": self.requires_credentials,
|
|
"supported_auth_types": self.supported_auth_types,
|
|
"logo": self.logo,
|
|
"is_multi_country": self.is_multi_country,
|
|
"supported_countries": self.supported_countries,
|
|
}
|
|
|
|
|
|
class M3UGroupMetadata:
|
|
"""
|
|
Specialized metadata for M3U group-based providers.
|
|
|
|
NOW SUPPORTS LAZY INITIALIZATION like regular ProviderMetadata.
|
|
This ensures M3U providers get proper proxy configuration.
|
|
"""
|
|
|
|
def __init__(self, plugin_class, group: str, country: str, enabled: bool = False):
|
|
"""
|
|
Initialize M3U group metadata.
|
|
|
|
Args:
|
|
plugin_class: M3UProvider class (not instance!)
|
|
group: Group title from M3U file
|
|
country: Country code
|
|
enabled: Whether this provider is enabled
|
|
"""
|
|
self.plugin_class = plugin_class
|
|
self.group = group
|
|
self.country = country.lower()
|
|
self.enabled = enabled
|
|
self.instance: Optional[StreamingProvider] = None
|
|
|
|
# Extract metadata from class
|
|
self._extract_metadata()
|
|
|
|
def _extract_metadata(self):
|
|
"""Extract metadata from provider class"""
|
|
self.plugin_name = "m3u"
|
|
|
|
# Create name from sanitized group title
|
|
# IMPORTANT: This MUST match what M3UProvider.provider_name property returns!
|
|
# See M3UProvider lines 189-194:
|
|
# clean_name = re.sub(r'[^a-z0-9\s]+', '', self.group_filter.lower())
|
|
# clean_name = re.sub(r'\s+', '_', clean_name).strip('_')
|
|
import re
|
|
clean_name = re.sub(r'[^a-z0-9\s]+', '', self.group.lower())
|
|
clean_name = re.sub(r'\s+', '_', clean_name).strip('_')
|
|
self.name = clean_name if clean_name else "m3u"
|
|
|
|
# Static metadata
|
|
self.label = self.group # Use original group as display label
|
|
self.is_multi_country = False
|
|
self.supported_auth_types = self.plugin_class.get_static_auth_types()
|
|
self.logo = self.plugin_class.get_static_logo()
|
|
self.supported_countries = ["*"] # M3U supports all countries
|
|
self.requires_credentials = False
|
|
|
|
def create_instance(self) -> Optional[StreamingProvider]:
|
|
"""
|
|
Lazily create M3U provider instance.
|
|
|
|
This now follows the same pattern as regular providers, ensuring
|
|
proper initialization including proxy configuration.
|
|
"""
|
|
if not self.enabled:
|
|
return None
|
|
|
|
if self.instance is None:
|
|
try:
|
|
logger.info(f"Creating M3U group instance: {self.name} (group: {self.group})")
|
|
|
|
# Create instance with group filter - __init__ will handle proxy setup
|
|
self.instance = self.plugin_class(
|
|
country=self.country,
|
|
group_filter=self.group
|
|
)
|
|
|
|
logger.debug(f"Successfully created instance for M3U group: {self.name}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to create M3U group instance {self.name}: {e}")
|
|
self.instance = None
|
|
|
|
return self.instance
|
|
|
|
def destroy_instance(self):
|
|
"""Clean up provider instance"""
|
|
if self.instance:
|
|
logger.debug(f"Destroying M3U group instance: {self.name}")
|
|
self.instance = None
|
|
|
|
def set_enabled(self, enabled: bool):
|
|
"""Update enabled status and manage instance accordingly"""
|
|
self.enabled = enabled
|
|
if enabled and self.instance is None:
|
|
self.create_instance()
|
|
elif not enabled and self.instance is not None:
|
|
self.destroy_instance()
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert metadata to dictionary for API response"""
|
|
return {
|
|
"name": self.name,
|
|
"label": self.label,
|
|
"plugin": self.plugin_name,
|
|
"country": self.country.upper(),
|
|
"enabled": self.enabled,
|
|
"instance_ready": self.instance is not None,
|
|
"requires_credentials": self.requires_credentials,
|
|
"supported_auth_types": self.supported_auth_types,
|
|
"logo": self.logo,
|
|
"is_multi_country": self.is_multi_country,
|
|
"supported_countries": self.supported_countries,
|
|
# M3U-specific fields
|
|
"group": self.group,
|
|
"type": "m3u_group",
|
|
}
|
|
|
|
|
|
class ProviderRegistry:
|
|
"""
|
|
Core registry for provider discovery, metadata, and lifecycle management.
|
|
Separated concern: Provider registration and access.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.providers: Dict[str, StreamingProvider] = {} # Active instances
|
|
self.provider_metadata: Dict[str, ProviderMetadata] = {} # All providers
|
|
logger.info("ProviderRegistry: Initialized")
|
|
|
|
@staticmethod
|
|
def _is_provider_enabled(provider_name: str, country: Optional[str] = None) -> bool:
|
|
"""Check if a provider is enabled via settings manager."""
|
|
try:
|
|
from .settings.provider_enable_manager import ProviderEnableManager
|
|
|
|
enable_manager = ProviderEnableManager()
|
|
|
|
instance_name = f"{provider_name}_{country}" if country else provider_name
|
|
return enable_manager.is_provider_enabled(instance_name)
|
|
except Exception as e:
|
|
logger.warning(f"Could not check enable status for '{provider_name}': {e}")
|
|
return True
|
|
|
|
def discover_all_providers(self, default_country: str = "DE") -> List[str]:
|
|
"""Discover ALL provider instances and extract metadata."""
|
|
from streaming_providers import AVAILABLE_PROVIDERS
|
|
|
|
logger.info("ProviderRegistry: Discovering provider instances")
|
|
discovered = []
|
|
|
|
for plugin_name, plugin_class in AVAILABLE_PROVIDERS.items():
|
|
# Special handling for M3U provider - discover groups
|
|
if plugin_name == "m3u":
|
|
discovered_groups = self._discover_m3u_groups(plugin_class, default_country)
|
|
discovered.extend(discovered_groups)
|
|
continue
|
|
|
|
if plugin_class.supports_multiple_countries():
|
|
for country in plugin_class.get_static_supported_countries():
|
|
instance_name = f"{plugin_name}_{country}"
|
|
enabled = self._is_provider_enabled(plugin_name, country)
|
|
|
|
metadata = ProviderMetadata(plugin_class, country, enabled)
|
|
self.provider_metadata[instance_name] = metadata
|
|
discovered.append(instance_name)
|
|
|
|
if enabled:
|
|
instance = metadata.create_instance()
|
|
if instance:
|
|
self.providers[instance_name] = instance
|
|
else:
|
|
instance_name = plugin_name
|
|
enabled = self._is_provider_enabled(plugin_name)
|
|
|
|
metadata = ProviderMetadata(plugin_class, default_country, enabled)
|
|
self.provider_metadata[instance_name] = metadata
|
|
discovered.append(instance_name)
|
|
|
|
if enabled:
|
|
instance = metadata.create_instance()
|
|
if instance:
|
|
self.providers[instance_name] = instance
|
|
|
|
logger.info(f"ProviderRegistry: Discovered {len(discovered)} provider instances")
|
|
return discovered
|
|
|
|
def _discover_m3u_groups(self, plugin_class, default_country: str) -> List[str]:
|
|
"""
|
|
Discover M3U groups and register each as a separate provider instance.
|
|
|
|
Each group-title in M3U files becomes an independent provider.
|
|
|
|
IMPORTANT: This now creates lazy metadata instead of eager instances,
|
|
allowing proper proxy configuration during initialization.
|
|
|
|
Args:
|
|
plugin_class: M3UProvider class
|
|
default_country: Default country code
|
|
|
|
Returns:
|
|
List of discovered provider instance names
|
|
"""
|
|
discovered = []
|
|
|
|
try:
|
|
# Import M3UProvider to call static discovery method
|
|
from streaming_providers.providers.m3u import M3UProvider
|
|
|
|
# Discover all unique groups from M3U files
|
|
# Now includes filename-based fallback for files without group-title
|
|
groups = M3UProvider.discover_groups(config_dir=None)
|
|
|
|
if not groups:
|
|
logger.warning("M3U: No M3U files found or all files are unreadable")
|
|
return discovered
|
|
|
|
logger.info(f"M3U: Discovered {len(groups)} groups: {', '.join(groups)}")
|
|
|
|
# Check if M3U provider is enabled
|
|
# Use base "m3u" for enable check (all groups share same enable status)
|
|
enabled = self._is_provider_enabled("m3u", None)
|
|
|
|
# Register each group as a separate provider with LAZY METADATA
|
|
for group in groups:
|
|
try:
|
|
# Create lazy metadata (no instance yet!)
|
|
metadata = M3UGroupMetadata(
|
|
plugin_class=plugin_class, # Pass class, not instance
|
|
group=group,
|
|
country=default_country,
|
|
enabled=enabled
|
|
)
|
|
|
|
# Register metadata
|
|
self.provider_metadata[metadata.name] = metadata
|
|
discovered.append(metadata.name)
|
|
|
|
# Only create instance if enabled
|
|
if enabled:
|
|
instance = metadata.create_instance()
|
|
if instance:
|
|
self.providers[metadata.name] = instance
|
|
|
|
logger.debug(
|
|
f"M3U: Registered group provider '{metadata.name}' "
|
|
f"(label: {group}, enabled: {enabled})"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"M3U: Failed to register group '{group}': {e}")
|
|
continue
|
|
|
|
logger.info(f"M3U: Successfully registered {len(discovered)} group providers")
|
|
|
|
except ImportError:
|
|
logger.warning("M3U: M3UProvider not available for group discovery")
|
|
except Exception as e:
|
|
logger.error(f"M3U: Error during group discovery: {e}")
|
|
|
|
return discovered
|
|
|
|
def get_provider(self, provider_name: str) -> Optional[StreamingProvider]:
|
|
"""Get provider instance, creating it lazily if needed."""
|
|
provider = self.providers.get(provider_name)
|
|
if provider:
|
|
return provider
|
|
|
|
metadata = self.provider_metadata.get(provider_name)
|
|
if not metadata or not metadata.enabled:
|
|
return None
|
|
|
|
provider = metadata.create_instance()
|
|
if provider:
|
|
self.providers[provider_name] = provider
|
|
return provider
|
|
|
|
def set_provider_enabled(self, provider_name: str, enabled: bool) -> bool:
|
|
"""Enable or disable a provider dynamically."""
|
|
metadata = self.provider_metadata.get(provider_name)
|
|
if not metadata:
|
|
logger.error(f"Cannot enable/disable unknown provider '{provider_name}'")
|
|
return False
|
|
|
|
metadata.set_enabled(enabled)
|
|
|
|
if enabled and metadata.instance:
|
|
self.providers[provider_name] = metadata.instance
|
|
elif not enabled and provider_name in self.providers:
|
|
del self.providers[provider_name]
|
|
|
|
try:
|
|
from .settings.provider_enable_manager import ProviderEnableManager
|
|
|
|
enable_manager = ProviderEnableManager()
|
|
success, message = enable_manager.set_provider_enabled(provider_name, enabled)
|
|
return success
|
|
except Exception as e:
|
|
logger.error(f"Error updating enable status: {e}")
|
|
return False
|
|
|
|
def reinitialize_provider(self, provider_name: str) -> bool:
|
|
"""Reinitialize a provider instance."""
|
|
metadata = self.provider_metadata.get(provider_name)
|
|
if not metadata or not metadata.enabled:
|
|
return False
|
|
|
|
try:
|
|
metadata.destroy_instance()
|
|
new_instance = metadata.create_instance()
|
|
if new_instance:
|
|
self.providers[provider_name] = new_instance
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"Failed to reinitialize '{provider_name}': {e}")
|
|
return False
|
|
|
|
def get_all_providers_metadata(self) -> List[Dict[str, Any]]:
|
|
"""Get metadata for ALL provider instances."""
|
|
return [m.to_dict() for m in self.provider_metadata.values()]
|
|
|
|
def list_providers(self) -> List[str]:
|
|
"""List enabled provider names."""
|
|
return list(self.providers.keys())
|
|
|
|
def list_all_providers(self) -> List[str]:
|
|
"""List ALL provider names (enabled + disabled)."""
|
|
return list(self.provider_metadata.keys())
|
|
|
|
def get_enabled_providers(self) -> List[str]:
|
|
"""Get list of enabled provider names."""
|
|
return [name for name, m in self.provider_metadata.items() if m.enabled]
|
|
|
|
def clear_providers(self):
|
|
"""Clear all providers."""
|
|
self.providers.clear()
|
|
logger.info("ProviderRegistry: Cleared all providers") |