"""Simple publish/subscribe event bus for AYN Antivirus. Decouples the scan engine from consumers like the CLI, logger, quarantine manager, and real-time monitor so each component can react to events independently. """ from __future__ import annotations import logging import threading from enum import Enum, auto from typing import Any, Callable, Dict, List logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Event types # --------------------------------------------------------------------------- class EventType(Enum): """All events emitted by the AYN engine.""" THREAT_FOUND = auto() SCAN_STARTED = auto() SCAN_COMPLETED = auto() FILE_SCANNED = auto() SIGNATURE_UPDATED = auto() QUARANTINE_ACTION = auto() REMEDIATION_ACTION = auto() DASHBOARD_METRIC = auto() # Type alias for subscriber callbacks. Callback = Callable[[EventType, Any], None] # --------------------------------------------------------------------------- # EventBus # --------------------------------------------------------------------------- class EventBus: """Thread-safe publish/subscribe event bus. Usage:: bus = EventBus() bus.subscribe(EventType.THREAT_FOUND, lambda et, data: print(data)) bus.publish(EventType.THREAT_FOUND, {"path": "/tmp/evil.elf"}) """ def __init__(self) -> None: self._subscribers: Dict[EventType, List[Callback]] = {et: [] for et in EventType} self._lock = threading.Lock() # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def subscribe(self, event_type: EventType, callback: Callback) -> None: """Register *callback* to be invoked whenever *event_type* is published. Parameters ---------- event_type: The event to listen for. callback: A callable with signature ``(event_type, data) -> None``. """ with self._lock: if callback not in self._subscribers[event_type]: self._subscribers[event_type].append(callback) def unsubscribe(self, event_type: EventType, callback: Callback) -> None: """Remove a previously-registered callback.""" with self._lock: try: self._subscribers[event_type].remove(callback) except ValueError: pass def publish(self, event_type: EventType, data: Any = None) -> None: """Emit an event, invoking all registered callbacks synchronously. Exceptions raised by individual callbacks are logged and swallowed so that one faulty subscriber cannot break the pipeline. Parameters ---------- event_type: The event being emitted. data: Arbitrary payload — typically a dataclass or dict. """ with self._lock: callbacks = list(self._subscribers[event_type]) for cb in callbacks: try: cb(event_type, data) except Exception: logger.exception( "Subscriber %r raised an exception for event %s", cb, event_type.name, ) def clear(self, event_type: EventType | None = None) -> None: """Remove all subscribers for *event_type*, or all subscribers if ``None``.""" with self._lock: if event_type is None: for et in EventType: self._subscribers[et].clear() else: self._subscribers[event_type].clear() # --------------------------------------------------------------------------- # Module-level singleton # --------------------------------------------------------------------------- event_bus = EventBus()