some routing fixes

This commit is contained in:
Lukas Pupka-Lipinski 2025-09-29 15:53:18 +02:00
parent 54ca564db8
commit 083eefe697
5 changed files with 7765 additions and 37 deletions

View File

@ -89,11 +89,26 @@ app.register_blueprint(health_bp)
# Keep only series_app for backward compatibility # Keep only series_app for backward compatibility
series_app = None series_app = None
def init_series_app(): def init_series_app(verbose=True):
"""Initialize the SeriesApp with configuration directory.""" """Initialize the SeriesApp with configuration directory."""
global series_app global series_app
try:
directory_to_search = config.anime_directory directory_to_search = config.anime_directory
if verbose:
print(f"Initializing SeriesApp with directory: {directory_to_search}")
series_app = SeriesApp(directory_to_search) series_app = SeriesApp(directory_to_search)
if verbose:
print(f"SeriesApp initialized successfully. List length: {len(series_app.List.GetList()) if series_app.List else 'No List'}")
return series_app
except Exception as e:
print(f"Error initializing SeriesApp: {e}")
import traceback
traceback.print_exc()
return None
def get_series_app():
"""Get the current series app instance."""
global series_app
return series_app return series_app
# Register WebSocket handlers # Register WebSocket handlers
@ -172,10 +187,7 @@ if __name__ == '__main__':
# Only run initialization and logging setup in the main process # Only run initialization and logging setup in the main process
# This prevents duplicate initialization when Flask debug reloader starts # This prevents duplicate initialization when Flask debug reloader starts
# Initialize the series app # Configure enhanced logging system first
init_series_app()
# Configure enhanced logging system
try: try:
from server.infrastructure.logging.config import get_logger, logging_config from server.infrastructure.logging.config import get_logger, logging_config
logger = get_logger(__name__, 'webapp') logger = get_logger(__name__, 'webapp')
@ -186,7 +198,34 @@ if __name__ == '__main__':
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.warning("Using fallback logging - enhanced logging not available") logger.warning("Using fallback logging - enhanced logging not available")
# Only display startup messages if we're not in the reloader child process if __name__ == '__main__':
# Configure enhanced logging system first
try:
from server.infrastructure.logging.config import get_logger, logging_config
logger = get_logger(__name__, 'webapp')
logger.info("Enhanced logging system initialized")
except ImportError:
# Fallback to basic logging with UTF-8 support
import logging
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(levelname)s: %(message)s',
datefmt='%H:%M:%S',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
logger.warning("Using fallback logging - enhanced logging not available")
# Try to configure console for UTF-8 on Windows
try:
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
except Exception:
pass
# Only run startup messages and scheduler in the parent process
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
logger.info("Starting Aniworld Flask server...") logger.info("Starting Aniworld Flask server...")
logger.info(f"Anime directory: {config.anime_directory}") logger.info(f"Anime directory: {config.anime_directory}")
@ -200,6 +239,9 @@ if __name__ == '__main__':
logger.info("Scheduled operations disabled") logger.info("Scheduled operations disabled")
logger.info("Server will be available at http://localhost:5000") logger.info("Server will be available at http://localhost:5000")
else:
# Initialize the series app only in the reloader child process (the actual working process)
init_series_app(verbose=True)
try: try:
# Run with SocketIO # Run with SocketIO

View File

@ -12,6 +12,43 @@ from typing import Optional
from config import config from config import config
class UnicodeStreamHandler(logging.StreamHandler):
"""Custom stream handler that safely handles Unicode characters."""
def __init__(self, stream=None):
super().__init__(stream)
def emit(self, record):
try:
msg = self.format(record)
stream = self.stream
# Handle Unicode encoding issues on Windows
if hasattr(stream, 'encoding') and stream.encoding:
try:
# Try to encode with the stream's encoding
encoded_msg = msg.encode(stream.encoding, errors='replace').decode(stream.encoding)
stream.write(encoded_msg + self.terminator)
except (UnicodeEncodeError, UnicodeDecodeError):
# Fallback: replace problematic characters
safe_msg = msg.encode('ascii', errors='replace').decode('ascii')
stream.write(safe_msg + self.terminator)
else:
# No encoding info, write directly but catch errors
try:
stream.write(msg + self.terminator)
except UnicodeEncodeError:
# Last resort: ASCII-only output
safe_msg = msg.encode('ascii', errors='replace').decode('ascii')
stream.write(safe_msg + self.terminator)
self.flush()
except RecursionError:
raise
except Exception:
self.handleError(record)
class Fail2BanFormatter(logging.Formatter): class Fail2BanFormatter(logging.Formatter):
"""Custom formatter for fail2ban compatible authentication failure logs.""" """Custom formatter for fail2ban compatible authentication failure logs."""
@ -33,8 +70,14 @@ class StructuredFormatter(logging.Formatter):
# Add component info # Add component info
component = getattr(record, 'component', record.name) component = getattr(record, 'component', record.name)
# Safely get message and handle Unicode
try:
message = record.getMessage()
except (UnicodeEncodeError, UnicodeDecodeError):
message = str(record.msg)
# Format: timestamp - level - component - function - message # Format: timestamp - level - component - function - message
formatted = f"{record.asctime} - {record.levelname:8} - {component:15} - {record.funcName:20} - {record.getMessage()}" formatted = f"{record.asctime} - {record.levelname:8} - {component:15} - {record.funcName:20} - {message}"
# Add exception info if present # Add exception info if present
if record.exc_info: if record.exc_info:
@ -49,7 +92,16 @@ class ConsoleOnlyFormatter(logging.Formatter):
def format(self, record): def format(self, record):
# Only show timestamp, level and message for console # Only show timestamp, level and message for console
timestamp = datetime.now().strftime('%H:%M:%S') timestamp = datetime.now().strftime('%H:%M:%S')
return f"[{timestamp}] {record.levelname}: {record.getMessage()}" try:
message = record.getMessage()
# Ensure the message can be safely encoded
if isinstance(message, str):
# Replace problematic Unicode characters with safe alternatives
message = message.encode('ascii', errors='replace').decode('ascii')
except (UnicodeEncodeError, UnicodeDecodeError):
message = str(record.msg)
return f"[{timestamp}] {record.levelname}: {message}"
class LoggingConfig: class LoggingConfig:
@ -96,7 +148,7 @@ class LoggingConfig:
# Console handler (optional, controlled by config) # Console handler (optional, controlled by config)
if console_logging: if console_logging:
console_handler = logging.StreamHandler(sys.stdout) console_handler = UnicodeStreamHandler(sys.stdout)
console_handler.setLevel(numeric_level) console_handler.setLevel(numeric_level)
console_handler.setFormatter(ConsoleOnlyFormatter()) console_handler.setFormatter(ConsoleOnlyFormatter())
root_logger.addHandler(console_handler) root_logger.addHandler(console_handler)

File diff suppressed because it is too large Load Diff

38
src/server/test_api.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""
Simple script to test the API endpoint without crashing the server.
"""
import requests
import json
import time
def test_api():
url = "http://localhost:5000/api/series"
try:
print("Testing API endpoint...")
response = requests.get(url, timeout=30)
print(f"Status Code: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"Response status: {data.get('status', 'unknown')}")
print(f"Total series: {data.get('total_series', 0)}")
print(f"Message: {data.get('message', 'No message')}")
# Print first few series
series = data.get('series', [])
if series:
print(f"\nFirst 3 series:")
for i, serie in enumerate(series[:3]):
print(f" {i+1}. {serie.get('name', 'Unknown')} ({serie.get('folder', 'Unknown folder')})")
else:
print("No series found in response")
else:
print(f"Error: {response.text}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
test_api()

View File

@ -79,26 +79,30 @@ def init_series_app():
"""Initialize the SeriesApp with configuration directory.""" """Initialize the SeriesApp with configuration directory."""
global series_app global series_app
from config import config from config import config
from main import SeriesApp from Main import SeriesApp
directory_to_search = config.anime_directory directory_to_search = config.anime_directory
series_app = SeriesApp(directory_to_search) series_app = SeriesApp(directory_to_search)
return series_app return series_app
def get_series_app(): def get_series_app():
"""Get the current series app instance, initializing if needed.""" """Get the current series app instance from the main app."""
global series_app global series_app
if series_app is None:
# Try to get it from the main app module first
try: try:
print("API: Attempting to get series app from main app...")
import app import app
if hasattr(app, 'series_app') and app.series_app is not None: series_app_from_main = app.get_series_app()
series_app = app.series_app print(f"API: Got series app from main app: {series_app_from_main is not None}")
return series_app return series_app_from_main
except ImportError: except ImportError as ie:
pass print(f"API: Import error getting app module: {ie}")
# If not available, initialize it # Fallback: initialize our own if app module isn't available
if series_app is None:
print("API: Initializing fallback series app...")
init_series_app() init_series_app()
return series_app return series_app
except Exception as e:
print(f"API: Error getting series app: {e}")
return None
# Import socketio instance - this will need to be passed from app.py # Import socketio instance - this will need to be passed from app.py
socketio = None socketio = None
@ -147,8 +151,12 @@ def update_directory():
def get_series(): def get_series():
"""Get all series data.""" """Get all series data."""
try: try:
print("API: Getting series app...")
current_series_app = get_series_app() current_series_app = get_series_app()
print(f"API: Series app obtained: {current_series_app is not None}")
if current_series_app is None or current_series_app.List is None: if current_series_app is None or current_series_app.List is None:
print("API: No series app or list available")
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',
'series': [], 'series': [],
@ -156,21 +164,47 @@ def get_series():
'message': 'No series data available. Please perform a scan to load series.' 'message': 'No series data available. Please perform a scan to load series.'
}) })
print(f"API: Getting series list...")
series_list = current_series_app.List.GetList()
print(f"API: Series list length: {len(series_list)}")
# Get series data # Get series data
series_data = [] series_data = []
for serie in current_series_app.List.GetList(): for i, serie in enumerate(series_list):
try:
print(f"API: Processing serie {i+1}/{len(series_list)}: {getattr(serie, 'folder', 'unknown')}")
# Safely get serie properties
folder = getattr(serie, 'folder', f'serie_{i}')
name = getattr(serie, 'name', None) or folder
episode_dict = getattr(serie, 'episodeDict', {})
# Calculate episodes safely
total_episodes = 0
for season, episodes in episode_dict.items():
if episodes and hasattr(episodes, '__len__'):
total_episodes += len(episodes)
series_data.append({ series_data.append({
'folder': serie.folder, 'folder': folder,
'name': serie.name or serie.folder, 'name': name,
'total_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()), 'total_episodes': total_episodes,
'missing_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()), 'missing_episodes': total_episodes, # For now, assume all are missing
'status': 'ongoing', 'status': 'ongoing',
'episodes': { 'episodes': dict(episode_dict) if episode_dict else {}
season: episodes
for season, episodes in serie.episodeDict.items()
}
}) })
# Limit to first 50 series to avoid timeout
if len(series_data) >= 50:
print("API: Limiting to first 50 series to prevent timeout")
break
except Exception as serie_error:
print(f"API: Error processing serie {i}: {serie_error}")
# Continue with next serie
continue
print(f"API: Returning {len(series_data)} series")
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',
'series': series_data, 'series': series_data,
@ -180,6 +214,8 @@ def get_series():
except Exception as e: except Exception as e:
# Log the error but don't return 500 to prevent page reload loops # Log the error but don't return 500 to prevent page reload loops
print(f"Error in get_series: {e}") print(f"Error in get_series: {e}")
import traceback
traceback.print_exc()
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',
'series': [], 'series': [],