From b43a675a0503f25d30a8c3b5969e5f55a2f3a5df Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 14 May 2025 11:27:12 +0200 Subject: [PATCH] Fix loader --- changedetectionio/__init__.py | 74 +++++++++++++++++++-- changedetectionio/flask_app.py | 11 ++- changedetectionio/realtime/socket_server.py | 15 ++--- requirements.txt | 2 + 4 files changed, 85 insertions(+), 17 deletions(-) diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index e1f22dca..27c85998 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -196,13 +196,73 @@ def main(): s_type = socket.AF_INET6 if ipv6_enabled else socket.AF_INET - if ssl_mode: - # @todo finalise SSL config, but this should get you in the right direction if you need it. - eventlet.wsgi.server(eventlet.wrap_ssl(eventlet.listen((host, port), s_type), - certfile='cert.pem', - keyfile='privkey.pem', - server_side=True), app) + # Get socketio_server from flask_app + from changedetectionio.flask_app import socketio_server + if socketio_server: + logger.info("Starting server with Socket.IO support (using eventlet)...") + + # Use Flask-SocketIO's run method with error handling for Werkzeug warning + # This is the cleanest approach that works with all Flask-SocketIO versions + # Use '0.0.0.0' as the default host if none is specified + # This will listen on all available interfaces + listen_host = '0.0.0.0' if host == '' else host + logger.info(f"Using host: {listen_host} and port: {port}") + + try: + # First try with the allow_unsafe_werkzeug parameter (newer versions) + if ssl_mode: + socketio_server.run( + app, + host=listen_host, + port=int(port), + certfile='cert.pem', + keyfile='privkey.pem', + debug=False, + use_reloader=False, + allow_unsafe_werkzeug=True # Only in newer versions + ) + else: + socketio_server.run( + app, + host=listen_host, + port=int(port), + debug=False, + use_reloader=False, + allow_unsafe_werkzeug=True # Only in newer versions + ) + except TypeError: + # If allow_unsafe_werkzeug is not a valid parameter, try without it + logger.info("Falling back to basic run method without allow_unsafe_werkzeug") + # Override the werkzeug safety check by setting an environment variable + os.environ['WERKZEUG_RUN_MAIN'] = 'true' + if ssl_mode: + socketio_server.run( + app, + host=listen_host, + port=int(port), + certfile='cert.pem', + keyfile='privkey.pem', + debug=False, + use_reloader=False + ) + else: + socketio_server.run( + app, + host=listen_host, + port=int(port), + debug=False, + use_reloader=False + ) else: - eventlet.wsgi.server(eventlet.listen((host, int(port)), s_type), app) + logger.warning("Socket.IO server not initialized, falling back to standard WSGI server") + # Fallback to standard WSGI server if socketio_server is not available + if ssl_mode: + # @todo finalise SSL config, but this should get you in the right direction if you need it. + eventlet.wsgi.server(eventlet.wrap_ssl(eventlet.listen((host, port), s_type), + certfile='cert.pem', + keyfile='privkey.pem', + server_side=True), app) + else: + eventlet.wsgi.server(eventlet.listen((host, int(port)), s_type), app) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 203cd642..aaa8f6a0 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -273,6 +273,9 @@ def changedetection_app(config=None, datastore_o=None): # RSS access with token is allowed elif request.endpoint and 'rss.feed' in request.endpoint: return None + # Socket.IO routes - need separate handling + elif request.path.startswith('/socket.io/'): + return None # API routes - use their own auth mechanism (@auth.check_token) elif request.path.startswith('/api/'): return None @@ -473,7 +476,13 @@ def changedetection_app(config=None, datastore_o=None): import changedetectionio.blueprint.watchlist as watchlist app.register_blueprint(watchlist.construct_blueprint(datastore=datastore, update_q=update_q, queuedWatchMetaData=queuedWatchMetaData), url_prefix='') - + + # Initialize Socket.IO server + from changedetectionio.realtime.socket_server import init_socketio + global socketio_server + socketio_server = init_socketio(app, datastore) + logger.info("Socket.IO server initialized") + # Memory cleanup endpoint @app.route('/gc-cleanup', methods=['GET']) @login_optionally_required diff --git a/changedetectionio/realtime/socket_server.py b/changedetectionio/realtime/socket_server.py index 9c5ba6c3..b77e6d7e 100644 --- a/changedetectionio/realtime/socket_server.py +++ b/changedetectionio/realtime/socket_server.py @@ -1,12 +1,9 @@ import timeago -from flask import Flask from flask_socketio import SocketIO -import threading -import json + import time import os from loguru import logger -import blinker from changedetectionio.flask_app import _jinja2_filter_datetime, watch_check_completed @@ -82,13 +79,13 @@ def handle_watch_update(socketio, **kwargs): def init_socketio(app, datastore): """Initialize SocketIO with the main Flask app""" - # Use threading mode only - eventlet monkey patching causes issues - # when patching after other modules have been imported - async_mode = 'threading' - logger.info("Using threading mode for Socket.IO (long-polling transport)") + # Since the app already uses eventlet, we'll use that for Socket.IO as well + # This provides better performance for Socket.IO operations + async_mode = 'eventlet' + logger.info(f"Using {async_mode} mode for Socket.IO") socketio = SocketIO(app, - async_mode=async_mode, # Use eventlet if available, otherwise threading + async_mode=async_mode, cors_allowed_origins="*", logger=True, engineio_logger=True) diff --git a/requirements.txt b/requirements.txt index a8e5f091..3b14de93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -106,6 +106,8 @@ panzi-json-logic # For conditions - extracted number from a body of text price-parser +# flask_socket_io - incorrect package name, already have flask-socketio above + # Scheduler - Windows seemed to miss a lot of default timezone info (even "UTC" !) tzdata