Compare commits

..

No commits in common. "083eefe69781607e0a5a1907961047f6a0e82988" and "9497633e784416e8f37a242f1c148fd927d567ce" have entirely different histories.

6 changed files with 51 additions and 7825 deletions

View File

@ -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,26 +89,11 @@ 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(verbose=True): def init_series_app():
"""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 series_app = SeriesApp(directory_to_search)
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
@ -187,7 +172,10 @@ 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
# Configure enhanced logging system first # Initialize the series app
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')
@ -198,34 +186,7 @@ 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")
if __name__ == '__main__': # Only display startup messages if we're not in the reloader child process
# 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}")
@ -239,9 +200,6 @@ 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,43 +12,6 @@ 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."""
@ -70,14 +33,8 @@ 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} - {message}" formatted = f"{record.asctime} - {record.levelname:8} - {component:15} - {record.funcName:20} - {record.getMessage()}"
# Add exception info if present # Add exception info if present
if record.exc_info: if record.exc_info:
@ -92,16 +49,7 @@ 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')
try: return f"[{timestamp}] {record.levelname}: {record.getMessage()}"
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:
@ -148,7 +96,7 @@ class LoggingConfig:
# Console handler (optional, controlled by config) # Console handler (optional, controlled by config)
if console_logging: if console_logging:
console_handler = UnicodeStreamHandler(sys.stdout) console_handler = logging.StreamHandler(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

View File

@ -1,38 +0,0 @@
#!/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,30 +79,26 @@ 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 from the main app.""" """Get the current series app instance, initializing if needed."""
global series_app global series_app
try: if series_app is None:
print("API: Attempting to get series app from main app...") # Try to get it from the main app module first
import app try:
series_app_from_main = app.get_series_app() import app
print(f"API: Got series app from main app: {series_app_from_main is not None}") if hasattr(app, 'series_app') and app.series_app is not None:
return series_app_from_main series_app = app.series_app
except ImportError as ie: return series_app
print(f"API: Import error getting app module: {ie}") except ImportError:
# Fallback: initialize our own if app module isn't available pass
if series_app is None: # If not available, initialize it
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
@ -151,12 +147,8 @@ 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': [],
@ -164,47 +156,21 @@ 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 i, serie in enumerate(series_list): for serie in current_series_app.List.GetList():
try: series_data.append({
print(f"API: Processing serie {i+1}/{len(series_list)}: {getattr(serie, 'folder', 'unknown')}") '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()
}
})
# 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({ return jsonify({
'status': 'success', 'status': 'success',
'series': series_data, 'series': series_data,
@ -214,8 +180,6 @@ 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': [],

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"> <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('static.ux_features_css') }}"> <link rel="stylesheet" href="{{ url_for('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('static.keyboard_shortcuts_js') }}"></script> <script src="{{ url_for('keyboard_shortcuts_js') }}"></script>
<script src="{{ url_for('static.drag_drop_js') }}"></script> <script src="{{ url_for('drag_drop_js') }}"></script>
<script src="{{ url_for('static.bulk_operations_js') }}"></script> <script src="{{ url_for('bulk_operations_js') }}"></script>
<script src="{{ url_for('static.user_preferences_js') }}"></script> <script src="{{ url_for('user_preferences_js') }}"></script>
<script src="{{ url_for('static.advanced_search_js') }}"></script> <script src="{{ url_for('advanced_search_js') }}"></script>
<script src="{{ url_for('static.undo_redo_js') }}"></script> <script src="{{ url_for('undo_redo_js') }}"></script>
<!-- Mobile & Accessibility Scripts --> <!-- Mobile & Accessibility Scripts -->
<script src="{{ url_for('static.mobile_responsive_js') }}"></script> <script src="{{ url_for('mobile_responsive_js') }}"></script>
<script src="{{ url_for('static.touch_gestures_js') }}"></script> <script src="{{ url_for('touch_gestures_js') }}"></script>
<script src="{{ url_for('static.accessibility_features_js') }}"></script> <script src="{{ url_for('accessibility_features_js') }}"></script>
<script src="{{ url_for('static.screen_reader_support_js') }}"></script> <script src="{{ url_for('screen_reader_support_js') }}"></script>
<script src="{{ url_for('static.color_contrast_compliance_js') }}"></script> <script src="{{ url_for('color_contrast_compliance_js') }}"></script>
<script src="{{ url_for('static.multi_screen_support_js') }}"></script> <script src="{{ url_for('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>