145 lines
4.8 KiB
Python
145 lines
4.8 KiB
Python
import logging
|
|
import subprocess
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
|
|
from core.emulator.enumerations import EventTypes
|
|
from core.errors import CoreError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HookManager:
|
|
"""
|
|
Provides functionality for managing and running script/callback hooks.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""
|
|
Create a HookManager instance.
|
|
"""
|
|
self.script_hooks: dict[EventTypes, dict[str, str]] = {}
|
|
self.callback_hooks: dict[EventTypes, list[Callable[[], None]]] = {}
|
|
|
|
def reset(self) -> None:
|
|
"""
|
|
Clear all current hooks.
|
|
|
|
:return: nothing
|
|
"""
|
|
self.script_hooks.clear()
|
|
self.callback_hooks.clear()
|
|
|
|
def add_script_hook(self, state: EventTypes, file_name: str, data: str) -> None:
|
|
"""
|
|
Add a hook script to run for a given state.
|
|
|
|
:param state: state to run hook on
|
|
:param file_name: hook file name
|
|
:param data: file data
|
|
:return: nothing
|
|
"""
|
|
logger.info("setting state hook: %s - %s", state, file_name)
|
|
state_hooks = self.script_hooks.setdefault(state, {})
|
|
if file_name in state_hooks:
|
|
raise CoreError(
|
|
f"adding duplicate state({state.name}) hook script({file_name})"
|
|
)
|
|
state_hooks[file_name] = data
|
|
|
|
def delete_script_hook(self, state: EventTypes, file_name: str) -> None:
|
|
"""
|
|
Delete a script hook from a given state.
|
|
|
|
:param state: state to delete script hook from
|
|
:param file_name: name of script to delete
|
|
:return: nothing
|
|
"""
|
|
state_hooks = self.script_hooks.get(state, {})
|
|
if file_name not in state_hooks:
|
|
raise CoreError(
|
|
f"deleting state({state.name}) hook script({file_name}) "
|
|
"that does not exist"
|
|
)
|
|
del state_hooks[file_name]
|
|
|
|
def add_callback_hook(
|
|
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
|
) -> None:
|
|
"""
|
|
Add a hook callback to run for a state.
|
|
|
|
:param state: state to add hook for
|
|
:param hook: callback to run
|
|
:return: nothing
|
|
"""
|
|
hooks = self.callback_hooks.setdefault(state, [])
|
|
if hook in hooks:
|
|
name = getattr(callable, "__name__", repr(hook))
|
|
raise CoreError(
|
|
f"adding duplicate state({state.name}) hook callback({name})"
|
|
)
|
|
hooks.append(hook)
|
|
|
|
def delete_callback_hook(
|
|
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
|
) -> None:
|
|
"""
|
|
Delete a state hook.
|
|
|
|
:param state: state to delete hook for
|
|
:param hook: hook to delete
|
|
:return: nothing
|
|
"""
|
|
hooks = self.callback_hooks.get(state, [])
|
|
if hook not in hooks:
|
|
name = getattr(callable, "__name__", repr(hook))
|
|
raise CoreError(
|
|
f"deleting state({state.name}) hook callback({name}) "
|
|
"that does not exist"
|
|
)
|
|
hooks.remove(hook)
|
|
|
|
def run_hooks(
|
|
self, state: EventTypes, directory: Path, env: dict[str, str]
|
|
) -> None:
|
|
"""
|
|
Run all hooks for the current state.
|
|
|
|
:param state: state to run hooks for
|
|
:param directory: directory to run script hooks within
|
|
:param env: environment to run script hooks with
|
|
:return: nothing
|
|
"""
|
|
for state_hooks in self.script_hooks.get(state, {}):
|
|
for file_name, data in state_hooks.items():
|
|
logger.info("running hook %s", file_name)
|
|
file_path = directory / file_name
|
|
log_path = directory / f"{file_name}.log"
|
|
try:
|
|
with file_path.open("w") as f:
|
|
f.write(data)
|
|
with log_path.open("w") as f:
|
|
args = ["/bin/sh", file_name]
|
|
subprocess.check_call(
|
|
args,
|
|
stdout=f,
|
|
stderr=subprocess.STDOUT,
|
|
close_fds=True,
|
|
cwd=directory,
|
|
env=env,
|
|
)
|
|
except (OSError, subprocess.CalledProcessError) as e:
|
|
raise CoreError(
|
|
f"failure running state({state.name}) "
|
|
f"hook script({file_name}): {e}"
|
|
)
|
|
for hook in self.callback_hooks.get(state, []):
|
|
try:
|
|
hook()
|
|
except Exception as e:
|
|
name = getattr(callable, "__name__", repr(hook))
|
|
raise CoreError(
|
|
f"failure running state({state.name}) "
|
|
f"hook callback({name}): {e}"
|
|
)
|