some routing fixes
This commit is contained in:
parent
54ca564db8
commit
083eefe697
@ -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
|
||||
directory_to_search = config.anime_directory
|
||||
series_app = SeriesApp(directory_to_search)
|
||||
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
|
||||
|
||||
@ -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
38
src/server/test_api.py
Normal 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()
|
||||
@ -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:
|
||||
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
|
||||
init_series_app()
|
||||
return series_app
|
||||
try:
|
||||
print("API: Attempting to get series app from main app...")
|
||||
import app
|
||||
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():
|
||||
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()),
|
||||
'status': 'ongoing',
|
||||
'episodes': {
|
||||
season: episodes
|
||||
for season, episodes in serie.episodeDict.items()
|
||||
}
|
||||
})
|
||||
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': folder,
|
||||
'name': name,
|
||||
'total_episodes': total_episodes,
|
||||
'missing_episodes': total_episodes, # For now, assume all are missing
|
||||
'status': 'ongoing',
|
||||
'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': [],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user