Files
Huntarr-Sonarr/main.py
Admin9705 f22deaa193 update
2025-05-01 01:01:33 -04:00

161 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""
Main entry point for Huntarr
Starts both the web server and the background processing tasks.
"""
import os
import threading
import sys
import signal
import logging # Use standard logging for initial setup
# Ensure the 'src' directory is in the Python path
# This allows importing modules from 'src.primary' etc.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')))
# --- Early Logging Setup (Before importing app components) ---
# Basic logging to capture early errors during import or setup
log_level = logging.DEBUG if os.environ.get('DEBUG', 'false').lower() == 'true' else logging.INFO
logging.basicConfig(level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
root_logger = logging.getLogger("HuntarrRoot") # Specific logger for this entry point
root_logger.info("--- Huntarr Main Process Starting ---")
root_logger.info(f"Python sys.path: {sys.path}")
try:
# Import the Flask app instance
from primary.web_server import app
# Import the background task starter function and shutdown helpers from the renamed file
from primary.background import start_huntarr, stop_event, shutdown_threads
# Configure logging first
import logging
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from primary.utils.logger import setup_main_logger, get_logger
# Initialize main logger
huntarr_logger = setup_main_logger()
huntarr_logger.info("Successfully imported application components.")
except ImportError as e:
root_logger.critical(f"Fatal Error: Failed to import application components: {e}", exc_info=True)
root_logger.critical("Please ensure the application structure is correct, dependencies are installed (`pip install -r requirements.txt`), and the script is run from the project root.")
sys.exit(1)
except Exception as e:
root_logger.critical(f"Fatal Error: An unexpected error occurred during initial imports: {e}", exc_info=True)
sys.exit(1)
def run_background_tasks():
"""Runs the Huntarr background processing."""
bg_logger = get_logger("HuntarrBackground") # Use app's logger
try:
bg_logger.info("Starting Huntarr background tasks...")
start_huntarr() # This function contains the main loop and shutdown logic
except Exception as e:
bg_logger.exception(f"Critical error in Huntarr background tasks: {e}")
finally:
bg_logger.info("Huntarr background tasks stopped.")
def run_web_server():
"""Runs the Flask web server using Waitress in production."""
web_logger = get_logger("WebServer") # Use app's logger
debug_mode = os.environ.get('DEBUG', 'false').lower() == 'true'
host = os.environ.get('FLASK_HOST', '0.0.0.0')
port = int(os.environ.get('PORT', 9705)) # Use PORT for consistency
web_logger.info(f"Starting web server on {host}:{port} (Debug: {debug_mode})...")
if debug_mode:
# Use Flask's development server for debugging (less efficient, auto-reloads)
# Note: use_reloader=True can cause issues with threads starting twice.
web_logger.warning("Running in DEBUG mode with Flask development server.")
try:
app.run(host=host, port=port, debug=True, use_reloader=False)
except Exception as e:
web_logger.exception(f"Flask development server failed: {e}")
# Signal background thread to stop if server fails critically
if not stop_event.is_set():
stop_event.set()
else:
# Use Waitress for production
try:
from waitress import serve
web_logger.info("Running with Waitress production server.")
# Adjust threads as needed, default is 4
serve(app, host=host, port=port, threads=8)
except ImportError:
web_logger.error("Waitress not found. Falling back to Flask development server (NOT recommended for production).")
web_logger.error("Install waitress ('pip install waitress') for production use.")
try:
app.run(host=host, port=port, debug=False, use_reloader=False)
except Exception as e:
web_logger.exception(f"Flask development server (fallback) failed: {e}")
# Signal background thread to stop if server fails critically
if not stop_event.is_set():
stop_event.set()
except Exception as e:
web_logger.exception(f"Waitress server failed: {e}")
# Signal background thread to stop if server fails critically
if not stop_event.is_set():
stop_event.set()
def main_shutdown_handler(signum, frame):
"""Gracefully shut down the application."""
huntarr_logger.warning(f"Received signal {signal.Signals(signum).name}. Initiating shutdown...")
if not stop_event.is_set():
stop_event.set()
# The rest of the cleanup happens after run_web_server() returns or in the finally block.
if __name__ == '__main__':
# Register signal handlers for graceful shutdown in the main process
signal.signal(signal.SIGINT, main_shutdown_handler)
signal.signal(signal.SIGTERM, main_shutdown_handler)
background_thread = None
try:
# Start background tasks in a daemon thread
# Daemon threads exit automatically if the main thread exits unexpectedly,
# but we'll try to join() them for a graceful shutdown.
background_thread = threading.Thread(target=run_background_tasks, name="HuntarrBackground", daemon=True)
background_thread.start()
# Start the web server in the main thread (blocking)
# This will run until the server is stopped (e.g., by Ctrl+C)
run_web_server()
except KeyboardInterrupt:
huntarr_logger.info("KeyboardInterrupt received in main thread. Shutting down...")
if not stop_event.is_set():
stop_event.set()
except Exception as e:
huntarr_logger.exception(f"An unexpected error occurred in the main execution block: {e}")
if not stop_event.is_set():
stop_event.set() # Ensure shutdown is triggered on unexpected errors
finally:
# --- Cleanup ---
huntarr_logger.info("Web server has stopped. Initiating final shutdown sequence...")
# Ensure the stop event is set (might already be set by signal handler or error)
if not stop_event.is_set():
huntarr_logger.warning("Stop event was not set before final cleanup. Setting now.")
stop_event.set()
# Wait for the background thread to finish cleanly
if background_thread and background_thread.is_alive():
huntarr_logger.info("Waiting for background tasks to complete...")
background_thread.join(timeout=30) # Wait up to 30 seconds
if background_thread.is_alive():
huntarr_logger.warning("Background thread did not stop gracefully within the timeout.")
elif background_thread:
huntarr_logger.info("Background thread already stopped.")
else:
huntarr_logger.info("Background thread was not started.")
# Call the shutdown_threads function from primary.main (if it does more than just join)
# This might be redundant if start_huntarr handles its own cleanup via stop_event
# huntarr_logger.info("Calling shutdown_threads()...")
# shutdown_threads() # Uncomment if primary.main.shutdown_threads() does more cleanup
huntarr_logger.info("--- Huntarr Main Process Exiting ---")
# Use os._exit(0) for a more forceful exit if necessary, but sys.exit(0) is generally preferred
sys.exit(0)