From b6700b17d370029ac798e0ca21243e8a5debc0d8 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Fri, 12 Dec 2025 20:45:04 +0100 Subject: [PATCH] Add web config --- Dockerfile | 1 + docker-compose.yml | 3 +- .../base/epg/epg_manager.py | 43 +++++ service.py | 176 +++++++++++++++++- 4 files changed, 221 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 05da2d9..57f2b06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ ENV PYTHONUNBUFFERED=1 \ ULTIMATE_PORT=7777 \ ULTIMATE_COUNTRY=DE \ ULTIMATE_DEBUG=false \ + ULTIMATE_EPG_URL="https://example.com/epg.xml.gz" \ PYTHONPATH=/app/lib # Install system dependencies diff --git a/docker-compose.yml b/docker-compose.yml index c97a401..27f8a10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,10 +11,11 @@ services: container_name: ultimate-backend restart: unless-stopped ports: - - "7777:7777" + - 7777:7777 environment: - ULTIMATE_PORT=7777 - ULTIMATE_DEBUG=false + - ULTIMATE_EPG_URL=https://raw.githubusercontent.com/epgshare01/share01/master/epg.xml.gz - TZ=${TZ:-Europe/Berlin} volumes: - ./config:/config diff --git a/lib/streaming_providers/base/epg/epg_manager.py b/lib/streaming_providers/base/epg/epg_manager.py index db0f1ab..bdd08cf 100644 --- a/lib/streaming_providers/base/epg/epg_manager.py +++ b/lib/streaming_providers/base/epg/epg_manager.py @@ -42,6 +42,7 @@ from .epg_mapping import EPGMapping from .epg_parser import EPGParser from ..models.epg_models import EPGEntry from ..utils.logger import logger +from ..utils.environment import get_environment_manager class EPGManager: @@ -359,6 +360,47 @@ class EPGManager: """ return self.parser.get_provider_from_broadcast_id(broadcast_id) + def _determine_epg_url(self, explicit_url: Optional[str]) -> str: + """ + Determine EPG URL with priority: + 1. Explicit parameter + 2. config.json (via environment manager) + 3. Environment variable ULTIMATE_EPG_URL + 4. Kodi addon setting (if in Kodi) + 5. Default + """ + import os + + # 1. Explicit parameter + if explicit_url: + logger.debug(f"EPGManager: Using explicit EPG URL: {explicit_url}") + return explicit_url + + # 2. config.json via environment manager + try: + env_manager = get_environment_manager() + config_url = env_manager.get_config('epg_url') + if config_url: + logger.info(f"EPGManager: Using EPG URL from config.json: {config_url}") + return config_url + except Exception as e: + logger.debug(f"Could not get EPG URL from environment manager: {e}") + + # 3. Environment variable + env_url = os.environ.get('ULTIMATE_EPG_URL') + if env_url and env_url.strip(): # Check for non-empty string + logger.info(f"EPGManager: Using EPG URL from environment: {env_url}") + return env_url.strip() + + # 4. Kodi addon setting + settings_url = self._get_epg_url_from_settings() + if settings_url: + return settings_url + + # 5. Default + logger.info(f"EPGManager: Using default EPG URL: {self.DEFAULT_EPG_URL}") + return self.DEFAULT_EPG_URL + @staticmethod def verify_broadcast_id_provider(broadcast_id: int, provider_name: str) -> bool: """ @@ -372,3 +414,4 @@ class EPGManager: True if broadcast_id was generated for this provider """ return EPGEntry.verify_provider(broadcast_id, provider_name) + diff --git a/service.py b/service.py index 0fbf2cd..d0b6088 100644 --- a/service.py +++ b/service.py @@ -21,6 +21,8 @@ try: from streaming_providers.base.utils.environment import get_environment_manager, get_vfs_instance from streaming_providers.base.utils.environment import is_kodi_environment from streaming_providers.base.settings.provider_enable_manager import ProviderEnableManager + # Add EPG Manager import + from streaming_providers.base.epg.epg_manager import EPGManager except ImportError as import_err: print(f"Ultimate Backend: Critical import failed - {str(import_err)}", file=sys.stderr) raise @@ -56,8 +58,17 @@ class UltimateService: self.mpd_cache = MPDCacheManager() logger.info(f"MPD cache initialized: {self.mpd_cache.vfs.base_path}") - self.setup_routes() + # Initialize EPG Manager + self.epg_manager = None + try: + self.epg_manager = EPGManager() + logger.info("EPG Manager initialized successfully") + except ImportError as e: + logger.warning(f"Could not import EPG Manager: {e}") + except Exception as e: + logger.warning(f"Could not initialize EPG Manager: {e}") + self.setup_routes() self.config_html = self._load_config_html() def _load_config_html(self): @@ -1863,6 +1874,169 @@ class UltimateService: 'message': f'Imported {imported_count} configurations' } + @self.app.route('/api/config/epg', method='GET') + def get_epg_config(): + """Get current EPG configuration""" + try: + # Use the already imported get_environment_manager + env_mgr = get_environment_manager() + + config = { + 'epg_url': env_mgr.get_config('epg_url', ''), + 'epg_cache_ttl': env_mgr.get_config('epg_cache_ttl', 86400), + 'source': 'config.json' if env_mgr.get_config('epg_url') else 'default' + } + + # Also check environment variable for reference + import os + env_epg_url = os.environ.get('ULTIMATE_EPG_URL') + if env_epg_url: + config['environment_variable'] = env_epg_url + + return { + 'success': True, + 'config': config, + 'epg_manager_status': 'initialized' if hasattr(self, 'epg_manager') else 'not_initialized' + } + except Exception as e: + logger.error(f"API Error in /api/config/epg: {str(e)}") + response.status = 500 + return {'error': f'Internal server error: {str(e)}'} + + @self.app.route('/api/config/epg', method='POST') + def set_epg_config(): + """Set EPG configuration""" + try: + # Parse JSON body + try: + epg_data = request.json + except Exception as json_err: + logger.error(f"Invalid JSON in request body: {json_err}") + response.status = 400 + return {'error': 'Invalid JSON in request body'} + + if not epg_data: + response.status = 400 + return {'error': 'Request body must contain EPG configuration'} + + # Validate it's a dictionary + if not isinstance(epg_data, dict): + response.status = 400 + return {'error': 'EPG data must be a JSON object'} + + # Validate URL format + epg_url = epg_data.get('epg_url', '').strip() + if epg_url: + # Basic URL validation + if not (epg_url.startswith('http://') or epg_url.startswith('https://')): + response.status = 400 + return {'error': 'EPG URL must start with http:// or https://'} + + # Validate it's an XML/GZ file + if not (epg_url.endswith('.xml') or epg_url.endswith('.xml.gz') or epg_url.endswith('.gz')): + logger.warning(f"EPG URL doesn't end with .xml or .gz: {epg_url}") + + # Use environment manager + env_mgr = get_environment_manager() + + # Get current config + import os + import json + profile_path = env_mgr.get_config('profile_path', '') + config_file = os.path.join(profile_path, 'config.json') + config_data = {} + + if os.path.exists(config_file): + try: + with open(config_file, 'r', encoding='utf-8') as f: + config_data = json.load(f) + except Exception as e: + logger.error(f"Error reading config.json: {e}") + config_data = {} + + # Update with new values + config_data['epg_url'] = epg_url + + # Optional: EPG cache TTL + if 'epg_cache_ttl' in epg_data: + try: + ttl = int(epg_data['epg_cache_ttl']) + if ttl > 0: + config_data['epg_cache_ttl'] = ttl + except ValueError: + pass + + # Save back to config.json + try: + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(config_data, f, indent=2, ensure_ascii=False) + + # Update environment manager cache + env_mgr.set_config('epg_url', epg_url) + if 'epg_cache_ttl' in config_data: + env_mgr.set_config('epg_cache_ttl', config_data['epg_cache_ttl']) + + logger.info(f"Updated EPG configuration: URL={epg_url}") + + return { + 'success': True, + 'message': 'EPG configuration updated successfully', + 'config': { + 'epg_url': epg_url, + 'epg_cache_ttl': config_data.get('epg_cache_ttl', 86400) + } + } + + except Exception as e: + logger.error(f"Error writing config.json: {e}") + response.status = 500 + return {'error': f'Failed to save configuration: {str(e)}'} + + except Exception as e: + logger.error(f"API Error in POST /api/config/epg: {str(e)}") + response.status = 500 + return {'error': f'Internal server error: {str(e)}'} + + @self.app.route('/api/config/epg/clear-cache', method='POST') + def clear_epg_cache(): + """Clear EPG cache""" + try: + if hasattr(self, 'epg_manager') and self.epg_manager: + success = self.epg_manager.clear_cache() + if success: + return {'success': True, 'message': 'EPG cache cleared'} + else: + response.status = 500 + return {'error': 'Failed to clear EPG cache'} + else: + response.status = 404 + return {'error': 'EPG manager not initialized'} + except Exception as e: + logger.error(f"API Error clearing EPG cache: {str(e)}") + response.status = 500 + return {'error': f'Internal server error: {str(e)}'} + + @self.app.route('/api/config/epg/cache-info', method='GET') + def get_epg_cache_info(): + """Get EPG cache information""" + try: + if hasattr(self, 'epg_manager') and self.epg_manager: + cache_info = self.epg_manager.get_cache_info() + mapping_stats = self.epg_manager.get_mapping_stats() + + return { + 'success': True, + 'cache_info': cache_info, + 'mapping_stats': mapping_stats + } + else: + response.status = 404 + return {'error': 'EPG manager not initialized'} + except Exception as e: + logger.error(f"API Error getting EPG cache info: {str(e)}") + response.status = 500 + return {'error': f'Internal server error: {str(e)}'} + @self.app.route('/config') def serve_config_ui(): """Serve the web configuration interface"""