From c5ded9084160f70452b40bc53ba8e58ee2f3ef66 Mon Sep 17 00:00:00 2001 From: kalzu rekku Date: Sun, 11 Aug 2024 22:09:40 +0300 Subject: [PATCH] Added message_bus module for module to module comms. Some changes to core_daemon and daemon_cli... --- core_daemon.py | 163 ++++++++++------------- daemon_cli.py | 161 +++++++---------------- hook_manager.py | 110 ++++++++++++++++ module_manager.py | 252 ++++++++++++++++++++++++++++++++++++ modules/message_bus.py | 95 ++++++++++++++ modules/messages_example.py | 49 +++++++ modules/user_auth.py | 125 ++++++++++++++++++ 7 files changed, 744 insertions(+), 211 deletions(-) create mode 100644 hook_manager.py create mode 100644 module_manager.py create mode 100644 modules/message_bus.py create mode 100644 modules/messages_example.py create mode 100644 modules/user_auth.py diff --git a/core_daemon.py b/core_daemon.py index 5157106..6852e29 100644 --- a/core_daemon.py +++ b/core_daemon.py @@ -1,101 +1,35 @@ import os import time import sys -import importlib import socket import json import threading import logging import traceback +import hashlib + +from hook_manager import hook_manager +from module_manager import ModuleManager # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -class ModuleManager: - def __init__(self, module_dirs): - self.module_dirs = module_dirs - self.loaded_modules = {} - self.extra_commands = {} - self._update_sys_path() - - def _update_sys_path(self): - for dir in self.module_dirs: - full_path = os.path.abspath(dir) - if full_path not in sys.path: - sys.path.append(full_path) - logging.info(f"Added {full_path} to sys.path") - - def add_module_dir(self, new_dir): - try: - full_path = os.path.abspath(new_dir) - if full_path not in self.module_dirs: - self.module_dirs.append(full_path) - self._update_sys_path() - return True, f"Added module directory: {full_path}" - return False, f"Module directory already exists: {full_path}" - except Exception as e: - logging.error(f"Error adding module directory: {str(e)}") - return False, f"Error adding module directory: {str(e)}" - - def load_module(self, module_name): - for dir in self.module_dirs: - try: - module = importlib.import_module(f'{os.path.basename(dir)}.{module_name}') - self.loaded_modules[module_name] = module - if hasattr(module, 'initialize'): - module.initialize() - if hasattr(module, 'get_commands'): - new_commands = module.get_commands() - self.extra_commands.update(new_commands) - logging.info(f"Module '{module_name}' loaded successfully from {dir}.") - return True, f"Module '{module_name}' loaded and initialized successfully from {dir}." - except ImportError: - continue - except Exception as e: - logging.error(f"Error loading module '{module_name}' from {dir}: {str(e)}") - return False, f"Error loading module '{module_name}' from {dir}: {str(e)}" - return False, f"Error: Unable to load module '{module_name}' from any of the module directories." - - def unload_module(self, module_name): - if module_name in self.loaded_modules: - try: - module = self.loaded_modules[module_name] - if hasattr(module, 'shutdown'): - module.shutdown() - if hasattr(module, 'get_commands'): - commands_to_remove = module.get_commands().keys() - for cmd in commands_to_remove: - self.extra_commands.pop(cmd, None) - del self.loaded_modules[module_name] - logging.info(f"Module '{module_name}' unloaded successfully.") - return True, f"Module '{module_name}' unloaded and shut down." - except Exception as e: - logging.error(f"Error unloading module '{module_name}': {str(e)}") - return False, f"Error unloading module '{module_name}': {str(e)}" - return False, f"Module '{module_name}' is not loaded." - - def list_modules(self): - return list(self.loaded_modules.keys()) - - def list_commands(self): - return list(self.extra_commands.keys()) - - def execute_command(self, command, args): - if command in self.extra_commands: - try: - result = self.extra_commands[command](args) - return True, result - except Exception as e: - logging.error(f"Error executing command '{command}': {str(e)}") - return False, f"Error executing command '{command}': {str(e)}" - return False, "Command not found" - class CoreDaemon: def __init__(self, host='localhost', port=9999, module_dirs=None): self.host = host self.port = port self.module_dirs = module_dirs or ['modules'] self.module_manager = ModuleManager(self.module_dirs) + self.script_path = sys.argv[0] + self.initial_hash = self.calculate_file_hash(self.script_path) + self.hook_manager = hook_manager + + def calculate_file_hash(self, file_path): + sha256_hash = hashlib.sha256() + with open(file_path, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return sha256_hash.hexdigest() def start(self): try: @@ -136,45 +70,80 @@ class CoreDaemon: logging.error(f"Error handling client request: {str(e)}") conn.sendall(json.dumps({'success': False, 'message': 'Internal server error'}).encode()) + def check_for_updates(self): + logging.info("Checking for updates...") + script_modified = False + current_script_hash = self.calculate_file_hash(self.script_path) + if current_script_hash != self.initial_hash: + script_modified = True + + modified_modules = self.module_manager.check_modules_modified() + + if script_modified or modified_modules: + message = "Updates detected:\n" + if script_modified: + message += "- Core script has been modified\n" + if modified_modules: + message += f"- Modified modules: {', '.join(modified_modules)}\n" + return True, message + else: + return False, "No updates detected." + def upgrade(self): - logging.info("Upgrading core daemon...") - try: - # Here you would typically download or copy the new version + update_available, message = self.check_for_updates() + if update_available: + logging.info(message) + # Here you would typically perform any necessary upgrade steps # For this example, we'll just restart the script os.execv(sys.executable, ['python'] + sys.argv) - except Exception as e: - logging.error(f"Error during upgrade: {str(e)}") - return False, f"Upgrade failed: {str(e)}" + return True, "Upgrade initiated. Restarting with new version." + else: + logging.info("No updates detected. Skipping upgrade.") + return False, "No upgrade needed. Current version is up to date." def process_command(self, command): action = command.get('action') + + # Execute pre-command hooks + pre_command_results = self.hook_manager.execute_hook('pre_command', command) + for result in pre_command_results: + if result is False: + return {'success': False, 'message': "Command rejected by pre-command hook"} + try: if action == 'load': - return self.module_manager.load_module(command.get('module')) + success, message = self.module_manager.load_module(command.get('module')) elif action == 'unload': - return self.module_manager.unload_module(command.get('module')) + success, message = self.module_manager.unload_module(command.get('module')) elif action == 'list': - modules = self.module_manager.list_modules() - return True, modules + success, modules = True, self.module_manager.list_modules() + message = modules elif action == 'execute': - return self.module_manager.execute_command(command.get('command'), command.get('args')) + success, message = self.module_manager.execute_command(command.get('command'), command.get('args')) elif action == 'upgrade': - return self.upgrade() + success, message = self.upgrade() elif action == 'list_commands': - commands = self.module_manager.list_commands() - return True, commands + success, commands = True, self.module_manager.list_commands() + message = commands elif action == 'add_module_dir': - return self.module_manager.add_module_dir(command.get('dir')) + success, message = self.module_manager.add_module_dir(command.get('dir')) elif action == 'shutdown': response = self.shutdown() # After sending the response, exit the program threading.Thread(target=self._delayed_shutdown).start() return response else: - return False, "Unknown command" + success, message = False, "Unknown command" except Exception as e: logging.error(f"Error processing command {action}: {str(e)}") - return False, f"Error processing command {action}: {str(e)}" + success, message = False, f"Error processing command: {str(e)}" + + response = {'success': success, 'message': message} + + # Execute post-command hooks + self.hook_manager.execute_hook('post_command', command, response) + + return response if __name__ == "__main__": import argparse diff --git a/daemon_cli.py b/daemon_cli.py index 46b61d3..2de8de1 100644 --- a/daemon_cli.py +++ b/daemon_cli.py @@ -1,7 +1,6 @@ import cmd import socket import json -import time import os class ModuleShell(cmd.Cmd): @@ -12,134 +11,66 @@ class ModuleShell(cmd.Cmd): super().__init__() self.host = host self.port = port - self.update_commands() + self.session_id = None - def send_command(self, command, retries=3, delay=2): - for attempt in range(retries): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(5) # Set a 5-second timeout - s.connect((self.host, self.port)) - s.sendall(json.dumps(command).encode()) - data = s.recv(1024).decode() - if data: - return json.loads(data) - else: - print("Received empty response. The server might be restarting.") - if command['action'] == 'upgrade': - return {'success': True, 'message': 'Upgrade initiated. The server is restarting.'} - except (socket.timeout, ConnectionRefusedError, json.JSONDecodeError) as e: - if attempt < retries - 1: - print(f"Connection failed. Retrying in {delay} seconds...") - time.sleep(delay) - else: - print(f"Failed to connect after {retries} attempts: {str(e)}") - return {'success': False, 'message': f"Connection error: {str(e)}"} - return {'success': False, 'message': "Failed to connect to the server."} + def send_command(self, action, **kwargs): + command = {'action': action, **kwargs} + if self.session_id: + command['session_id'] = self.session_id - def update_commands(self): try: - response = self.send_command({'action': 'list_commands'}) - if isinstance(response, dict) and response.get('success'): - self.dynamic_commands = response['message'] - elif isinstance(response, list): - self.dynamic_commands = response - else: - print("Unexpected response format when updating commands.") - self.dynamic_commands = [] + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((self.host, self.port)) + s.sendall(json.dumps(command).encode()) + data = s.recv(1024).decode() + return json.loads(data) if data else None except Exception as e: - print(f"Error updating commands: {str(e)}") - self.dynamic_commands = [] - - def default(self, line): - parts = line.split() - command = parts[0] - args = ' '.join(parts[1:]) - response = self.send_command({'action': 'execute', 'command': command, 'args': args}) - if isinstance(response, dict): - if response.get('success'): - print(response['message']) - else: - print(f"Error: {response.get('message', 'Unknown error')}") - else: - print(f"Unexpected response: {response}") - - def completenames(self, text, *ignored): - dotext = 'do_'+text - return [a[3:] for a in self.get_names() if a.startswith(dotext)] + \ - [cmd for cmd in self.dynamic_commands if cmd.startswith(text)] + print(f"Error: {str(e)}") + return None def do_load(self, arg): """Load a module: load """ - response = self.send_command({'action': 'load', 'module': arg}) - if isinstance(response, dict): + response = self.send_command('load', module=arg) + if response: print(response.get('message', 'Unknown response')) - else: - print(f"Unexpected response: {response}") - self.update_commands() # Update commands after loading a new module def do_unload(self, arg): """Unload a module: unload """ - response = self.send_command({'action': 'unload', 'module': arg}) - if isinstance(response, dict): + response = self.send_command('unload', module=arg) + if response: print(response.get('message', 'Unknown response')) - else: - print(f"Unexpected response: {response}") - self.update_commands() # Update commands after unloading a module def do_list(self, arg): """List all loaded modules""" - response = self.send_command({'action': 'list'}) - if isinstance(response, dict) and response.get('success'): - modules = response['message'] - elif isinstance(response, list): - modules = response + response = self.send_command('list') + if response and response.get('success'): + modules = response.get('message', []) + if modules: + print("Loaded modules:") + for module in modules: + print(f"- {module}") + else: + print("No modules are currently loaded.") else: - print("Unexpected response format when listing modules.") + print("Failed to retrieve module list.") + + def do_execute(self, arg): + """Execute a command: execute [args]""" + parts = arg.split() + if not parts: + print("Error: No command specified.") return - - if modules: - print("Loaded modules:") - for module in modules: - print(f"- {module}") - else: - print("No modules are currently loaded.") - - def do_list_available(self, arg): - """List available modules in the modules folder""" - modules_path = os.path.join(os.path.dirname(__file__), 'modules') - available_modules = [f[:-3] for f in os.listdir(modules_path) if f.endswith('.py') and not f.startswith('__')] - print("Available modules:") - for module in available_modules: - print(f"- {module}") - - def complete_load(self, text, line, begidx, endidx): - modules_path = os.path.join(os.path.dirname(__file__), 'modules') - available_modules = [f[:-3] for f in os.listdir(modules_path) if f.endswith('.py') and not f.startswith('__')] - return [m for m in available_modules if m.startswith(text)] - - def do_add_module_dir(self, arg): - """Add a new module directory: add_module_dir """ - if not arg: - print("Error: Please provide a directory path.") - return - response = self.send_command({'action': 'add_module_dir', 'dir': arg}) - if isinstance(response, dict): + command = parts[0] + args = ' '.join(parts[1:]) + response = self.send_command('execute', command=command, args=args) + if response: print(response.get('message', 'Unknown response')) - else: - print(f"Unexpected response: {response}") def do_upgrade(self, arg): - """Upgrade the core daemon""" - response = self.send_command({'action': 'upgrade'}) - if isinstance(response, list): - try: - print(response[0]['message']) - except Exception: - print('Core daemon response did not make sense.') - - print("Please reconnect in a few seconds.") - return True # This will exit the command loop + """Upgrade the core daemon and modules""" + response = self.send_command('upgrade') + if response: + print(response.get('message', 'Unknown response')) def do_exit(self, arg): """Exit the shell""" @@ -148,13 +79,15 @@ class ModuleShell(cmd.Cmd): def do_shutdown(self, arg): """Shutdown the core daemon""" - response = self.send_command({'action': 'shutdown'}) - if isinstance(response, dict): + response = self.send_command('shutdown') + if response: print(response.get('message', 'Unknown response')) - else: - print(f"Unexpected response: {response}") print("Core daemon is shutting down. Exiting CLI.") - return True # This will exit the command loop + return True + + def default(self, line): + """Handle unknown commands by trying to execute them""" + self.do_execute(line) def main(): ModuleShell().cmdloop() diff --git a/hook_manager.py b/hook_manager.py new file mode 100644 index 0000000..e135ac1 --- /dev/null +++ b/hook_manager.py @@ -0,0 +1,110 @@ +""" +hook_manager.py + +This module provides the HookManager class which allows for the registration, +unregistration, and execution of hooks. Hooks are functions that can be executed +at specific points in the program flow, such as before or after commands or module +loading/unloading. + +Classes: + HookManager: Manages the registration, unregistration, and execution of hooks. + +Variables: + hook_manager (HookManager): An instance of HookManager for global usage. +""" + +import logging +from typing import Callable, List, Dict + +class HookManager: + """ + A manager for handling hooks that can be registered, unregistered, and executed + at various points in a program's flow. + + Attributes: + hooks (dict): A dictionary containing lists of functions for each hook point. + """ + + def __init__(self): + """ + Initializes a new instance of the HookManager class with predefined hook points. + """ + self.hooks: Dict[str, List[Callable]] = { + 'pre_command': [], + 'post_command': [], + 'pre_module_load': [], + 'post_module_load': [], + 'pre_module_unload': [], + 'post_module_unload': [], + } + logging.info("HookManager initialized with hooks: %s", list(self.hooks.keys())) + + def register_hook(self, hook_name: str, func: Callable): + """ + Registers a function to a specified hook. + + Args: + hook_name (str): The name of the hook to register the function to. + func (function): The function to register. + + Raises: + ValueError: If the specified hook name is not recognized. + """ + if hook_name in self.hooks: + self.hooks[hook_name].append(func) + logging.info("Registered function %s to hook %s", func.__name__, hook_name) + else: + logging.error("Attempted to register to unknown hook: %s", hook_name) + raise ValueError(f"Unknown hook: {hook_name}") + + def unregister_hook(self, hook_name: str, func: Callable): + """ + Unregisters a function from a specified hook. + + Args: + hook_name (str): The name of the hook to unregister the function from. + func (function): The function to unregister. + + Raises: + ValueError: If the specified hook name is not recognized. + """ + if hook_name in self.hooks: + if func in self.hooks[hook_name]: + self.hooks[hook_name].remove(func) + logging.info("Unregistered function %s from hook %s", func.__name__, hook_name) + else: + logging.warning("Function %s not found in hook %s", func.__name__, hook_name) + else: + logging.error("Attempted to unregister from unknown hook: %s", hook_name) + raise ValueError(f"Unknown hook: {hook_name}") + + def execute_hook(self, hook_name: str, *args, **kwargs) -> List: + """ + Executes all functions registered to a specified hook with the provided arguments. + + Args: + hook_name (str): The name of the hook to execute the functions for. + *args: Variable length argument list to pass to the hook functions. + **kwargs: Arbitrary keyword arguments to pass to the hook functions. + + Returns: + list: A list of results from each hook function executed. + + Raises: + ValueError: If the specified hook name is not recognized. + """ + if hook_name not in self.hooks: + logging.error("Attempted to execute unknown hook: %s", hook_name) + raise ValueError(f"Unknown hook: {hook_name}") + + results = [] + for func in self.hooks[hook_name]: + try: + result = func(*args, **kwargs) + results.append(result) + logging.info("Executed function %s in hook %s", func.__name__, hook_name) + except Exception as e: + logging.error("Error executing function %s in hook %s: %s", func.__name__, hook_name, str(e)) + return results + +hook_manager = HookManager() diff --git a/module_manager.py b/module_manager.py new file mode 100644 index 0000000..3260e05 --- /dev/null +++ b/module_manager.py @@ -0,0 +1,252 @@ +""" +module_manager.py + +This module provides the ModuleManager class which allows for the management +of dynamically loadable modules. The ModuleManager supports loading, unloading, +and execution of modules, as well as handling module-specific commands and hooks. + +Classes: + ModuleManager: Manages loading, unloading, and executing modules and their commands. + +Variables: + hook_manager (HookManager): An instance of HookManager for managing hooks. +""" + +import os +import sys +import importlib +import logging +import hashlib +from hook_manager import HookManager + +hook_manager = HookManager() + +class ModuleManager: + """ + A manager for handling dynamically loadable modules, including loading, unloading, + and executing module-specific commands and hooks. + + Attributes: + module_dirs (list): A list of directories to search for modules. + loaded_modules (dict): A dictionary of loaded modules. + extra_commands (dict): A dictionary of additional commands provided by modules. + module_hashes (dict): A dictionary storing file paths and their hashes. + hook_manager (HookManager): An instance of HookManager for managing hooks. + """ + + def __init__(self, module_dirs): + """ + Initializes a new instance of the ModuleManager class. + + Args: + module_dirs (list): A list of directories to search for modules. + """ + self.module_dirs = module_dirs + self.loaded_modules = {} + self.extra_commands = {} + self.module_hashes = {} # store file paths and hashes + self._update_sys_path() + self.hook_manager = hook_manager # Use the global hook_manager! + + def _update_sys_path(self): + """ + Updates the system path to include the directories in module_dirs. + """ + for dir in self.module_dirs: + full_path = os.path.abspath(dir) + if full_path not in sys.path: + sys.path.append(full_path) + logging.info(f"Added {full_path} to sys.path") + + def add_module_dir(self, new_dir): + """ + Adds a new directory to the list of module directories and updates the system path. + + Args: + new_dir (str): The new directory to add. + + Returns: + tuple: A tuple containing a boolean indicating success and a message. + """ + try: + full_path = os.path.abspath(new_dir) + if full_path not in self.module_dirs: + self.module_dirs.append(full_path) + self._update_sys_path() + return True, f"Added module directory: {full_path}" + return False, f"Module directory already exists: {full_path}" + except Exception as e: + logging.error(f"Error adding module directory: {str(e)}") + return False, f"Error adding module directory: {str(e)}" + + def calculate_file_hash(self, file_path): + """ + Calculates the SHA-256 hash of a file. + + Args: + file_path (str): The path to the file. + + Returns: + str: The SHA-256 hash of the file. + """ + sha256_hash = hashlib.sha256() + try: + with open(file_path, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return sha256_hash.hexdigest() + except FileNotFoundError: + logging.error(f"File not found: {file_path}") + return None + except Exception as e: + logging.error(f"Error calculating hash for file '{file_path}': {str(e)}") + return None + + def load_module(self, module_name): + """ + Loads a module by name, initializing it and registering its commands and hooks. + + Args: + module_name (str): The name of the module to load. + + Returns: + tuple: A tuple containing a boolean indicating success and a message. + """ + self.hook_manager.execute_hook('pre_module_load', module_name) + for dir in self.module_dirs: + try: + module_path = os.path.join(dir, f"{module_name}.py") + if not os.path.exists(module_path): + continue + + module = importlib.import_module(f'{os.path.basename(dir)}.{module_name}') + self.loaded_modules[module_name] = module + + # Calculate and store the hash of the module file + module_hash = self.calculate_file_hash(module_path) + if module_hash: + self.module_hashes[module_path] = module_hash + + self._initialize_module(module) + self.hook_manager.execute_hook('post_module_load', module_name) + return True, f"Module '{module_name}' loaded and initialized successfully from {dir}." + except ImportError as e: + logging.error(f"ImportError for module '{module_name}' from {dir}: {str(e)}") + continue + except Exception as e: + logging.error(f"Error loading module '{module_name}' from {dir}: {str(e)}") + return False, f"Error loading module '{module_name}' from {dir}: {str(e)}" + return False, f"Error: Unable to load module '{module_name}' from any of the module directories." + + def _initialize_module(self, module): + """ + Initializes a loaded module by calling its initialize method, registering commands, and registering hooks. + + Args: + module: The loaded module. + """ + if hasattr(module, 'initialize'): + module.initialize() + if hasattr(module, 'get_commands'): + new_commands = module.get_commands() + self.extra_commands.update(new_commands) + if hasattr(module, 'register_hooks'): + module.register_hooks(self.hook_manager) + + def unload_module(self, module_name): + """ + Unloads a module by name, shutting it down and removing its commands and hooks. + + Args: + module_name (str): The name of the module to unload. + + Returns: + tuple: A tuple containing a boolean indicating success and a message. + """ + self.hook_manager.execute_hook('pre_module_unload', module_name) + if module_name in self.loaded_modules: + try: + module = self.loaded_modules[module_name] + self._shutdown_module(module) + self.hook_manager.execute_hook('post_module_unload', module_name) + # Remove the module's hash from our tracking + module_path = module.__file__ + self.module_hashes.pop(module_path, None) + + del self.loaded_modules[module_name] + logging.info(f"Module '{module_name}' unloaded successfully.") + return True, f"Module '{module_name}' unloaded and shut down." + except Exception as e: + logging.error(f"Error unloading module '{module_name}': {str(e)}") + return False, f"Error unloading module '{module_name}': {str(e)}" + return False, f"Module '{module_name}' is not loaded." + + def _shutdown_module(self, module): + """ + Shuts down a loaded module by calling its shutdown method and removing its commands and hooks. + + Args: + module: The loaded module. + """ + if hasattr(module, 'shutdown'): + module.shutdown() + if hasattr(module, 'get_commands'): + commands_to_remove = module.get_commands().keys() + for cmd in commands_to_remove: + self.extra_commands.pop(cmd, None) + if hasattr(module, 'unregister_hooks'): + module.unregister_hooks(self.hook_manager) + + def list_modules(self): + """ + Lists all currently loaded modules. + + Returns: + list: A list of names of loaded modules. + """ + return list(self.loaded_modules.keys()) + + def list_commands(self): + """ + Lists all commands provided by the loaded modules. + + Returns: + list: A list of command names. + """ + return list(self.extra_commands.keys()) + + def execute_command(self, command, args): + """ + Executes a command provided by a loaded module. + + Args: + command (str): The name of the command to execute. + args (list): The arguments to pass to the command. + + Returns: + tuple: A tuple containing a boolean indicating success and the command result or an error message. + """ + if command in self.extra_commands: + try: + result = self.extra_commands[command](args) + return True, result + except Exception as e: + logging.error(f"Error executing command '{command}': {str(e)}") + return False, f"Error executing command '{command}': {str(e)}" + return False, f"Command '{command}' not found" + + + def check_modules_modified(self): + """ + Checks if any loaded modules have been modified on disk by comparing file hashes. + + Returns: + list: A list of names of modified modules. + """ + modified_modules = [] + for module_path, stored_hash in self.module_hashes.items(): + current_hash = self.calculate_file_hash(module_path) + if current_hash and current_hash != stored_hash: + module_name = os.path.splitext(os.path.basename(module_path))[0] + modified_modules.append(module_name) + return modified_modules diff --git a/modules/message_bus.py b/modules/message_bus.py new file mode 100644 index 0000000..5bdb087 --- /dev/null +++ b/modules/message_bus.py @@ -0,0 +1,95 @@ +import queue +import threading + +class MessageBus: + def __init__(self): + self.queues = {} + self.lock = threading.Lock() + + def create_queue(self, queue_name): + with self.lock: + if queue_name not in self.queues: + self.queues[queue_name] = queue.Queue() + return True + return False + + def delete_queue(self, queue_name): + with self.lock: + if queue_name in self.queues: + del self.queues[queue_name] + return True + return False + + def send_message(self, queue_name, message): + with self.lock: + if queue_name in self.queues: + self.queues[queue_name].put(message) + return True + return False + + def receive_message(self, queue_name, timeout=None): + with self.lock: + if queue_name in self.queues: + try: + return self.queues[queue_name].get(timeout=timeout) + except queue.Empty: + return None + return None + +message_bus = MessageBus() + +def do_create_queue(args): + """Create a new message queue: create_queue """ + queue_name = args.strip() + if message_bus.create_queue(queue_name): + return f"Queue '{queue_name}' created successfully" + else: + return f"Queue '{queue_name}' already exists" + +def do_delete_queue(args): + """Delete a message queue: delete_queue """ + queue_name = args.strip() + if message_bus.delete_queue(queue_name): + return f"Queue '{queue_name}' deleted successfully" + else: + return f"Queue '{queue_name}' does not exist" + +def do_send_message(args): + """Send a message to a queue: send_message """ + parts = args.split(maxsplit=1) + if len(parts) != 2: + return "Invalid arguments. Usage: send_message " + queue_name, message = parts + if message_bus.send_message(queue_name, message): + return f"Message sent to queue '{queue_name}'" + else: + return f"Queue '{queue_name}' does not exist" + +def do_receive_message(args): + """Receive a message from a queue: receive_message [timeout]""" + parts = args.split() + if len(parts) not in (1, 2): + return "Invalid arguments. Usage: receive_message [timeout]" + queue_name = parts[0] + timeout = float(parts[1]) if len(parts) == 2 else None + message = message_bus.receive_message(queue_name, timeout) + if message is not None: + return f"Received message from queue '{queue_name}': {message}" + else: + return f"No message available in queue '{queue_name}'" + +commands = { + 'create_queue': do_create_queue, + 'delete_queue': do_delete_queue, + 'send_message': do_send_message, + 'receive_message': do_receive_message, +} + +def get_commands(): + return commands + +def initialize(): + print("Message bus module initialized") + +def shutdown(): + print("Message bus module shut down") \ No newline at end of file diff --git a/modules/messages_example.py b/modules/messages_example.py new file mode 100644 index 0000000..4055fdf --- /dev/null +++ b/modules/messages_example.py @@ -0,0 +1,49 @@ +message_bus = None + +def set_message_bus(bus): + global message_bus + message_bus = bus + +def do_something_and_notify(args): + # Do something... + result = "Operation completed" + + # Notify another module if message bus is available + if message_bus: + message_bus.send_message('notifications', f"Operation result: {result}") + return "Operation completed and notification sent" + else: + return "Operation completed (notification not sent, message bus not available)" + +def do_check_notifications(args): + if not message_bus: + return "Message bus not available" + + message = message_bus.receive_message('notifications', timeout=1) + if message: + return f"Received notification: {message}" + else: + return "No new notifications" + +commands = { + 'do_something': do_something_and_notify, + 'check_notifications': do_check_notifications, +} + +def get_commands(): + return commands + +def initialize(): + global message_bus + try: + from message_bus import message_bus as mb + message_bus = mb + message_bus.create_queue('notifications') + print("Example module initialized with message bus") + except ImportError: + print("Example module initialized without message bus") + +def shutdown(): + if message_bus: + message_bus.delete_queue('notifications') + print("Example module shut down") \ No newline at end of file diff --git a/modules/user_auth.py b/modules/user_auth.py new file mode 100644 index 0000000..940dca2 --- /dev/null +++ b/modules/user_auth.py @@ -0,0 +1,125 @@ +import hashlib +import uuid +import time + +class UserAuth: + def __init__(self): + self.users = {} # Store users as {username: {password_hash, salt}} + self.sessions = {} # Store sessions as {session_id: {username, expiry}} + self.session_duration = 3600 # 1 hour + + def hash_password(self, password, salt=None): + if salt is None: + salt = uuid.uuid4().hex + return hashlib.sha256((password + salt).encode()).hexdigest(), salt + + def register_user(self, username, password): + if username in self.users: + return False, "User already exists" + password_hash, salt = self.hash_password(password) + self.users[username] = {"password_hash": password_hash, "salt": salt} + return True, "User registered successfully" + + def authenticate(self, username, password): + if username not in self.users: + return False, "User not found" + user = self.users[username] + password_hash, _ = self.hash_password(password, user["salt"]) + if password_hash == user["password_hash"]: + session_id = uuid.uuid4().hex + expiry = time.time() + self.session_duration + self.sessions[session_id] = {"username": username, "expiry": expiry} + return True, session_id + return False, "Invalid password" + +def authenticate_request(self, session_id, action): + if action in ['register', 'login']: # These actions don't require authentication + return True, None + + success, result = self.validate_session(session_id) + if not success: + return False, "Authentication required" + return True, result # result here is the username + + def validate_session(self, session_id): + if session_id not in self.sessions: + return False, "Invalid session" + session = self.sessions[session_id] + if time.time() > session["expiry"]: + del self.sessions[session_id] + return False, "Session expired" + return True, session["username"] + + def logout(self, session_id): + if session_id in self.sessions: + del self.sessions[session_id] + return True, "Logged out successfully" + return False, "Invalid session" + +user_auth = UserAuth() + +def do_register(args): + """Register a new user: register """ + try: + username, password = args.split() + success, message = user_auth.register_user(username, password) + return message + except ValueError: + return "Invalid arguments. Usage: register " + +def do_login(args): + """Login a user: login """ + try: + username, password = args.split() + success, result = user_auth.authenticate(username, password) + if success: + return f"Login successful. Session ID: {result}" + return result + except ValueError: + return "Invalid arguments. Usage: login " + +def do_validate(args): + """Validate a session: validate """ + success, result = user_auth.validate_session(args) + if success: + return f"Valid session for user: {result}" + return result + +def do_logout(args): + """Logout a user: logout """ + success, message = user_auth.logout(args) + return message + +def auth_pre_command_hook(command): + action = command.get('action') + session_id = command.get('session_id') + + if action in ['register', 'login']: + return True + + success, result = user_auth.validate_session(session_id) + if not success: + return False + return True + +commands = { + 'register': do_register, + 'login': do_login, + 'validate': do_validate, + 'logout': do_logout, +} + +def get_commands(): + return commands + +def initialize(): + print("User authentication module initialized") + +def shutdown(): + print("User authentication module shut down") + +def register_hooks(hook_manager): + hook_manager.register_hook('pre_command', auth_pre_command_hook) + +def unregister_hooks(hook_manager): + hook_manager.unregister_hook('pre_command', auth_pre_command_hook)