mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
411 lines
17 KiB
Python
411 lines
17 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Copyright (c) 2025 Allaun
|
|
|
|
"""Core Loader Module.
|
|
|
|
Handles bootstrap and initialization of the NoDupeLabs framework.
|
|
Strictly decoupled: Does not import functional tools directly.
|
|
"""
|
|
|
|
import sys
|
|
import logging
|
|
import platform
|
|
import os
|
|
import multiprocessing
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
|
|
try:
|
|
import psutil
|
|
except ImportError:
|
|
psutil = None # type: ignore[assignment]
|
|
|
|
from .config import load_config
|
|
from .container import container as global_container
|
|
from .tool_system.registry import ToolRegistry
|
|
from .tool_system.loader import create_tool_loader
|
|
from .tool_system.discovery import create_tool_discovery
|
|
from .tool_system.lifecycle import create_lifecycle_manager
|
|
from .tool_system.hot_reload import ToolHotReload
|
|
from .api.ipc import ToolIPCServer
|
|
from .api.codes import ActionCode
|
|
|
|
|
|
class CoreLoader:
|
|
"""Main application loader and bootstrap class.
|
|
|
|
This class is responsible for initializing the entire NoDupeLabs framework,
|
|
including loading configuration, initializing the service container, setting up
|
|
the tool system, discovering and loading tools, and managing the lifecycle
|
|
of all components.
|
|
|
|
Attributes:
|
|
config: The loaded configuration object.
|
|
container: The global service container for dependency injection.
|
|
tool_registry: Registry for managing tool instances.
|
|
tool_loader: Loader for dynamically loading tools.
|
|
tool_discovery: Discovery service for finding available tools.
|
|
tool_lifecycle: Manager for tool lifecycle (init/shutdown).
|
|
hot_reload: Hot reload service for dynamic tool reloading.
|
|
ipc_server: IPC server for programmatic tool access.
|
|
initialized: Flag indicating if the framework has been initialized.
|
|
logger: Logger instance for this class.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize the core loader.
|
|
|
|
Creates a new CoreLoader instance with default values for all
|
|
attributes. The instance is not initialized until initialize()
|
|
is called.
|
|
|
|
Sets up:
|
|
- config: None until loaded
|
|
- container: None until initialized
|
|
- tool_registry: None until initialized
|
|
- tool_loader: None until initialized
|
|
- tool_discovery: None until initialized
|
|
- tool_lifecycle: None until initialized
|
|
- hot_reload: None until initialized
|
|
- ipc_server: None until initialized
|
|
- initialized: False
|
|
- logger: Logger for this module
|
|
"""
|
|
self.config = None
|
|
self.container = None
|
|
self.tool_registry = None
|
|
self.tool_loader = None
|
|
self.tool_discovery = None
|
|
self.tool_lifecycle = None
|
|
self.hot_reload = None
|
|
self.ipc_server = None
|
|
self.initialized = False
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
def initialize(self) -> None:
|
|
"""Initialize the core system framework.
|
|
|
|
Performs a complete initialization of the NoDupeLabs framework, including:
|
|
1. Loading configuration from file
|
|
2. Applying platform-specific autoconfiguration
|
|
3. Initializing the dependency injection container
|
|
4. Setting up the tool system (registry, loader, discovery, lifecycle)
|
|
5. Starting maintenance services (hot reload, IPC server)
|
|
6. Discovering and loading functional tools
|
|
7. Initializing all loaded tools
|
|
8. Performing hash algorithm autotuning
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
Exception: Re-raises any exception that occurs during initialization
|
|
after logging the error with the appropriate action code.
|
|
"""
|
|
if self.initialized:
|
|
return
|
|
|
|
try:
|
|
# 1. Load configuration
|
|
self.config = load_config()
|
|
platform_config = self._apply_platform_autoconfig()
|
|
if hasattr(self.config, 'config'):
|
|
for key, value in platform_config.items():
|
|
if key not in self.config.config:
|
|
self.config.config[key] = value
|
|
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_INIT}] Framework configuration loaded")
|
|
|
|
# 2. Initialize dependency container
|
|
self.container = global_container
|
|
self.container.register_service('config', self.config)
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_INIT}] Service container ready")
|
|
|
|
# 3. Initialize tool system
|
|
self.tool_registry = ToolRegistry()
|
|
self.tool_registry.initialize(self.container)
|
|
self.container.register_service('tool_registry', self.tool_registry)
|
|
|
|
self.tool_loader = create_tool_loader(self.tool_registry)
|
|
self.tool_loader.initialize(self.container)
|
|
self.container.register_service('tool_loader', self.tool_loader)
|
|
|
|
self.tool_discovery = create_tool_discovery()
|
|
self.container.register_service('tool_discovery', self.tool_discovery)
|
|
|
|
self.tool_lifecycle = create_lifecycle_manager(self.tool_registry)
|
|
self.container.register_service('tool_lifecycle', self.tool_lifecycle)
|
|
|
|
# 4. Start maintenance services
|
|
self.hot_reload = ToolHotReload(self.tool_registry, self.tool_loader)
|
|
self.hot_reload.start()
|
|
self.container.register_service('hot_reload', self.hot_reload)
|
|
|
|
# 5. Start programmatic interface (IPC)
|
|
self.ipc_server = ToolIPCServer(self.tool_registry)
|
|
self.ipc_server.start()
|
|
self.container.register_service('ipc_server', self.ipc_server)
|
|
|
|
# 6. Discover and load functional tools (Deduplication, Databases, Hashing, etc.)
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_LOAD}] Starting tool discovery and loading")
|
|
self._discover_and_load_tools()
|
|
|
|
# 7. Lifecycle: Initialize all loaded tools
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_INIT}] Initializing all loaded tools")
|
|
self.tool_lifecycle.initialize_all_tools(self.container)
|
|
|
|
# 8. Post-initialization tasks (e.g. autotuning) handled via dynamic discovery
|
|
self._perform_hash_autotuning()
|
|
|
|
self.initialized = True
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_INIT}] Pure Core Engine initialized successfully")
|
|
self.logger.info(f"[{ActionCode.ACC_ISO_CMP}] Core engine is ISO accessibility compliant")
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"[{ActionCode.FPT_STM_ERR}] Framework startup failed: {e}")
|
|
raise
|
|
|
|
def _discover_and_load_tools(self) -> None:
|
|
"""Discover and load tools from configured directories.
|
|
|
|
Searches for tools in the directories specified in the configuration
|
|
under 'tools.directories'. If no directories are configured or the
|
|
configured directories don't exist, falls back to standard locations
|
|
('nodupe/tools' and 'tools').
|
|
|
|
For each discovered tool, calls _load_single_tool to load it.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Side Effects:
|
|
- Logs discovery and loading actions at INFO level
|
|
- Calls _load_single_tool for each discovered tool
|
|
"""
|
|
config_dict = getattr(self.config, 'config', {})
|
|
tool_dirs = config_dict.get('tools', {}).get('directories', ['tools'])
|
|
|
|
# Absolute paths for discovery
|
|
tool_path_dirs = [Path(p).resolve() for p in tool_dirs if Path(p).exists()]
|
|
if not tool_path_dirs:
|
|
# Fallback to standard locations
|
|
standard_paths = [Path('nodupe/tools').resolve(), Path('tools').resolve()]
|
|
tool_path_dirs = [p for p in standard_paths if p.exists()]
|
|
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_LOAD}] Discovering tools in: {tool_path_dirs}")
|
|
|
|
for tool_dir in tool_path_dirs:
|
|
tools = self.tool_discovery.discover_tools_in_directory(tool_dir)
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_LOAD}] Found {len(tools)} tools in {tool_dir}")
|
|
for tool_info in tools:
|
|
self._load_single_tool(tool_info)
|
|
|
|
def _load_single_tool(self, tool_info: Any) -> None:
|
|
"""Load a tool using the framework's loader.
|
|
|
|
Attempts to load a single tool from the given tool info object.
|
|
If loading succeeds, the tool is instantiated and registered.
|
|
If hot reload is enabled, the tool is also watched for changes.
|
|
|
|
Args:
|
|
tool_info: An object containing information about the tool to load,
|
|
including at minimum 'name' and 'path' attributes.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Side Effects:
|
|
- Logs loading actions at INFO level
|
|
- If loading succeeds, registers tool with tool_loader
|
|
- If hot reload is enabled, starts watching the tool file
|
|
- Logs accessibility compliance if the tool implements the
|
|
required interface
|
|
|
|
Notes:
|
|
If any exception occurs during loading, it is caught and logged
|
|
as an error, but does not propagate to allow other tools to
|
|
continue loading.
|
|
"""
|
|
try:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_LOAD}] Loading tool: {tool_info.name}")
|
|
tool_class = self.tool_loader.load_tool_from_file(tool_info.path)
|
|
if tool_class:
|
|
tool_instance = self.tool_loader.instantiate_tool(tool_class)
|
|
self.tool_loader.register_loaded_tool(tool_instance, tool_info.path)
|
|
|
|
if self.hot_reload:
|
|
self.hot_reload.watch_tool(tool_instance.name, tool_info.path)
|
|
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_LOAD}] Loaded tool: {tool_info.name}")
|
|
|
|
# Check if the tool is accessibility-compliant
|
|
if hasattr(tool_instance, 'get_ipc_socket_documentation'):
|
|
self.logger.info(f"[{ActionCode.ACC_ISO_CMP}] Tool {tool_info.name} is ISO accessibility compliant")
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"[{ActionCode.FPT_FLS_FAIL}] Failed to load tool {tool_info.name}: {e}")
|
|
|
|
def _perform_hash_autotuning(self) -> None:
|
|
"""Perform hash algorithm autotuning if the hashing tool is present.
|
|
|
|
Attempts to optimize the hash algorithm used by the hasher service
|
|
by running an autotuning process. This is only performed if:
|
|
1. A hasher service is registered in the container
|
|
2. The autotune logic can be imported from the hashing tool
|
|
|
|
The optimal algorithm is determined by benchmarking different algorithms
|
|
and selecting the fastest one for the current system.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Side Effects:
|
|
- If hasher service exists and autotuning succeeds, calls
|
|
hasher.set_algorithm() with the optimal algorithm name
|
|
- Logs the optimal algorithm at INFO level
|
|
- Silently handles ImportError if autotune logic is not available
|
|
- Logs any other exceptions as errors
|
|
"""
|
|
try:
|
|
# Check if hasher service was registered by any loaded tool
|
|
hasher = self.container.get_service('hasher_service')
|
|
if not hasher: return
|
|
|
|
# Dynamic import of autotune logic from the hashing tool
|
|
try:
|
|
from ..tools.hashing.autotune_logic import autotune_hash_algorithm
|
|
self.logger.info("Starting hash algorithm autotuning...")
|
|
results = autotune_hash_algorithm()
|
|
algo = results['optimal_algorithm']
|
|
self.logger.info(f"[{ActionCode.FDP_DAU_HASH}] Optimal algorithm identified: {algo}")
|
|
|
|
if hasattr(hasher, 'set_algorithm'):
|
|
hasher.set_algorithm(algo)
|
|
except ImportError:
|
|
self.logger.debug("Hashing autotune logic not available in toolpath")
|
|
except Exception as e:
|
|
self.logger.error(f"[{ActionCode.FPT_STM_ERR}] Autotune failed: {e}")
|
|
|
|
def shutdown(self) -> None:
|
|
"""Gracefully shutdown the framework and all loaded tools.
|
|
|
|
Performs a clean shutdown of the entire framework, including:
|
|
1. Shutting down all tools via the lifecycle manager
|
|
2. Stopping the hot reload service
|
|
3. Stopping the IPC server
|
|
4. Shutting down the tool registry
|
|
5. Compressing old log files (if maintenance tool is available)
|
|
|
|
Returns:
|
|
None
|
|
|
|
Side Effects:
|
|
- Logs all shutdown actions at INFO level
|
|
- Sets initialized to False
|
|
- If LogCompressor is available, compresses old log files
|
|
|
|
Notes:
|
|
This method is safe to call even if the framework was not fully
|
|
initialized. It will check for the presence of each component
|
|
before attempting to shut it down.
|
|
Exceptions during shutdown are caught and logged but do not propagate.
|
|
"""
|
|
if not self.initialized: return
|
|
|
|
try:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Starting framework shutdown")
|
|
|
|
if self.tool_lifecycle:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Shutting down all tools")
|
|
self.tool_lifecycle.shutdown_all_tools()
|
|
if self.hot_reload:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Stopping hot reload")
|
|
self.hot_reload.stop()
|
|
if self.ipc_server:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Stopping IPC server")
|
|
self.ipc_server.stop()
|
|
if self.tool_registry:
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Shutting down registry")
|
|
self.tool_registry.shutdown()
|
|
|
|
self.initialized = False
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Framework shutdown complete")
|
|
|
|
# Final maintenance using maintenance tool if available
|
|
try:
|
|
from ..tools.maintenance.log_compressor import LogCompressor
|
|
log_dir = getattr(self.config, 'config', {}).get('log_dir', 'logs')
|
|
self.logger.info(f"[{ActionCode.FIA_UAU_SHUTDOWN}] Compressing old logs in {log_dir}")
|
|
LogCompressor.compress_old_logs(log_dir)
|
|
except ImportError:
|
|
pass
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"[{ActionCode.FPT_STM_ERR}] Shutdown error: {e}")
|
|
|
|
def _apply_platform_autoconfig(self) -> Dict[str, Any]:
|
|
"""Apply system resource-based autoconfiguration.
|
|
|
|
Determines platform-specific configuration settings based on the
|
|
detected system resources and capabilities. Currently provides
|
|
default values for database path and log directory.
|
|
|
|
Returns:
|
|
Dict[str, Any]: A dictionary containing platform-specific
|
|
configuration values with keys:
|
|
- 'db_path': Path to the database file
|
|
- 'log_dir': Path to the log directory
|
|
|
|
Notes:
|
|
This is a simplified implementation. Future versions may
|
|
detect CPU cores, memory, and other system resources to
|
|
provide more informed autoconfiguration.
|
|
"""
|
|
config: Dict[str, Any] = {
|
|
'db_path': 'output/index.db',
|
|
'log_dir': 'logs'
|
|
}
|
|
# Simplified for Core
|
|
return config
|
|
|
|
def _detect_system_resources(self) -> Dict[str, Any]:
|
|
"""Detect system resources (CPU, RAM).
|
|
|
|
Gathers information about the system's available resources,
|
|
including the number of CPU cores. This information can be
|
|
used for performance tuning and optimization.
|
|
|
|
Returns:
|
|
Dict[str, Any]: A dictionary containing system resource
|
|
information with keys:
|
|
- 'cpu_cores': Number of CPU cores available
|
|
"""
|
|
return {'cpu_cores': multiprocessing.cpu_count()}
|
|
|
|
|
|
def bootstrap() -> 'CoreLoader':
|
|
"""Global bootstrap entry point.
|
|
|
|
Creates and initializes a new CoreLoader instance, configuring
|
|
logging in the process. This is the main entry point for
|
|
starting the NoDupeLabs framework programmatically.
|
|
|
|
Returns:
|
|
CoreLoader: An initialized CoreLoader instance ready for use.
|
|
|
|
Example:
|
|
>>> loader = bootstrap()
|
|
>>> # Framework is now initialized and ready
|
|
>>> # ... use the framework ...
|
|
>>> loader.shutdown() # Clean shutdown when done
|
|
|
|
Notes:
|
|
- This function sets up basic logging with INFO level
|
|
- The returned loader has already called initialize()
|
|
- Always call shutdown() on the returned loader when done
|
|
to ensure clean resource cleanup
|
|
"""
|
|
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')
|
|
loader = CoreLoader()
|
|
loader.initialize()
|
|
return loader
|