Files
2026-01-16 12:24:41 +01:00

520 lines
18 KiB
Python

# 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
from typing import Any, Dict, Optional, Tuple
from ..utils.environment import get_vfs_instance, is_kodi_environment
from ..utils.logger import logger
class EnableSource(Enum):
"""Source of the enable setting"""
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:
# Fixed import path - kodi_settings_bridge is in the same directory
from .kodi_settings_bridge import KodiSettingsBridge
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
except Exception as e:
logger.error(f"ProviderEnableManager: Error initializing Kodi bridge: {e}")
self._kodi_bridge = None
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
if not force_reload and self._cache and (current_time - self._cache_time) < self.CACHE_TTL:
return self._cache
# Initialize default structure
default_data = {"version": self.DEFAULT_VERSION, "providers": {}}
# Check if file exists
if not self.vfs.exists(self.DEFAULT_FILENAME):
self._cache = default_data
self._cache_time = current_time
logger.debug(
f"ProviderEnableManager: File {self.DEFAULT_FILENAME} not found, using defaults"
)
return default_data
try:
# Read file via VFS
data = self.vfs.read_json(self.DEFAULT_FILENAME)
if not isinstance(data, dict):
logger.warning(f"ProviderEnableManager: Invalid file format, using defaults")
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):
logger.warning(
f"ProviderEnableManager: Invalid value for {provider}, removing"
)
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
logger.debug(
f"ProviderEnableManager: Loaded {len(data['providers'])} providers from file"
)
return data
except Exception as e:
logger.error(f"ProviderEnableManager: Error loading {self.DEFAULT_FILENAME}: {e}")
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()
logger.debug(
f"ProviderEnableManager: Saved {len(data.get('providers', {}))} providers to file"
)
else:
logger.error(f"ProviderEnableManager: Failed to write to {self.DEFAULT_FILENAME}")
return success
except Exception as e:
logger.error(f"ProviderEnableManager: Error saving {self.DEFAULT_FILENAME}: {e}")
return False
def _get_kodi_enabled_status(self, provider_name: str) -> Optional[bool]:
"""
Get enabled status from Kodi settings.
Args:
provider_name: Provider name (e.g., "joyn_de", "rtlplus")
Returns:
True/False if setting exists in Kodi, None if not set
"""
if not is_kodi_environment() or not self._kodi_bridge:
return None
try:
# Parse provider_name for potential country suffix
parts = provider_name.rsplit("_", 1)
# Check country-specific setting first (e.g., "enable_joyn_de")
if len(parts) == 2 and len(parts[1]) in (2, 3): # Country code length
provider, country = parts
country_setting = f"enable_{provider}_{country}"
# Try country-specific setting
country_value = self._kodi_bridge.get_setting(country_setting)
if country_value:
enabled = country_value.lower() in ["true", "1", "yes"]
logger.debug(
f"ProviderEnableManager: Found Kodi setting {country_setting}={enabled}"
)
return enabled
# Try general provider setting (e.g., "enable_joyn")
general_setting = f"enable_{provider_name}"
general_value = self._kodi_bridge.get_setting(general_setting)
if general_value:
enabled = general_value.lower() in ["true", "1", "yes"]
logger.debug(
f"ProviderEnableManager: Found Kodi setting {general_setting}={enabled}"
)
return enabled
# No Kodi setting found
logger.debug(f"ProviderEnableManager: No Kodi setting found for {provider_name}")
return None
except Exception as e:
logger.warning(
f"ProviderEnableManager: Error reading Kodi setting for {provider_name}: {e}"
)
return None
def is_provider_enabled(self, provider_name: str) -> bool:
"""
Check if provider is enabled using precedence rules.
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
"""
# 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
def set_provider_enabled(self, provider_name: str, enabled: bool) -> Tuple[bool, str]:
"""
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:
message = (
f"Provider '{provider_name}' is controlled by Kodi settings. "
f"Change the setting in Kodi addon settings."
)
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,
"source": source.value, # Convert enum to string
"can_modify": source != EnableSource.KODI,
}
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,
"source": source.value, # Convert enum to string
"can_modify": source != EnableSource.KODI,
"in_file": True,
}
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:
message = f"Cannot delete setting for '{provider_name}' - controlled by Kodi"
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(),
"providers": {},
}
# 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),
"file_path": (
self.vfs.get_file_path(self.DEFAULT_FILENAME)
if hasattr(self.vfs, "get_file_path")
else "vfs"
),
}
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
logger.debug(f"ProviderEnableManager: Migrated {provider_name}={kodi_enabled}")
if migrated_count > 0:
# Save migrated data
success = self._save_file(data)
if success:
message = f"Migrated {migrated_count} provider settings from Kodi to file"
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}")
return False, message, 0