Add web config

This commit is contained in:
Nirvana
2025-12-12 20:45:04 +01:00
parent 2f4883e2fe
commit b6700b17d3
4 changed files with 221 additions and 2 deletions
+1
View File
@@ -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
+2 -1
View File
@@ -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
@@ -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)
+175 -1
View File
@@ -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"""