Compare commits
2 Commits
9497633e78
...
083eefe697
| Author | SHA1 | Date | |
|---|---|---|---|
| 083eefe697 | |||
| 54ca564db8 |
@ -14,7 +14,7 @@ from flask_socketio import SocketIO, emit
|
|||||||
import logging
|
import logging
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from main import SeriesApp
|
from Main import SeriesApp
|
||||||
from server.core.entities.series import Serie
|
from server.core.entities.series import Serie
|
||||||
from server.core.entities import SerieList
|
from server.core.entities import SerieList
|
||||||
from server.infrastructure.file_system import SerieScanner
|
from server.infrastructure.file_system import SerieScanner
|
||||||
@ -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
|
||||||
directory_to_search = config.anime_directory
|
try:
|
||||||
series_app = SeriesApp(directory_to_search)
|
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
|
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
|
||||||
|
|||||||
@ -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
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."""
|
"""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:
|
||||||
# Try to get it from the main app module first
|
print("API: Attempting to get series app from main app...")
|
||||||
try:
|
import app
|
||||||
import app
|
series_app_from_main = app.get_series_app()
|
||||||
if hasattr(app, 'series_app') and app.series_app is not None:
|
print(f"API: Got series app from main app: {series_app_from_main is not None}")
|
||||||
series_app = app.series_app
|
return series_app_from_main
|
||||||
return series_app
|
except ImportError as ie:
|
||||||
except ImportError:
|
print(f"API: Import error getting app module: {ie}")
|
||||||
pass
|
# Fallback: initialize our own if app module isn't available
|
||||||
# If not available, initialize it
|
if series_app is None:
|
||||||
init_series_app()
|
print("API: Initializing fallback series app...")
|
||||||
return 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
|
# 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):
|
||||||
series_data.append({
|
try:
|
||||||
'folder': serie.folder,
|
print(f"API: Processing serie {i+1}/{len(series_list)}: {getattr(serie, 'folder', 'unknown')}")
|
||||||
'name': serie.name or serie.folder,
|
|
||||||
'total_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
|
# Safely get serie properties
|
||||||
'missing_episodes': sum(len(episodes) for episodes in serie.episodeDict.values()),
|
folder = getattr(serie, 'folder', f'serie_{i}')
|
||||||
'status': 'ongoing',
|
name = getattr(serie, 'name', None) or folder
|
||||||
'episodes': {
|
episode_dict = getattr(serie, 'episodeDict', {})
|
||||||
season: episodes
|
|
||||||
for season, episodes in serie.episodeDict.items()
|
# 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({
|
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': [],
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<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 -->
|
<!-- 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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -459,20 +459,20 @@
|
|||||||
<script src="{{ url_for('static', filename='js/localization.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/localization.js') }}"></script>
|
||||||
|
|
||||||
<!-- UX Enhancement Scripts -->
|
<!-- UX Enhancement Scripts -->
|
||||||
<script src="{{ url_for('keyboard_shortcuts_js') }}"></script>
|
<script src="{{ url_for('static.keyboard_shortcuts_js') }}"></script>
|
||||||
<script src="{{ url_for('drag_drop_js') }}"></script>
|
<script src="{{ url_for('static.drag_drop_js') }}"></script>
|
||||||
<script src="{{ url_for('bulk_operations_js') }}"></script>
|
<script src="{{ url_for('static.bulk_operations_js') }}"></script>
|
||||||
<script src="{{ url_for('user_preferences_js') }}"></script>
|
<script src="{{ url_for('static.user_preferences_js') }}"></script>
|
||||||
<script src="{{ url_for('advanced_search_js') }}"></script>
|
<script src="{{ url_for('static.advanced_search_js') }}"></script>
|
||||||
<script src="{{ url_for('undo_redo_js') }}"></script>
|
<script src="{{ url_for('static.undo_redo_js') }}"></script>
|
||||||
|
|
||||||
<!-- Mobile & Accessibility Scripts -->
|
<!-- Mobile & Accessibility Scripts -->
|
||||||
<script src="{{ url_for('mobile_responsive_js') }}"></script>
|
<script src="{{ url_for('static.mobile_responsive_js') }}"></script>
|
||||||
<script src="{{ url_for('touch_gestures_js') }}"></script>
|
<script src="{{ url_for('static.touch_gestures_js') }}"></script>
|
||||||
<script src="{{ url_for('accessibility_features_js') }}"></script>
|
<script src="{{ url_for('static.accessibility_features_js') }}"></script>
|
||||||
<script src="{{ url_for('screen_reader_support_js') }}"></script>
|
<script src="{{ url_for('static.screen_reader_support_js') }}"></script>
|
||||||
<script src="{{ url_for('color_contrast_compliance_js') }}"></script>
|
<script src="{{ url_for('static.color_contrast_compliance_js') }}"></script>
|
||||||
<script src="{{ url_for('multi_screen_support_js') }}"></script>
|
<script src="{{ url_for('static.multi_screen_support_js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user