Files
script.service.ultimate/lib/streaming_providers/base/utils/environment.py
T

332 lines
12 KiB
Python
Raw Normal View History

2025-12-10 13:56:11 +01:00
# streaming_providers/base/utils/environment.py
"""
Central environment detection and service management.
Provides unified access to VFS, logger, settings bridge, and other services.
"""
2026-01-06 16:41:03 +01:00
import json
2025-12-10 13:56:11 +01:00
import os
import sys
from pathlib import Path
2026-01-06 16:41:03 +01:00
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
2025-12-10 13:56:11 +01:00
# Type hints to avoid circular imports
if TYPE_CHECKING:
from ..settings.kodi_settings_bridge import KodiSettingsBridge
2026-01-06 16:41:03 +01:00
from .logger import BaseLogger # Changed from 'logger' to 'BaseLogger'
from .vfs import VFS
2025-12-10 13:56:11 +01:00
class EnvironmentManager:
"""
Central manager for environment detection and service coordination.
"""
2026-01-06 16:41:03 +01:00
_instance: Optional["EnvironmentManager"] = None
2025-12-10 13:56:11 +01:00
2026-01-06 16:41:03 +01:00
def __new__(cls) -> "EnvironmentManager":
2025-12-10 13:56:11 +01:00
if cls._instance is None:
cls._instance = super(EnvironmentManager, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self) -> None:
if self._initialized:
return
self._initialized = True
# Check for Kodi availability
try:
import xbmcaddon
import xbmcvfs
2026-01-06 16:41:03 +01:00
2025-12-10 13:56:11 +01:00
self._is_kodi = True
self._kodi_import_error: Optional[Exception] = None
except ImportError as import_err:
self._is_kodi = False
self._kodi_import_error = import_err
self._addon: Any = None
self._config: Dict[str, Any] = {}
self._services: Dict[str, Any] = {}
# Initialize based on environment
if self._is_kodi:
self._init_kodi()
else:
self._init_standalone()
self._load_config()
def _init_kodi(self) -> None:
"""Initialize Kodi-specific components"""
try:
# Import inside the method where we know Kodi is available
import xbmcaddon as kodi_xbmcaddon
import xbmcvfs as kodi_xbmcvfs
2025-12-16 13:22:44 +01:00
2025-12-16 13:28:34 +01:00
# Create addon instance
2025-12-10 13:56:11 +01:00
self._addon = kodi_xbmcaddon.Addon()
2026-01-06 16:41:03 +01:00
self._config["environment"] = "kodi"
self._config["addon_id"] = self._addon.getAddonInfo("id")
self._config["addon_name"] = self._addon.getAddonInfo("name")
self._config["addon_version"] = self._addon.getAddonInfo("version")
self._config["addon_path"] = self._addon.getAddonInfo("path")
2025-12-10 13:56:11 +01:00
# Get profile path
2026-01-06 16:41:03 +01:00
profile_info = self._addon.getAddonInfo("profile")
2025-12-10 13:56:11 +01:00
profile_path = kodi_xbmcvfs.translatePath(profile_info)
2026-01-06 16:41:03 +01:00
self._config["profile_path"] = str(profile_path)
2025-12-10 13:56:11 +01:00
# Get settings
2026-01-06 16:41:03 +01:00
default_country = self._addon.getSetting("default_country")
2026-01-23 15:52:35 +01:00
self._config["default_country"] = (
str(default_country) if default_country else "DE"
)
2025-12-10 13:56:11 +01:00
2026-01-06 16:41:03 +01:00
server_port = self._addon.getSetting("server_port")
2025-12-10 13:56:11 +01:00
try:
2026-01-23 15:52:35 +01:00
self._config["server_port"] = (
int(str(server_port)) if server_port else 7777
)
2025-12-16 13:44:22 +01:00
except ValueError:
2026-01-06 16:41:03 +01:00
self._config["server_port"] = 7777
2025-12-10 13:56:11 +01:00
2025-12-16 13:44:22 +01:00
except Exception as init_error: # noqa: B902
2026-01-23 15:52:35 +01:00
print(
f"DEBUG: Exception type: {type(init_error).__name__}", file=sys.stderr
)
2025-12-16 13:22:44 +01:00
print(f"DEBUG: Exception message: {str(init_error)}", file=sys.stderr)
2025-12-10 13:56:11 +01:00
# Log the error and fallback to standalone
self._log_init_error("Kodi initialization failed", init_error)
self._is_kodi = False
self._init_standalone()
@staticmethod
def _log_init_error(message: str, error: Exception) -> None:
"""Log initialization errors (static method)"""
# We can't use logger here yet, so print to stderr
print(f"{message}: {error}", file=sys.stderr)
def _init_standalone(self) -> None:
"""Initialize standalone mode components"""
2026-01-06 16:41:03 +01:00
self._config["environment"] = "standalone"
self._config["addon_id"] = "ultimate-backend-standalone"
self._config["addon_name"] = "Ultimate Backend"
self._config["addon_version"] = "1.0.0"
self._config["addon_path"] = os.path.dirname(os.path.abspath(__file__))
2025-12-10 13:56:11 +01:00
# Default configuration paths
2026-01-23 15:52:35 +01:00
config_home = os.environ.get("XDG_CONFIG_HOME") or os.path.join(
str(Path.home()), ".config"
)
2026-01-06 16:41:03 +01:00
self._config["config_dir"] = os.path.join(config_home, "ultimate-backend")
self._config["profile_path"] = self._config["config_dir"]
2025-12-10 13:56:11 +01:00
# Load environment variables with defaults
2026-01-06 16:41:03 +01:00
self._config["default_country"] = os.environ.get("DEFAULT_COUNTRY", "DE")
2025-12-10 13:56:11 +01:00
try:
2026-01-06 16:41:03 +01:00
self._config["server_port"] = int(os.environ.get("SERVER_PORT", "7777"))
2025-12-10 13:56:11 +01:00
except ValueError as port_error:
print(f"Invalid server port, using default: {port_error}", file=sys.stderr)
2026-01-06 16:41:03 +01:00
self._config["server_port"] = 7777
2025-12-10 13:56:11 +01:00
# Ensure config directory exists
try:
2026-01-06 16:41:03 +01:00
os.makedirs(self._config["config_dir"], exist_ok=True)
2025-12-10 13:56:11 +01:00
except OSError as dir_error:
print(f"Failed to create config directory: {dir_error}", file=sys.stderr)
# Use temp directory as fallback
import tempfile
2026-01-06 16:41:03 +01:00
self._config["config_dir"] = tempfile.mkdtemp(prefix="ultimate-backend-")
self._config["profile_path"] = self._config["config_dir"]
2025-12-10 13:56:11 +01:00
def _load_config(self) -> None:
"""Load additional configuration from files"""
2026-01-06 16:41:03 +01:00
config_file = os.path.join(self._config["profile_path"], "config.json")
2025-12-10 13:56:11 +01:00
if os.path.exists(config_file):
try:
2026-01-06 16:41:03 +01:00
with open(config_file, "r", encoding="utf-8") as f:
2025-12-10 13:56:11 +01:00
file_config = json.load(f)
# Update config with file contents
for key, value in file_config.items():
if isinstance(value, (str, int, float, bool, type(None))):
self._config[key] = value
except json.JSONDecodeError as json_error:
print(f"Invalid JSON in config file: {json_error}", file=sys.stderr)
except OSError as io_error:
print(f"Failed to read config file: {io_error}", file=sys.stderr)
def is_kodi(self) -> bool:
"""Check if running in Kodi environment"""
return self._is_kodi
def get_environment(self) -> str:
"""Get current environment name"""
2026-01-06 16:41:03 +01:00
return str(self._config.get("environment", "unknown"))
2025-12-10 13:56:11 +01:00
def get_config(self, key: str, default: Any = None) -> Any:
"""Get configuration value"""
return self._config.get(key, default)
def set_config(self, key: str, value: Union[str, int, float, bool, None]) -> None:
"""Set configuration value"""
self._config[key] = value
# Auto-save to config file in standalone mode
if not self._is_kodi:
2026-01-06 16:41:03 +01:00
config_file = os.path.join(self._config["profile_path"], "config.json")
2025-12-10 13:56:11 +01:00
try:
# Read existing config
existing_config = {}
if os.path.exists(config_file):
2026-01-06 16:41:03 +01:00
with open(config_file, "r", encoding="utf-8") as f:
2025-12-10 13:56:11 +01:00
existing_config = json.load(f)
# Update with new value
existing_config[key] = value
# Write back
2026-01-06 16:41:03 +01:00
with open(config_file, "w", encoding="utf-8") as f:
2025-12-10 13:56:11 +01:00
json.dump(existing_config, f, indent=2, ensure_ascii=False)
except OSError as save_error:
print(f"Failed to save config: {save_error}", file=sys.stderr)
except (TypeError, ValueError) as type_error:
2026-01-06 16:41:03 +01:00
print(
f"Config contains non-serializable data: {type_error}",
file=sys.stderr,
)
2025-12-10 13:56:11 +01:00
2026-01-06 16:41:03 +01:00
def get_vfs(self, subdir: str = "", config_dir: Optional[str] = None) -> "VFS":
2025-12-10 13:56:11 +01:00
"""Get VFS instance with appropriate configuration"""
# Import here to avoid circular imports
from .vfs import VFS
# Use provided config_dir, or environment default
if config_dir is None:
2026-01-06 16:41:03 +01:00
config_dir = self._config.get("profile_path", "")
2025-12-10 13:56:11 +01:00
return VFS(config_dir=config_dir, addon_subdir=subdir)
@staticmethod
2026-01-06 16:41:03 +01:00
def get_logger() -> "BaseLogger":
2025-12-10 13:56:11 +01:00
"""Get logger instance for current environment (static method)"""
# Import here to avoid circular imports
from .logger import logger as logger_instance
2026-01-06 16:41:03 +01:00
2025-12-10 13:56:11 +01:00
return logger_instance
2026-01-06 16:41:03 +01:00
def get_settings_bridge(
self, addon_id: Optional[str] = None, config_dir: Optional[str] = None
) -> "KodiSettingsBridge":
2025-12-10 13:56:11 +01:00
"""Get settings bridge instance"""
# Import here to avoid circular imports
from ..settings.kodi_settings_bridge import KodiSettingsBridge
if config_dir is None:
2026-01-06 16:41:03 +01:00
config_dir = self._config.get("profile_path", "")
2025-12-10 13:56:11 +01:00
return KodiSettingsBridge(addon_id=addon_id, config_dir=config_dir)
def get_manager(self) -> Any:
"""Get the configured streaming provider manager"""
try:
# Lazy import to avoid circular dependencies
import importlib
2026-01-06 16:41:03 +01:00
2025-12-10 13:56:11 +01:00
# Adjust this import path based on your actual module structure
2026-01-06 16:41:03 +01:00
module = importlib.import_module("streaming_providers")
if hasattr(module, "get_configured_manager"):
2025-12-10 13:56:11 +01:00
manager_func = module.get_configured_manager
if callable(manager_func):
return manager_func()
else:
raise ImportError("get_configured_manager is not callable")
else:
2026-01-23 15:52:35 +01:00
raise ImportError(
"get_configured_manager not found in streaming_providers module"
)
2025-12-10 13:56:11 +01:00
except ImportError as manager_error:
# Log error using the logger once we have it
logger_instance = self.get_logger()
logger_instance.error(f"Failed to import manager: {manager_error}")
raise
def get_service_config(self) -> Dict[str, Any]:
"""Get configuration for running the service"""
return {
2026-01-06 16:41:03 +01:00
"is_kodi": self._is_kodi,
"port": self._config.get("server_port", 7777),
"default_country": self._config.get("default_country", "DE"),
"profile_path": self._config.get("profile_path", ""),
"addon_path": self._config.get("addon_path", ""),
2025-12-10 13:56:11 +01:00
}
def debug_info(self) -> Dict[str, Any]:
"""Get debug information about the environment"""
info: Dict[str, Any] = {
2026-01-06 16:41:03 +01:00
"environment": self.get_environment(),
"is_kodi": self._is_kodi,
"python_version": sys.version,
"platform": sys.platform,
2025-12-10 13:56:11 +01:00
}
# Add Kodi-specific info if available
if self._is_kodi and self._addon:
2026-01-06 16:41:03 +01:00
info["kodi_addon_id"] = self._addon.getAddonInfo("id")
info["kodi_addon_version"] = self._addon.getAddonInfo("version")
2025-12-10 13:56:11 +01:00
# Add import error info if present
2026-01-06 16:41:03 +01:00
if hasattr(self, "_kodi_import_error") and self._kodi_import_error:
info["kodi_import_error"] = str(self._kodi_import_error)
2025-12-10 13:56:11 +01:00
# Create a safe config summary without sensitive paths
safe_config: Dict[str, Any] = {}
for key, value in self._config.items():
2026-01-06 16:41:03 +01:00
if (
not key.endswith("_path")
and key != "config_dir"
and key not in ["profile_path", "addon_path"]
):
2025-12-10 13:56:11 +01:00
if isinstance(value, (str, int, float, bool, type(None))):
safe_config[key] = value
else:
safe_config[key] = str(type(value))
2026-01-06 16:41:03 +01:00
info["config_summary"] = safe_config
2025-12-10 13:56:11 +01:00
return info
# Global singleton instance
_env_manager: Optional[EnvironmentManager] = None
def get_environment_manager() -> EnvironmentManager:
"""Get the global environment manager instance"""
global _env_manager
if _env_manager is None:
_env_manager = EnvironmentManager()
return _env_manager
2025-12-16 13:53:31 +01:00
# Convenience functions for common operations
2025-12-10 13:56:11 +01:00
def is_kodi_environment() -> bool:
"""Check if we're running in Kodi environment (convenience function)"""
2025-12-16 13:53:31 +01:00
return get_environment_manager().is_kodi()
2025-12-10 13:56:11 +01:00
2026-01-06 16:41:03 +01:00
def get_logger_instance() -> "BaseLogger":
2025-12-10 13:56:11 +01:00
"""Get logger instance (convenience function)"""
return EnvironmentManager.get_logger()
2026-01-06 16:41:03 +01:00
def get_vfs_instance(subdir: str = "", config_dir: Optional[str] = None) -> "VFS":
2025-12-10 13:56:11 +01:00
"""Get VFS instance (convenience function)"""
2026-01-06 16:41:03 +01:00
return get_environment_manager().get_vfs(subdir, config_dir)