Added message_bus module for module to module comms. Some changes to core_daemon and daemon_cli...
This commit is contained in:
parent
a411cdee81
commit
c5ded90841
163
core_daemon.py
163
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
|
||||
|
161
daemon_cli.py
161
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 <module_name>"""
|
||||
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 <module_name>"""
|
||||
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 <command> [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 <directory_path>"""
|
||||
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()
|
||||
|
110
hook_manager.py
Normal file
110
hook_manager.py
Normal file
@ -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()
|
252
module_manager.py
Normal file
252
module_manager.py
Normal file
@ -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
|
95
modules/message_bus.py
Normal file
95
modules/message_bus.py
Normal file
@ -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>"""
|
||||
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>"""
|
||||
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 <queue_name> <message>"""
|
||||
parts = args.split(maxsplit=1)
|
||||
if len(parts) != 2:
|
||||
return "Invalid arguments. Usage: send_message <queue_name> <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 <queue_name> [timeout]"""
|
||||
parts = args.split()
|
||||
if len(parts) not in (1, 2):
|
||||
return "Invalid arguments. Usage: receive_message <queue_name> [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")
|
49
modules/messages_example.py
Normal file
49
modules/messages_example.py
Normal file
@ -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")
|
125
modules/user_auth.py
Normal file
125
modules/user_auth.py
Normal file
@ -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 <username> <password>"""
|
||||
try:
|
||||
username, password = args.split()
|
||||
success, message = user_auth.register_user(username, password)
|
||||
return message
|
||||
except ValueError:
|
||||
return "Invalid arguments. Usage: register <username> <password>"
|
||||
|
||||
def do_login(args):
|
||||
"""Login a user: login <username> <password>"""
|
||||
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 <username> <password>"
|
||||
|
||||
def do_validate(args):
|
||||
"""Validate a session: validate <session_id>"""
|
||||
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 <session_id>"""
|
||||
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)
|
Loading…
Reference in New Issue
Block a user