-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
222 lines (200 loc) · 10.1 KB
/
main.py
File metadata and controls
222 lines (200 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/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}")
# Check for Windows service commands
if sys.platform == 'win32' and len(sys.argv) > 1:
if sys.argv[1] == '--install-service':
try:
from src.primary.windows_service import install_service
success = install_service()
sys.exit(0 if success else 1)
except ImportError:
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
sys.exit(1)
except Exception as e:
root_logger.exception(f"Error installing Windows service: {e}")
sys.exit(1)
elif sys.argv[1] == '--remove-service':
try:
from src.primary.windows_service import remove_service
success = remove_service()
sys.exit(0 if success else 1)
except ImportError:
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
sys.exit(1)
except Exception as e:
root_logger.exception(f"Error removing Windows service: {e}")
sys.exit(1)
elif sys.argv[1] in ['--start', '--stop', '--restart', '--debug', '--update']:
try:
import win32serviceutil
service_name = "Huntarr"
if sys.argv[1] == '--start':
win32serviceutil.StartService(service_name)
print(f"Started {service_name} service")
elif sys.argv[1] == '--stop':
win32serviceutil.StopService(service_name)
print(f"Stopped {service_name} service")
elif sys.argv[1] == '--restart':
win32serviceutil.RestartService(service_name)
print(f"Restarted {service_name} service")
elif sys.argv[1] == '--debug':
# Run the service in debug mode directly
from src.primary.windows_service import HuntarrService
win32serviceutil.HandleCommandLine(HuntarrService)
elif sys.argv[1] == '--update':
# Update the service
win32serviceutil.StopService(service_name)
from src.primary.windows_service import install_service
install_service()
win32serviceutil.StartService(service_name)
print(f"Updated {service_name} service")
sys.exit(0)
except ImportError:
root_logger.error("Failed to import Windows service module. Make sure pywin32 is installed.")
sys.exit(1)
except Exception as e:
root_logger.exception(f"Error managing Windows service: {e}")
sys.exit(1)
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)
_waitress_server = None # Handle to Waitress server for graceful shutdown
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."""
global _waitress_server
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:
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}")
if not stop_event.is_set():
stop_event.set()
else:
try:
from waitress import create_server
web_logger.info("Running with Waitress production server.")
_waitress_server = create_server(app, host=host, port=port, threads=8)
_waitress_server.run()
except ImportError:
web_logger.error("Waitress not found. Falling back to Flask development server.")
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}")
if not stop_event.is_set():
stop_event.set()
except OSError as e:
# Bad file descriptor is expected when .close() is called during shutdown
if not stop_event.is_set():
web_logger.exception(f"Waitress server failed: {e}")
stop_event.set()
else:
web_logger.info("Waitress server stopped.")
except Exception as e:
web_logger.exception(f"Waitress server failed: {e}")
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()
# Close Waitress so serve()/run() unblocks and the finally block can execute
if _waitress_server is not None:
_waitress_server.close()
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=5) # Keep under Docker's 10s SIGKILL window
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)