mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 02:41:06 -05:00
fixed
This commit is contained in:
@@ -2,33 +2,61 @@
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Dict, Any, Optional, Set, List
|
||||
from enum import Enum
|
||||
from typing import Dict, Any, Optional, Set, List, TypedDict, ClassVar, Type, Union, Protocol
|
||||
from enum import Enum, auto
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from ..utils.exceptions import (
|
||||
ComponentError,
|
||||
ErrorContext,
|
||||
ErrorSeverity
|
||||
)
|
||||
from ..utils.path_manager import ensure_directory
|
||||
|
||||
logger = logging.getLogger("VideoArchiver")
|
||||
|
||||
class ComponentState(Enum):
|
||||
"""Possible states of a component"""
|
||||
UNREGISTERED = "unregistered"
|
||||
REGISTERED = "registered"
|
||||
INITIALIZING = "initializing"
|
||||
READY = "ready"
|
||||
ERROR = "error"
|
||||
SHUTDOWN = "shutdown"
|
||||
UNREGISTERED = auto()
|
||||
REGISTERED = auto()
|
||||
INITIALIZING = auto()
|
||||
READY = auto()
|
||||
ERROR = auto()
|
||||
SHUTDOWN = auto()
|
||||
|
||||
class ComponentDependencyError(Exception):
|
||||
"""Raised when component dependencies cannot be satisfied"""
|
||||
pass
|
||||
class ComponentHistory(TypedDict):
|
||||
"""Type definition for component history entry"""
|
||||
component: str
|
||||
state: str
|
||||
timestamp: str
|
||||
error: Optional[str]
|
||||
duration: float
|
||||
|
||||
class ComponentLifecycleError(Exception):
|
||||
"""Raised when component lifecycle operations fail"""
|
||||
pass
|
||||
class ComponentStatus(TypedDict):
|
||||
"""Type definition for component status"""
|
||||
state: str
|
||||
registration_time: Optional[str]
|
||||
initialization_time: Optional[str]
|
||||
dependencies: Set[str]
|
||||
dependents: Set[str]
|
||||
error: Optional[str]
|
||||
health: bool
|
||||
|
||||
class Initializable(Protocol):
|
||||
"""Protocol for initializable components"""
|
||||
async def initialize(self) -> None:
|
||||
"""Initialize the component"""
|
||||
...
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Shutdown the component"""
|
||||
...
|
||||
|
||||
class Component:
|
||||
"""Base class for managed components"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
self.state = ComponentState.UNREGISTERED
|
||||
self.dependencies: Set[str] = set()
|
||||
@@ -36,33 +64,74 @@ class Component:
|
||||
self.registration_time: Optional[datetime] = None
|
||||
self.initialization_time: Optional[datetime] = None
|
||||
self.error: Optional[str] = None
|
||||
self._health_check_task: Optional[asyncio.Task] = None
|
||||
|
||||
async def initialize(self) -> None:
|
||||
"""Initialize the component"""
|
||||
"""
|
||||
Initialize the component.
|
||||
|
||||
Raises:
|
||||
ComponentError: If initialization fails
|
||||
"""
|
||||
pass
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Shutdown the component"""
|
||||
pass
|
||||
"""
|
||||
Shutdown the component.
|
||||
|
||||
Raises:
|
||||
ComponentError: If shutdown fails
|
||||
"""
|
||||
if self._health_check_task:
|
||||
self._health_check_task.cancel()
|
||||
try:
|
||||
await self._health_check_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
def is_healthy(self) -> bool:
|
||||
"""Check if component is healthy"""
|
||||
return self.state == ComponentState.READY and not self.error
|
||||
|
||||
class ComponentTracker:
|
||||
"""Tracks component states and relationships"""
|
||||
|
||||
def __init__(self):
|
||||
self.states: Dict[str, ComponentState] = {}
|
||||
self.history: List[Dict[str, Any]] = []
|
||||
MAX_HISTORY: ClassVar[int] = 1000 # Maximum history entries to keep
|
||||
|
||||
def update_state(self, name: str, state: ComponentState, error: Optional[str] = None) -> None:
|
||||
def __init__(self) -> None:
|
||||
self.states: Dict[str, ComponentState] = {}
|
||||
self.history: List[ComponentHistory] = []
|
||||
|
||||
def update_state(
|
||||
self,
|
||||
name: str,
|
||||
state: ComponentState,
|
||||
error: Optional[str] = None
|
||||
) -> None:
|
||||
"""Update component state"""
|
||||
self.states[name] = state
|
||||
self.history.append({
|
||||
"component": name,
|
||||
"state": state.value,
|
||||
"timestamp": datetime.utcnow(),
|
||||
"error": error
|
||||
})
|
||||
|
||||
# Add history entry
|
||||
now = datetime.utcnow()
|
||||
duration = 0.0
|
||||
if self.history:
|
||||
last_entry = self.history[-1]
|
||||
last_time = datetime.fromisoformat(last_entry["timestamp"])
|
||||
duration = (now - last_time).total_seconds()
|
||||
|
||||
def get_component_history(self, name: str) -> List[Dict[str, Any]]:
|
||||
self.history.append(ComponentHistory(
|
||||
component=name,
|
||||
state=state.name,
|
||||
timestamp=now.isoformat(),
|
||||
error=error,
|
||||
duration=duration
|
||||
))
|
||||
|
||||
# Cleanup old history
|
||||
if len(self.history) > self.MAX_HISTORY:
|
||||
self.history = self.history[-self.MAX_HISTORY:]
|
||||
|
||||
def get_component_history(self, name: str) -> List[ComponentHistory]:
|
||||
"""Get state history for a component"""
|
||||
return [
|
||||
entry for entry in self.history
|
||||
@@ -72,12 +141,33 @@ class ComponentTracker:
|
||||
class DependencyManager:
|
||||
"""Manages component dependencies"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.dependencies: Dict[str, Set[str]] = {}
|
||||
self.dependents: Dict[str, Set[str]] = {}
|
||||
|
||||
def add_dependency(self, component: str, dependency: str) -> None:
|
||||
"""Add a dependency relationship"""
|
||||
"""
|
||||
Add a dependency relationship.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
dependency: Dependency name
|
||||
|
||||
Raises:
|
||||
ComponentError: If dependency cycle is detected
|
||||
"""
|
||||
# Check for cycles
|
||||
if self._would_create_cycle(component, dependency):
|
||||
raise ComponentError(
|
||||
f"Dependency cycle detected: {component} -> {dependency}",
|
||||
context=ErrorContext(
|
||||
"DependencyManager",
|
||||
"add_dependency",
|
||||
{"component": component, "dependency": dependency},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
if component not in self.dependencies:
|
||||
self.dependencies[component] = set()
|
||||
self.dependencies[component].add(dependency)
|
||||
@@ -86,6 +176,23 @@ class DependencyManager:
|
||||
self.dependents[dependency] = set()
|
||||
self.dependents[dependency].add(component)
|
||||
|
||||
def _would_create_cycle(self, component: str, dependency: str) -> bool:
|
||||
"""Check if adding dependency would create a cycle"""
|
||||
visited = set()
|
||||
|
||||
def has_path(start: str, end: str) -> bool:
|
||||
if start == end:
|
||||
return True
|
||||
if start in visited:
|
||||
return False
|
||||
visited.add(start)
|
||||
return any(
|
||||
has_path(dep, end)
|
||||
for dep in self.dependencies.get(start, set())
|
||||
)
|
||||
|
||||
return has_path(dependency, component)
|
||||
|
||||
def get_dependencies(self, component: str) -> Set[str]:
|
||||
"""Get dependencies for a component"""
|
||||
return self.dependencies.get(component, set())
|
||||
@@ -95,27 +202,72 @@ class DependencyManager:
|
||||
return self.dependents.get(component, set())
|
||||
|
||||
def get_initialization_order(self) -> List[str]:
|
||||
"""Get components in dependency order"""
|
||||
visited = set()
|
||||
order = []
|
||||
"""
|
||||
Get components in dependency order.
|
||||
|
||||
Returns:
|
||||
List of component names in initialization order
|
||||
|
||||
Raises:
|
||||
ComponentError: If dependency cycle is detected
|
||||
"""
|
||||
visited: Set[str] = set()
|
||||
temp_visited: Set[str] = set()
|
||||
order: List[str] = []
|
||||
|
||||
def visit(component: str) -> None:
|
||||
if component in temp_visited:
|
||||
cycle = " -> ".join(
|
||||
name for name in self.dependencies
|
||||
if name in temp_visited
|
||||
)
|
||||
raise ComponentError(
|
||||
f"Dependency cycle detected: {cycle}",
|
||||
context=ErrorContext(
|
||||
"DependencyManager",
|
||||
"get_initialization_order",
|
||||
{"cycle": cycle},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
if component in visited:
|
||||
return
|
||||
visited.add(component)
|
||||
|
||||
temp_visited.add(component)
|
||||
for dep in self.dependencies.get(component, set()):
|
||||
visit(dep)
|
||||
temp_visited.remove(component)
|
||||
visited.add(component)
|
||||
order.append(component)
|
||||
|
||||
for component in self.dependencies:
|
||||
visit(component)
|
||||
try:
|
||||
for component in self.dependencies:
|
||||
if component not in visited:
|
||||
visit(component)
|
||||
except RecursionError:
|
||||
raise ComponentError(
|
||||
"Dependency resolution exceeded maximum recursion depth",
|
||||
context=ErrorContext(
|
||||
"DependencyManager",
|
||||
"get_initialization_order",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
return order
|
||||
|
||||
class ComponentManager:
|
||||
"""Manages VideoArchiver components"""
|
||||
|
||||
def __init__(self, cog):
|
||||
CORE_COMPONENTS: ClassVar[Dict[str, Tuple[Type[Any], Set[str]]]] = {
|
||||
"config_manager": ("..config_manager.ConfigManager", set()),
|
||||
"processor": ("..processor.core.Processor", {"config_manager"}),
|
||||
"queue_manager": ("..queue.manager.EnhancedVideoQueueManager", {"config_manager"}),
|
||||
"ffmpeg_mgr": ("..ffmpeg.ffmpeg_manager.FFmpegManager", set())
|
||||
}
|
||||
|
||||
def __init__(self, cog: Any) -> None:
|
||||
self.cog = cog
|
||||
self._components: Dict[str, Component] = {}
|
||||
self.tracker = ComponentTracker()
|
||||
@@ -124,21 +276,41 @@ class ComponentManager:
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
component: Any,
|
||||
component: Union[Component, Any],
|
||||
dependencies: Optional[Set[str]] = None
|
||||
) -> None:
|
||||
"""Register a component with dependencies"""
|
||||
"""
|
||||
Register a component with dependencies.
|
||||
|
||||
Args:
|
||||
name: Component name
|
||||
component: Component instance
|
||||
dependencies: Optional set of dependency names
|
||||
|
||||
Raises:
|
||||
ComponentError: If registration fails
|
||||
"""
|
||||
try:
|
||||
# Wrap non-Component objects
|
||||
if not isinstance(component, Component):
|
||||
component = Component(name)
|
||||
wrapped = Component(name)
|
||||
if isinstance(component, Initializable):
|
||||
wrapped.initialize = component.initialize
|
||||
wrapped.shutdown = component.shutdown
|
||||
component = wrapped
|
||||
|
||||
# Register dependencies
|
||||
if dependencies:
|
||||
for dep in dependencies:
|
||||
if dep not in self._components:
|
||||
raise ComponentDependencyError(
|
||||
f"Dependency {dep} not registered for {name}"
|
||||
raise ComponentError(
|
||||
f"Dependency {dep} not registered for {name}",
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"register",
|
||||
{"component": name, "dependency": dep},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
self.dependency_manager.add_dependency(name, dep)
|
||||
|
||||
@@ -149,19 +321,33 @@ class ComponentManager:
|
||||
logger.debug(f"Registered component: {name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering component {name}: {e}")
|
||||
error = f"Failed to register component {name}: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
self.tracker.update_state(name, ComponentState.ERROR, str(e))
|
||||
raise ComponentLifecycleError(f"Failed to register component: {str(e)}")
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"register",
|
||||
{"component": name},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
async def initialize_components(self) -> None:
|
||||
"""Initialize all components in dependency order"""
|
||||
"""
|
||||
Initialize all components in dependency order.
|
||||
|
||||
Raises:
|
||||
ComponentError: If initialization fails
|
||||
"""
|
||||
try:
|
||||
# Get initialization order
|
||||
init_order = self.dependency_manager.get_initialization_order()
|
||||
|
||||
# Initialize core components first
|
||||
await self._initialize_core_components()
|
||||
|
||||
# Get initialization order
|
||||
init_order = self.dependency_manager.get_initialization_order()
|
||||
|
||||
# Initialize remaining components
|
||||
for name in init_order:
|
||||
if name not in self._components:
|
||||
@@ -174,88 +360,172 @@ class ComponentManager:
|
||||
component.initialization_time = datetime.utcnow()
|
||||
self.tracker.update_state(name, ComponentState.READY)
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing component {name}: {e}")
|
||||
error = f"Failed to initialize component {name}: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
self.tracker.update_state(name, ComponentState.ERROR, str(e))
|
||||
raise ComponentLifecycleError(
|
||||
f"Failed to initialize component {name}: {str(e)}"
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"initialize_components",
|
||||
{"component": name},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during component initialization: {e}")
|
||||
raise ComponentLifecycleError(f"Component initialization failed: {str(e)}")
|
||||
error = f"Component initialization failed: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"initialize_components",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
async def _initialize_core_components(self) -> None:
|
||||
"""Initialize core system components"""
|
||||
from ..config_manager import ConfigManager
|
||||
from ..processor.core import Processor
|
||||
from ..queue.manager import EnhancedVideoQueueManager
|
||||
from ..ffmpeg.ffmpeg_manager import FFmpegManager
|
||||
"""
|
||||
Initialize core system components.
|
||||
|
||||
Raises:
|
||||
ComponentError: If core component initialization fails
|
||||
"""
|
||||
try:
|
||||
for name, (component_path, deps) in self.CORE_COMPONENTS.items():
|
||||
module_path, class_name = component_path.rsplit(".", 1)
|
||||
module = __import__(module_path, fromlist=[class_name])
|
||||
component_class = getattr(module, class_name)
|
||||
|
||||
if name == "processor":
|
||||
component = component_class(self.cog)
|
||||
elif name == "ffmpeg_mgr":
|
||||
component = component_class(self.cog)
|
||||
else:
|
||||
component = component_class()
|
||||
|
||||
core_components = {
|
||||
"config_manager": (ConfigManager(self.cog), set()),
|
||||
"processor": (Processor(self.cog), {"config_manager"}),
|
||||
"queue_manager": (EnhancedVideoQueueManager(), {"config_manager"}),
|
||||
"ffmpeg_mgr": (FFmpegManager(self.cog), set())
|
||||
}
|
||||
self.register(name, component, deps)
|
||||
|
||||
for name, (component, deps) in core_components.items():
|
||||
self.register(name, component, deps)
|
||||
# Initialize paths
|
||||
await self._initialize_paths()
|
||||
|
||||
# Initialize paths
|
||||
await self._initialize_paths()
|
||||
except Exception as e:
|
||||
error = f"Failed to initialize core components: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"_initialize_core_components",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
async def _initialize_paths(self) -> None:
|
||||
"""Initialize required paths"""
|
||||
from pathlib import Path
|
||||
from ..utils.path_manager import ensure_directory
|
||||
"""
|
||||
Initialize required paths.
|
||||
|
||||
Raises:
|
||||
ComponentError: If path initialization fails
|
||||
"""
|
||||
try:
|
||||
data_dir = Path(self.cog.bot.data_path) / "VideoArchiver"
|
||||
download_dir = data_dir / "downloads"
|
||||
|
||||
data_dir = Path(self.cog.bot.data_path) / "VideoArchiver"
|
||||
download_dir = data_dir / "downloads"
|
||||
# Ensure directories exist
|
||||
await ensure_directory(data_dir)
|
||||
await ensure_directory(download_dir)
|
||||
|
||||
# Ensure directories exist
|
||||
await ensure_directory(data_dir)
|
||||
await ensure_directory(download_dir)
|
||||
# Register paths
|
||||
self.register("data_path", data_dir)
|
||||
self.register("download_path", download_dir)
|
||||
|
||||
# Register paths
|
||||
self.register("data_path", data_dir)
|
||||
self.register("download_path", download_dir)
|
||||
except Exception as e:
|
||||
error = f"Failed to initialize paths: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"_initialize_paths",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
def get(self, name: str) -> Optional[Any]:
|
||||
def get(self, name: str) -> Optional[Component]:
|
||||
"""Get a registered component"""
|
||||
component = self._components.get(name)
|
||||
return component if isinstance(component, Component) else None
|
||||
return self._components.get(name)
|
||||
|
||||
async def shutdown_components(self) -> None:
|
||||
"""Shutdown components in reverse dependency order"""
|
||||
shutdown_order = reversed(self.dependency_manager.get_initialization_order())
|
||||
"""
|
||||
Shutdown components in reverse dependency order.
|
||||
|
||||
for name in shutdown_order:
|
||||
if name not in self._components:
|
||||
continue
|
||||
|
||||
component = self._components[name]
|
||||
try:
|
||||
await component.shutdown()
|
||||
self.tracker.update_state(name, ComponentState.SHUTDOWN)
|
||||
except Exception as e:
|
||||
logger.error(f"Error shutting down component {name}: {e}")
|
||||
self.tracker.update_state(name, ComponentState.ERROR, str(e))
|
||||
Raises:
|
||||
ComponentError: If shutdown fails
|
||||
"""
|
||||
try:
|
||||
shutdown_order = reversed(self.dependency_manager.get_initialization_order())
|
||||
|
||||
for name in shutdown_order:
|
||||
if name not in self._components:
|
||||
continue
|
||||
|
||||
component = self._components[name]
|
||||
try:
|
||||
await component.shutdown()
|
||||
self.tracker.update_state(name, ComponentState.SHUTDOWN)
|
||||
except Exception as e:
|
||||
error = f"Error shutting down component {name}: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
self.tracker.update_state(name, ComponentState.ERROR, str(e))
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"shutdown_components",
|
||||
{"component": name},
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error = f"Component shutdown failed: {str(e)}"
|
||||
logger.error(error, exc_info=True)
|
||||
raise ComponentError(
|
||||
error,
|
||||
context=ErrorContext(
|
||||
"ComponentManager",
|
||||
"shutdown_components",
|
||||
None,
|
||||
ErrorSeverity.HIGH
|
||||
)
|
||||
)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all registered components"""
|
||||
self._components.clear()
|
||||
logger.debug("Cleared all components")
|
||||
|
||||
def get_component_status(self) -> Dict[str, Any]:
|
||||
"""Get status of all components"""
|
||||
def get_component_status(self) -> Dict[str, ComponentStatus]:
|
||||
"""
|
||||
Get status of all components.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping component names to their status
|
||||
"""
|
||||
return {
|
||||
name: {
|
||||
"state": self.tracker.states.get(name, ComponentState.UNREGISTERED).value,
|
||||
"registration_time": component.registration_time,
|
||||
"initialization_time": component.initialization_time,
|
||||
"dependencies": self.dependency_manager.get_dependencies(name),
|
||||
"dependents": self.dependency_manager.get_dependents(name),
|
||||
"error": component.error
|
||||
}
|
||||
name: ComponentStatus(
|
||||
state=self.tracker.states.get(name, ComponentState.UNREGISTERED).name,
|
||||
registration_time=component.registration_time.isoformat() if component.registration_time else None,
|
||||
initialization_time=component.initialization_time.isoformat() if component.initialization_time else None,
|
||||
dependencies=self.dependency_manager.get_dependencies(name),
|
||||
dependents=self.dependency_manager.get_dependents(name),
|
||||
error=component.error,
|
||||
health=component.is_healthy()
|
||||
)
|
||||
for name, component in self._components.items()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user