adds .local/share/millennium and easyeffects

This commit is contained in:
2026-08-06 18:19:44 +02:00
parent d4541842a4
commit 2b51bfa8c5
2581 changed files with 339035 additions and 0 deletions
@@ -0,0 +1,214 @@
"""
WebSocket server implementation for Extendium plugin.
"""
import threading
import time
import uuid
from typing import Optional
from logger.logger import logger # pylint: disable=import-error
from websockets.exceptions import ConnectionClosed
from websockets.sync.server import Server, ServerConnection, serve
from .client_manager import ClientManager
from .message_handler import MessageHandler
from .request_handler import RequestHandler
# Global instances
_server: Optional[Server] = None
_client_manager: Optional[ClientManager] = None
_request_handler: Optional[RequestHandler] = None
_message_handler: Optional[MessageHandler] = None
_cleanup_thread: Optional[threading.Thread] = None
_server_thread: Optional[threading.Thread] = None
_running = False
class WebSocketClientAdapter:
"""
Adapter class to maintain compatibility with the existing code that expects
the client structure from the websocket-server package.
"""
def __init__(self, websocket: ServerConnection):
self.websocket = websocket
self.id = str(uuid.uuid4())
self.data = {'id': self.id}
def get(self, key, default=None):
return self.data.get(key, default)
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
class MessageAdapter:
"""
Adapter to maintain compatibility with the existing MessageHandler that expects
a server with a send_message method.
"""
def send_message(self, client: WebSocketClientAdapter, message: str) -> None:
try:
client.websocket.send(message)
except ConnectionClosed:
# logger.error("Failed to send message to client: connection closed")
pass
except Exception as e:
logger.error(f"Error sending message: {e}")
def handle_client(websocket: ServerConnection) -> None:
"""
Main handler for each client connection.
Args:
websocket: The WebSocket connection object
"""
# Create client adapter
client = WebSocketClientAdapter(websocket)
try:
# Process messages in a loop
for message in websocket:
if _message_handler:
_message_handler.process_message(client, message)
except ConnectionClosed:
pass
except Exception as e:
logger.error(f"Error handling client: {e}")
finally:
# Handle client disconnection (equivalent to _client_left)
if _client_manager:
_client_manager.remove_client(client.id)
def _cleanup_routine() -> None:
"""Periodically clean up old pending requests."""
global _request_handler, _running
while _running and _request_handler:
# Clean up requests older than 30 seconds
_request_handler.cleanup_old_requests(30)
# split wait into intervals to allow faster shutdown detection
for _ in range(20):
if not _running:
return
time.sleep(0.25)
def initialize_server() -> None:
"""
Initialize the WebSocket server.
Args:
port: The port to listen on
host: The host address to bind to
Returns:
The initialized WebSocket server
"""
global _server, _client_manager, _request_handler, _message_handler
if _server:
logger.warn("WebSocket server already initialized")
return _server
# Create instances
_client_manager = ClientManager()
_request_handler = RequestHandler()
# Create message adapter for compatibility
message_adapter = MessageAdapter()
# Create message handler with the adapter
_message_handler = MessageHandler(message_adapter, _client_manager, _request_handler)
def run_server(port: int, host: str = "localhost") -> None:
"""Run the WebSocket server in a separate thread."""
global _server, _cleanup_thread, _server_thread, _running
if _running:
logger.log("WebSocket server already running")
return
_running = True
# Start cleanup thread as non-daemon for proper shutdown
_cleanup_thread = threading.Thread(target=_cleanup_routine)
_cleanup_thread.daemon = False # non-daemon so we can actually clean it up.
_cleanup_thread.start()
# Start server in a separate thread
def start_server():
global _server
try:
_server = serve(handle_client, host, port)
_server.serve_forever()
except Exception as e:
if _running: # Only log if we're not shutting down
logger.error(f"Error in WebSocket server: {e}")
finally:
logger.log("WebSocket server thread ending")
_server_thread = threading.Thread(target=start_server)
_server_thread.daemon = False # Non-daemon so we can wait for proper termination
_server_thread.start()
logger.log(f"WebSocket server is running on {host}:{port}")
def shutdown_server() -> None:
"""Shutdown the WebSocket server."""
global _server, _running, _cleanup_thread, _server_thread, _client_manager, _request_handler, _message_handler
if not _running:
logger.log("WebSocket server not running")
return
logger.log("Shutting down WebSocket server...")
_running = False
# disconnect all connected/dangling clients
if _client_manager:
try:
_client_manager.disconnect_all_clients()
except Exception as e:
logger.error(f"Error disconnecting clients: {e}")
# shutdown the server
if _server:
try:
_server.shutdown()
except Exception as e:
logger.error(f"Error shutting down WebSocket server: {e}")
try:
if hasattr(_server, 'close'):
_server.close()
except Exception as e:
logger.error(f"Error closing WebSocket server: {e}")
# give some time for the server to shutdown, this is somewhat just a safeguard.
time.sleep(0.2)
# wait for cleanup thread to finish
if _cleanup_thread and _cleanup_thread.is_alive():
_cleanup_thread.join(timeout=5)
if _cleanup_thread.is_alive():
logger.error("Cleanup thread did not finish within timeout")
# wait for server thread to finish
if _server_thread and _server_thread.is_alive():
_server_thread.join(timeout=5)
if _server_thread.is_alive():
logger.error("Server thread did not finish within timeout")
# force inline gc cleanup from the gil.
_server = None
_client_manager = None
_request_handler = None
_message_handler = None
_cleanup_thread = None
_server_thread = None
logger.log("WebSocket server shutdown complete")