Files
script.service.ultimate/lib/streaming_providers/base/settings/provider_enable_manager.py
T

520 lines
18 KiB
Python
Raw Normal View History

2025-12-12 12:03:13 +01:00
# streaming_providers/base/settings/provider_enable_manager.py
"""
Provider Enable Manager - Handles provider enable/disable status with precedence:
1. Kodi settings (if in Kodi environment and setting exists)
2. providers_enabled.json file
3. Default to enabled (True)
Works in both Kodi and standalone environments.
"""
import time
from enum import Enum
2026-01-06 16:41:03 +01:00
from typing import Any, Dict, Optional, Tuple
2025-12-12 12:03:13 +01:00
2026-01-06 16:41:03 +01:00
from ..utils.environment import get_vfs_instance, is_kodi_environment
2025-12-12 12:03:13 +01:00
from ..utils.logger import logger
class EnableSource(Enum):
"""Source of the enable setting"""
2026-01-06 16:41:03 +01:00
2025-12-12 12:03:13 +01:00
KODI = "kodi"
FILE = "file"
DEFAULT = "default"
class ProviderEnableManager:
"""
Manages provider enable/disable status with proper precedence.
Uses VFS for file operations to work in both Kodi and standalone.
"""
DEFAULT_FILENAME = "providers_enabled.json"
DEFAULT_VERSION = "1.0"
CACHE_TTL = 30 # seconds
def __init__(self, config_dir: Optional[str] = None):
"""
Initialize the enable manager.
Args:
config_dir: Optional configuration directory (uses VFS default if None)
"""
self.vfs = get_vfs_instance(config_dir=config_dir)
self._cache: Optional[Dict[str, Any]] = None
self._cache_time: float = 0
self._kodi_bridge = None
# Try to initialize Kodi bridge if in Kodi environment
if is_kodi_environment():
try:
2025-12-16 13:53:31 +01:00
# Fixed import path - kodi_settings_bridge is in the same directory
from .kodi_settings_bridge import KodiSettingsBridge
2026-01-06 16:41:03 +01:00
2025-12-12 12:03:13 +01:00
self._kodi_bridge = KodiSettingsBridge(config_dir=config_dir)
logger.debug("ProviderEnableManager: Kodi bridge initialized")
except ImportError as e:
logger.debug(f"ProviderEnableManager: Kodi bridge not available: {e}")
self._kodi_bridge = None
2025-12-16 13:53:31 +01:00
except Exception as e:
2026-01-16 12:24:41 +01:00
logger.error(f"ProviderEnableManager: Error initializing Kodi bridge: {e}")
2025-12-16 13:53:31 +01:00
self._kodi_bridge = None
2025-12-12 12:03:13 +01:00
def _load_file(self, force_reload: bool = False) -> Dict[str, Any]:
"""
Load providers_enabled.json file with caching.
Args:
force_reload: Force reload from disk ignoring cache
Returns:
Dictionary with file contents
"""
current_time = time.time()
# Check cache first
2026-01-16 12:24:41 +01:00
if not force_reload and self._cache and (current_time - self._cache_time) < self.CACHE_TTL:
2025-12-12 12:03:13 +01:00
return self._cache
# Initialize default structure
2026-01-06 16:41:03 +01:00
default_data = {"version": self.DEFAULT_VERSION, "providers": {}}
2025-12-12 12:03:13 +01:00
# Check if file exists
if not self.vfs.exists(self.DEFAULT_FILENAME):
self._cache = default_data
self._cache_time = current_time
2026-01-06 16:41:03 +01:00
logger.debug(
f"ProviderEnableManager: File {self.DEFAULT_FILENAME} not found, using defaults"
)
2025-12-12 12:03:13 +01:00
return default_data
try:
# Read file via VFS
data = self.vfs.read_json(self.DEFAULT_FILENAME)
if not isinstance(data, dict):
2026-01-16 12:24:41 +01:00
logger.warning(f"ProviderEnableManager: Invalid file format, using defaults")
2025-12-12 12:03:13 +01:00
self._cache = default_data
self._cache_time = current_time
return default_data
# Ensure required structure
if "version" not in data:
data["version"] = self.DEFAULT_VERSION
if "providers" not in data or not isinstance(data["providers"], dict):
data["providers"] = {}
# Ensure all values are booleans
for provider, value in list(data["providers"].items()):
if not isinstance(value, bool):
try:
data["providers"][provider] = bool(value)
except (ValueError, TypeError):
2026-01-06 16:41:03 +01:00
logger.warning(
f"ProviderEnableManager: Invalid value for {provider}, removing"
)
2025-12-12 12:03:13 +01:00
del data["providers"][provider]
# Add last_updated if missing
if "last_updated" not in data:
data["last_updated"] = current_time
# Update cache
self._cache = data
self._cache_time = current_time
2026-01-06 16:41:03 +01:00
logger.debug(
f"ProviderEnableManager: Loaded {len(data['providers'])} providers from file"
)
2025-12-12 12:03:13 +01:00
return data
except Exception as e:
2026-01-16 12:24:41 +01:00
logger.error(f"ProviderEnableManager: Error loading {self.DEFAULT_FILENAME}: {e}")
2025-12-12 12:03:13 +01:00
self._cache = default_data
self._cache_time = current_time
return default_data
def _save_file(self, data: Dict[str, Any]) -> bool:
"""
Save data to providers_enabled.json file.
Args:
data: Data to save
Returns:
True if successful, False otherwise
"""
try:
# Add/update metadata
data["last_updated"] = time.time()
if "version" not in data:
data["version"] = self.DEFAULT_VERSION
# Write via VFS
success = self.vfs.write_json(self.DEFAULT_FILENAME, data)
if success:
# Update cache
self._cache = data
self._cache_time = time.time()
2026-01-06 16:41:03 +01:00
logger.debug(
f"ProviderEnableManager: Saved {len(data.get('providers', {}))} providers to file"
)
2025-12-12 12:03:13 +01:00
else:
2026-01-16 12:24:41 +01:00
logger.error(f"ProviderEnableManager: Failed to write to {self.DEFAULT_FILENAME}")
2025-12-12 12:03:13 +01:00
return success
except Exception as e:
2026-01-16 12:24:41 +01:00
logger.error(f"ProviderEnableManager: Error saving {self.DEFAULT_FILENAME}: {e}")
2025-12-12 12:03:13 +01:00
return False
def _get_kodi_enabled_status(self, provider_name: str) -> Optional[bool]:
2025-12-16 13:22:44 +01:00
"""
Get enabled status from Kodi settings.
Args:
provider_name: Provider name (e.g., "joyn_de", "rtlplus")
2025-12-12 12:03:13 +01:00
2025-12-16 13:22:44 +01:00
Returns:
True/False if setting exists in Kodi, None if not set
"""
2025-12-12 12:03:13 +01:00
if not is_kodi_environment() or not self._kodi_bridge:
return None
try:
2025-12-16 13:22:44 +01:00
# Parse provider_name for potential country suffix
2026-01-06 16:41:03 +01:00
parts = provider_name.rsplit("_", 1)
2025-12-12 12:03:13 +01:00
2025-12-16 13:22:44 +01:00
# Check country-specific setting first (e.g., "enable_joyn_de")
if len(parts) == 2 and len(parts[1]) in (2, 3): # Country code length
2025-12-12 12:03:13 +01:00
provider, country = parts
country_setting = f"enable_{provider}_{country}"
2025-12-16 13:22:44 +01:00
# Try country-specific setting
2025-12-12 12:03:13 +01:00
country_value = self._kodi_bridge.get_setting(country_setting)
if country_value:
2026-01-06 16:41:03 +01:00
enabled = country_value.lower() in ["true", "1", "yes"]
logger.debug(
f"ProviderEnableManager: Found Kodi setting {country_setting}={enabled}"
)
2025-12-12 12:03:13 +01:00
return enabled
2025-12-16 13:22:44 +01:00
# Try general provider setting (e.g., "enable_joyn")
2025-12-12 12:03:13 +01:00
general_setting = f"enable_{provider_name}"
general_value = self._kodi_bridge.get_setting(general_setting)
if general_value:
2026-01-06 16:41:03 +01:00
enabled = general_value.lower() in ["true", "1", "yes"]
logger.debug(
f"ProviderEnableManager: Found Kodi setting {general_setting}={enabled}"
)
2025-12-12 12:03:13 +01:00
return enabled
2025-12-16 13:22:44 +01:00
# No Kodi setting found
2026-01-16 12:24:41 +01:00
logger.debug(f"ProviderEnableManager: No Kodi setting found for {provider_name}")
2025-12-12 12:03:13 +01:00
return None
except Exception as e:
2026-01-06 16:41:03 +01:00
logger.warning(
f"ProviderEnableManager: Error reading Kodi setting for {provider_name}: {e}"
)
2025-12-12 12:03:13 +01:00
return None
def is_provider_enabled(self, provider_name: str) -> bool:
2025-12-16 12:55:05 +01:00
"""
Check if provider is enabled using precedence rules.
2025-12-12 12:03:13 +01:00
2025-12-16 12:55:05 +01:00
Precedence:
1. Kodi settings (if in Kodi and setting exists)
2. providers_enabled.json file
3. Default to True
Args:
provider_name: Provider name (e.g., "joyn_de", "rtlplus")
Returns:
True if enabled, False if disabled
"""
2025-12-12 12:03:13 +01:00
# 1. Check Kodi settings first
kodi_enabled = self._get_kodi_enabled_status(provider_name)
if kodi_enabled is not None:
return kodi_enabled
# 2. Check file
data = self._load_file()
file_enabled = data["providers"].get(provider_name)
if file_enabled is not None:
return file_enabled
# 3. Default to enabled
return True
def get_enabled_source(self, provider_name: str) -> EnableSource:
"""
Determine where the enabled status comes from.
Args:
provider_name: Provider name
Returns:
EnableSource enum (KODI, FILE, or DEFAULT)
"""
# Check Kodi first
kodi_enabled = self._get_kodi_enabled_status(provider_name)
if kodi_enabled is not None:
return EnableSource.KODI
# Check file
data = self._load_file()
file_enabled = data["providers"].get(provider_name)
if file_enabled is not None:
return EnableSource.FILE
return EnableSource.DEFAULT
2026-01-16 12:24:41 +01:00
def set_provider_enabled(self, provider_name: str, enabled: bool) -> Tuple[bool, str]:
2025-12-12 12:03:13 +01:00
"""
Set enabled status for a provider (writes to file only).
Note: Cannot override Kodi settings. If provider is controlled by
Kodi settings, this will return False.
Args:
provider_name: Provider name
enabled: True to enable, False to disable
Returns:
Tuple of (success: bool, message: str)
"""
# Check if controlled by Kodi
source = self.get_enabled_source(provider_name)
if source == EnableSource.KODI:
2026-01-06 16:41:03 +01:00
message = (
f"Provider '{provider_name}' is controlled by Kodi settings. "
f"Change the setting in Kodi addon settings."
)
2025-12-12 12:03:13 +01:00
logger.warning(f"ProviderEnableManager: {message}")
return False, message
# Load current data
data = self._load_file()
# Update provider status
data["providers"][provider_name] = bool(enabled)
# Save to file
success = self._save_file(data)
if success:
action = "enabled" if enabled else "disabled"
message = f"Provider '{provider_name}' {action} in file"
logger.info(f"ProviderEnableManager: {message}")
return True, message
else:
message = f"Failed to save enabled status for '{provider_name}'"
logger.error(f"ProviderEnableManager: {message}")
return False, message
def get_all_enabled_status(self) -> Dict[str, Dict[str, Any]]:
"""
Get enabled status for all providers in file.
Returns:
Dictionary mapping provider names to their status info
"""
data = self._load_file()
result = {}
for provider_name, enabled in data["providers"].items():
source = self.get_enabled_source(provider_name)
result[provider_name] = {
"enabled": enabled,
2025-12-22 15:41:34 +01:00
"source": source.value, # Convert enum to string
2026-01-06 16:41:03 +01:00
"can_modify": source != EnableSource.KODI,
2025-12-12 12:03:13 +01:00
}
return result
def get_provider_status(self, provider_name: str) -> Optional[Dict[str, Any]]:
"""
Get detailed status for a specific provider.
Args:
provider_name: Provider name
Returns:
Dictionary with status info, or None if provider not in file
"""
data = self._load_file()
if provider_name not in data["providers"]:
return None
enabled = data["providers"][provider_name]
source = self.get_enabled_source(provider_name)
return {
"provider": provider_name,
"enabled": enabled,
2025-12-22 15:41:34 +01:00
"source": source.value, # Convert enum to string
2025-12-12 12:03:13 +01:00
"can_modify": source != EnableSource.KODI,
2026-01-06 16:41:03 +01:00
"in_file": True,
2025-12-12 12:03:13 +01:00
}
def delete_provider_setting(self, provider_name: str) -> Tuple[bool, str]:
"""
Delete provider setting from file (reset to default).
Args:
provider_name: Provider name
Returns:
Tuple of (success: bool, message: str)
"""
# Check if controlled by Kodi
source = self.get_enabled_source(provider_name)
if source == EnableSource.KODI:
2026-01-16 12:24:41 +01:00
message = f"Cannot delete setting for '{provider_name}' - controlled by Kodi"
2025-12-12 12:03:13 +01:00
logger.warning(f"ProviderEnableManager: {message}")
return False, message
# Load current data
data = self._load_file()
# Remove if exists
if provider_name in data["providers"]:
del data["providers"][provider_name]
# Save to file
success = self._save_file(data)
if success:
message = f"Deleted setting for '{provider_name}' from file"
logger.info(f"ProviderEnableManager: {message}")
return True, message
else:
message = f"Failed to delete setting for '{provider_name}'"
logger.error(f"ProviderEnableManager: {message}")
return False, message
# Provider not in file
message = f"Setting for '{provider_name}' not found in file"
logger.debug(f"ProviderEnableManager: {message}")
return True, message # Already not in file, so success
def reset_all_to_defaults(self) -> Tuple[bool, str]:
"""
Reset all provider settings to defaults (empty file).
Returns:
Tuple of (success: bool, message: str)
"""
try:
# Create default structure
default_data = {
"version": self.DEFAULT_VERSION,
"last_updated": time.time(),
2026-01-06 16:41:03 +01:00
"providers": {},
2025-12-12 12:03:13 +01:00
}
# Save empty file
success = self._save_file(default_data)
if success:
message = "All provider settings reset to defaults"
logger.info(f"ProviderEnableManager: {message}")
return True, message
else:
message = "Failed to reset provider settings"
logger.error(f"ProviderEnableManager: {message}")
return False, message
except Exception as e:
message = f"Error resetting provider settings: {e}"
logger.error(f"ProviderEnableManager: {message}")
return False, message
def get_file_info(self) -> Dict[str, Any]:
"""
Get information about the providers_enabled.json file.
Returns:
Dictionary with file information
"""
data = self._load_file()
return {
"filename": self.DEFAULT_FILENAME,
"version": data.get("version", self.DEFAULT_VERSION),
"last_updated": data.get("last_updated"),
"provider_count": len(data.get("providers", {})),
"file_exists": self.vfs.exists(self.DEFAULT_FILENAME),
2026-01-06 16:41:03 +01:00
"file_path": (
self.vfs.get_file_path(self.DEFAULT_FILENAME)
if hasattr(self.vfs, "get_file_path")
else "vfs"
),
2025-12-12 12:03:13 +01:00
}
def migrate_from_kodi(self) -> Tuple[bool, str, int]:
"""
Migrate all Kodi enable settings to the file.
Only copies settings that are explicitly set in Kodi.
Returns:
Tuple of (success: bool, message: str, migrated_count: int)
"""
if not is_kodi_environment() or not self._kodi_bridge:
return False, "Not in Kodi environment or Kodi bridge not available", 0
try:
# Load current file data
data = self._load_file()
migrated_count = 0
# Discover all providers from Kodi settings
try:
kodi_providers = self._kodi_bridge.discover_all_providers()
except AttributeError:
# Fallback if discover_all_providers not available
kodi_providers = {}
# Check each provider
for provider_name in kodi_providers:
kodi_enabled = self._get_kodi_enabled_status(provider_name)
if kodi_enabled is not None:
# Kodi has explicit setting, migrate to file
data["providers"][provider_name] = kodi_enabled
migrated_count += 1
2026-01-16 12:24:41 +01:00
logger.debug(f"ProviderEnableManager: Migrated {provider_name}={kodi_enabled}")
2025-12-12 12:03:13 +01:00
if migrated_count > 0:
# Save migrated data
success = self._save_file(data)
if success:
2026-01-16 12:24:41 +01:00
message = f"Migrated {migrated_count} provider settings from Kodi to file"
2025-12-12 12:03:13 +01:00
logger.info(f"ProviderEnableManager: {message}")
return True, message, migrated_count
else:
message = "Failed to save migrated settings"
logger.error(f"ProviderEnableManager: {message}")
return False, message, 0
else:
message = "No Kodi provider settings found to migrate"
logger.info(f"ProviderEnableManager: {message}")
return True, message, 0
except Exception as e:
message = f"Error migrating from Kodi: {e}"
logger.error(f"ProviderEnableManager: {message}")
2026-01-06 16:41:03 +01:00
return False, message, 0