212 lines
7.9 KiB
Python
212 lines
7.9 KiB
Python
import os
|
|
import sys
|
|
import threading
|
|
from datetime import datetime
|
|
|
|
# Add the parent directory to sys.path to import our modules
|
|
# This must be done before any local imports
|
|
current_dir = os.path.dirname(__file__)
|
|
parent_dir = os.path.join(current_dir, '..')
|
|
sys.path.insert(0, os.path.abspath(parent_dir))
|
|
|
|
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
|
from flask_socketio import SocketIO, emit
|
|
import logging
|
|
import atexit
|
|
|
|
from main import SeriesApp
|
|
from server.core.entities.series import Serie
|
|
from server.core.entities import SerieList
|
|
from server.infrastructure.file_system import SerieScanner
|
|
from server.infrastructure.providers.provider_factory import Loaders
|
|
from web.controllers.auth_controller import session_manager, require_auth, optional_auth
|
|
from config import config
|
|
from application.services.queue_service import download_queue_bp
|
|
|
|
# Import route blueprints
|
|
from web.routes import (
|
|
auth_bp, auth_api_bp, api_bp, main_bp, static_bp,
|
|
diagnostic_bp, config_bp
|
|
)
|
|
from web.routes.websocket_handlers import register_socketio_handlers
|
|
|
|
# Import API blueprints from their correct locations
|
|
from web.controllers.api.v1.process import process_bp
|
|
from web.controllers.api.v1.scheduler import scheduler_bp
|
|
from web.controllers.api.v1.logging import logging_bp
|
|
from web.controllers.api.v1.health import health_bp
|
|
from application.services.scheduler_service import init_scheduler, get_scheduler
|
|
from shared.utils.process_utils import (with_process_lock, RESCAN_LOCK, DOWNLOAD_LOCK,
|
|
ProcessLockError, is_process_running, check_process_locks)
|
|
|
|
# Import error handling and monitoring modules
|
|
from web.middleware.error_handler import handle_api_errors
|
|
|
|
# Performance optimization modules - not yet implemented
|
|
|
|
# API integration and database modules - not yet implemented
|
|
# User experience and accessibility modules - not yet implemented
|
|
|
|
app = Flask(__name__,
|
|
template_folder='web/templates/base',
|
|
static_folder='web/static')
|
|
app.config['SECRET_KEY'] = os.urandom(24)
|
|
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24 hours
|
|
socketio = SocketIO(app, cors_allowed_origins="*")
|
|
|
|
# Error handler for API routes to return JSON instead of HTML
|
|
@app.errorhandler(404)
|
|
def handle_api_not_found(error):
|
|
"""Handle 404 errors for API routes by returning JSON instead of HTML."""
|
|
if request.path.startswith('/api/'):
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'API endpoint not found',
|
|
'path': request.path
|
|
}), 404
|
|
# For non-API routes, let Flask handle it normally
|
|
return error
|
|
|
|
# Register all blueprints
|
|
app.register_blueprint(download_queue_bp)
|
|
app.register_blueprint(main_bp)
|
|
app.register_blueprint(auth_bp)
|
|
app.register_blueprint(auth_api_bp)
|
|
app.register_blueprint(api_bp)
|
|
app.register_blueprint(static_bp)
|
|
app.register_blueprint(diagnostic_bp)
|
|
app.register_blueprint(config_bp)
|
|
# Register available API blueprints
|
|
app.register_blueprint(process_bp)
|
|
app.register_blueprint(scheduler_bp)
|
|
app.register_blueprint(logging_bp)
|
|
app.register_blueprint(health_bp)
|
|
# Additional blueprints will be registered when features are implemented
|
|
|
|
# Additional feature initialization will be added when features are implemented
|
|
|
|
# Global variables are now managed in their respective route files
|
|
# Keep only series_app for backward compatibility
|
|
series_app = None
|
|
|
|
def init_series_app():
|
|
"""Initialize the SeriesApp with configuration directory."""
|
|
global series_app
|
|
directory_to_search = config.anime_directory
|
|
series_app = SeriesApp(directory_to_search)
|
|
return series_app
|
|
|
|
# Register WebSocket handlers
|
|
register_socketio_handlers(socketio)
|
|
|
|
# Pass socketio instance to API routes
|
|
from web.routes.api_routes import set_socketio
|
|
set_socketio(socketio)
|
|
|
|
# Initialize scheduler
|
|
scheduler = init_scheduler(config, socketio)
|
|
|
|
def setup_scheduler_callbacks():
|
|
"""Setup callbacks for scheduler operations."""
|
|
|
|
def rescan_callback():
|
|
"""Callback for scheduled rescan operations."""
|
|
try:
|
|
# Reinit and scan
|
|
series_app.SerieScanner.Reinit()
|
|
series_app.SerieScanner.Scan()
|
|
|
|
# Refresh the series list
|
|
series_app.List = SerieList.SerieList(series_app.directory_to_search)
|
|
series_app.__InitList__()
|
|
|
|
return {"status": "success", "message": "Scheduled rescan completed"}
|
|
except Exception as e:
|
|
raise Exception(f"Scheduled rescan failed: {e}")
|
|
|
|
def download_callback():
|
|
"""Callback for auto-download after scheduled rescan."""
|
|
try:
|
|
if not series_app or not series_app.List:
|
|
return {"status": "skipped", "message": "No series data available"}
|
|
|
|
# Find series with missing episodes
|
|
series_with_missing = []
|
|
for serie in series_app.List.GetList():
|
|
if serie.episodeDict:
|
|
series_with_missing.append(serie)
|
|
|
|
if not series_with_missing:
|
|
return {"status": "skipped", "message": "No series with missing episodes found"}
|
|
|
|
# Note: Actual download implementation would go here
|
|
# For now, just return the count of series that would be downloaded
|
|
return {
|
|
"status": "started",
|
|
"message": f"Auto-download initiated for {len(series_with_missing)} series",
|
|
"series_count": len(series_with_missing)
|
|
}
|
|
|
|
except Exception as e:
|
|
raise Exception(f"Auto-download failed: {e}")
|
|
|
|
scheduler.set_rescan_callback(rescan_callback)
|
|
scheduler.set_download_callback(download_callback)
|
|
|
|
# Setup scheduler callbacks
|
|
setup_scheduler_callbacks()
|
|
|
|
# Advanced system initialization will be added when features are implemented
|
|
|
|
# Register cleanup functions
|
|
@atexit.register
|
|
def cleanup_on_exit():
|
|
"""Clean up resources on application exit."""
|
|
try:
|
|
# Additional cleanup functions will be added when features are implemented
|
|
logging.info("Application cleanup completed")
|
|
except Exception as e:
|
|
logging.error(f"Error during cleanup: {e}")
|
|
|
|
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
|
|
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
|
|
logging.basicConfig(level=logging.INFO)
|
|
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 os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
|
logger.info("Starting Aniworld Flask server...")
|
|
logger.info(f"Anime directory: {config.anime_directory}")
|
|
logger.info(f"Log level: {config.log_level}")
|
|
|
|
# Start scheduler if enabled
|
|
if hasattr(config, 'scheduled_rescan_enabled') and config.scheduled_rescan_enabled:
|
|
logger.info(f"Starting scheduler - daily rescan at {getattr(config, 'scheduled_rescan_time', '03:00')}")
|
|
scheduler.start_scheduler()
|
|
else:
|
|
logger.info("Scheduled operations disabled")
|
|
|
|
logger.info("Server will be available at http://localhost:5000")
|
|
|
|
try:
|
|
# Run with SocketIO
|
|
socketio.run(app, debug=True, host='0.0.0.0', port=5000, allow_unsafe_werkzeug=True)
|
|
finally:
|
|
# Clean shutdown
|
|
if 'scheduler' in locals() and scheduler:
|
|
scheduler.stop_scheduler()
|
|
logger.info("Scheduler stopped")
|
|
# Additional cleanup can be added here |