Compare commits

...

2 Commits

Author SHA1 Message Date
083eefe697 some routing fixes 2025-09-29 15:53:18 +02:00
54ca564db8 fix routing issue 2025-09-29 15:14:06 +02:00
6 changed files with 7825 additions and 51 deletions

View File

@ -14,7 +14,7 @@ from flask_socketio import SocketIO, emit
import logging
import atexit
from main import SeriesApp
from Main import SeriesApp
from server.core.entities.series import Serie
from server.core.entities import SerieList
from server.infrastructure.file_system import SerieScanner
@ -89,11 +89,26 @@ app.register_blueprint(health_bp)
# Keep only series_app for backward compatibility
series_app = None
def init_series_app():
def init_series_app(verbose=True):
"""Initialize the SeriesApp with configuration directory."""
global series_app
try:
directory_to_search = config.anime_directory
if verbose:
print(f"Initializing SeriesApp with directory: {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
# Register WebSocket handlers
@ -172,10 +187,7 @@ if __name__ == '__main__':
# Only run initialization and logging setup in the main process
# This prevents duplicate initialization when Flask debug reloader starts
# Initialize the series app
init_series_app()
# Configure enhanced logging system
# Configure enhanced logging system first
try:
from server.infrastructure.logging.config import get_logger, logging_config
logger = get_logger(__name__, 'webapp')
@ -186,7 +198,34 @@ if __name__ == '__main__':
logger = logging.getLogger(__name__)
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':
logger.info("Starting Aniworld Flask server...")
logger.info(f"Anime directory: {config.anime_directory}")
@ -200,6 +239,9 @@ if __name__ == '__main__':
logger.info("Scheduled operations disabled")
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:
# Run with SocketIO

View File

@ -12,6 +12,43 @@ from typing import Optional
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):
"""Custom formatter for fail2ban compatible authentication failure logs."""
@ -33,8 +70,14 @@ class StructuredFormatter(logging.Formatter):
# Add component info
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
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
if record.exc_info:
@ -49,7 +92,16 @@ class ConsoleOnlyFormatter(logging.Formatter):
def format(self, record):
# Only show timestamp, level and message for console
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:
@ -96,7 +148,7 @@ class LoggingConfig:
# Console handler (optional, controlled by config)
if console_logging:
console_handler = logging.StreamHandler(sys.stdout)
console_handler = UnicodeStreamHandler(sys.stdout)
console_handler.setLevel(numeric_level)
console_handler.setFormatter(ConsoleOnlyFormatter())
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."""
global series_app
from config import config
from main import SeriesApp
from Main import SeriesApp
directory_to_search = config.anime_directory
series_app = SeriesApp(directory_to_search)
return 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
if series_app is None:
# Try to get it from the main app module first
try:
print("API: Attempting to get series app from main app...")
import app
if hasattr(app, 'series_app') and app.series_app is not None:
series_app = app.series_app
return series_app
except ImportError:
pass
# If not available, initialize it
series_app_from_main = app.get_series_app()
print(f"API: Got series app from main app: {series_app_from_main is not None}")
return series_app_from_main
except ImportError as ie:
print(f"API: Import error getting app module: {ie}")
# Fallback: initialize our own if app module isn't available
if series_app is None:
print("API: Initializing fallback series app...")
init_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
socketio = None
@ -147,8 +151,12 @@ def update_directory():
def get_series():
"""Get all series data."""
try:
print("API: Getting 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:
print("API: No series app or list available")
return jsonify({
'status': 'success',
'series': [],
@ -156,21 +164,47 @@ def get_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
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({
'folder': serie.folder,
'name': serie.name or serie.folder,
'total_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
'missing_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
'folder': folder,
'name': name,
'total_episodes': total_episodes,
'missing_episodes': total_episodes, # For now, assume all are missing
'status': 'ongoing',
'episodes': {
season: episodes
for season, episodes in serie.episodeDict.items()
}
'episodes': dict(episode_dict) if episode_dict else {}
})
# 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({
'status': 'success',
'series': series_data,
@ -180,6 +214,8 @@ def get_series():
except Exception as e:
# Log the error but don't return 500 to prevent page reload loops
print(f"Error in get_series: {e}")
import traceback
traceback.print_exc()
return jsonify({
'status': 'success',
'series': [],

View File

@ -9,7 +9,7 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- UX Enhancement and Mobile & Accessibility CSS -->
<link rel="stylesheet" href="{{ url_for('ux_features_css') }}">
<link rel="stylesheet" href="{{ url_for('static.ux_features_css') }}">
</head>
<body>
@ -459,20 +459,20 @@
<script src="{{ url_for('static', filename='js/localization.js') }}"></script>
<!-- UX Enhancement Scripts -->
<script src="{{ url_for('keyboard_shortcuts_js') }}"></script>
<script src="{{ url_for('drag_drop_js') }}"></script>
<script src="{{ url_for('bulk_operations_js') }}"></script>
<script src="{{ url_for('user_preferences_js') }}"></script>
<script src="{{ url_for('advanced_search_js') }}"></script>
<script src="{{ url_for('undo_redo_js') }}"></script>
<script src="{{ url_for('static.keyboard_shortcuts_js') }}"></script>
<script src="{{ url_for('static.drag_drop_js') }}"></script>
<script src="{{ url_for('static.bulk_operations_js') }}"></script>
<script src="{{ url_for('static.user_preferences_js') }}"></script>
<script src="{{ url_for('static.advanced_search_js') }}"></script>
<script src="{{ url_for('static.undo_redo_js') }}"></script>
<!-- Mobile & Accessibility Scripts -->
<script src="{{ url_for('mobile_responsive_js') }}"></script>
<script src="{{ url_for('touch_gestures_js') }}"></script>
<script src="{{ url_for('accessibility_features_js') }}"></script>
<script src="{{ url_for('screen_reader_support_js') }}"></script>
<script src="{{ url_for('color_contrast_compliance_js') }}"></script>
<script src="{{ url_for('multi_screen_support_js') }}"></script>
<script src="{{ url_for('static.mobile_responsive_js') }}"></script>
<script src="{{ url_for('static.touch_gestures_js') }}"></script>
<script src="{{ url_for('static.accessibility_features_js') }}"></script>
<script src="{{ url_for('static.screen_reader_support_js') }}"></script>
<script src="{{ url_for('static.color_contrast_compliance_js') }}"></script>
<script src="{{ url_for('static.multi_screen_support_js') }}"></script>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>