use std::sync::Arc;
use async_trait::async_trait;
use dyn_clone::DynClone;
use tokio::sync::RwLock;
use crate::events::{DownloadEvent, EventFilter};
pub type HookResult = Result<(), HookError>;
#[derive(Debug, thiserror::Error)]
pub enum HookError {
#[error("Hook execution failed: {0}")]
ExecutionFailed(String),
#[error("Hook execution timed out")]
Timeout,
#[error("{0}")]
Custom(String),
}
#[async_trait]
pub trait EventHook: DynClone + Send + Sync {
async fn on_event(&self, event: &DownloadEvent) -> HookResult;
fn filter(&self) -> EventFilter {
EventFilter::all()
}
fn name(&self) -> &'static str {
"unnamed_hook"
}
fn parallel_execution(&self) -> bool {
true
}
}
dyn_clone::clone_trait_object!(EventHook);
pub struct HookRegistry {
hooks: Arc<RwLock<Vec<Box<dyn EventHook>>>>,
timeout: std::time::Duration,
}
impl HookRegistry {
pub fn new() -> Self {
tracing::debug!("⚙️ Creating new HookRegistry");
Self {
hooks: Arc::new(RwLock::new(Vec::new())),
timeout: std::time::Duration::from_secs(30),
}
}
pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = timeout;
self
}
pub async fn register(&self, hook: impl EventHook + 'static) {
let hook_name = hook.name();
tracing::debug!(
hook_name = hook_name,
parallel_execution = hook.parallel_execution(),
"🔔 Registering new hook"
);
let mut hooks = self.hooks.write().await;
hooks.push(Box::new(hook));
tracing::debug!(hook_name = hook_name, total_hooks = hooks.len(), "✅ Hook registered");
}
pub async fn execute(&self, event: &DownloadEvent) {
tracing::debug!(
event_type = event.event_type(),
download_id = event.download_id(),
"🔔 Executing hooks for event"
);
let hooks: Vec<_> = self.hooks.read().await.clone();
let mut parallel_hooks = Vec::new();
let mut sequential_hooks = Vec::new();
for hook in hooks.iter() {
if hook.filter().matches(event) {
if hook.parallel_execution() {
parallel_hooks.push(hook);
} else {
sequential_hooks.push(hook);
}
}
}
let parallel_count = parallel_hooks.len();
let sequential_count = sequential_hooks.len();
tracing::debug!(
event_type = event.event_type(),
total_hooks = hooks.len(),
parallel_hooks = parallel_count,
sequential_hooks = sequential_count,
"⚙️ Hooks separated by execution mode"
);
let timeout = self.timeout;
let event_arc = Arc::new(event.clone());
let parallel_futures: Vec<_> = parallel_hooks
.into_iter()
.map(|hook| {
let event = Arc::clone(&event_arc);
async move {
match tokio::time::timeout(timeout, hook.on_event(&event)).await {
Ok(Ok(())) => {}
Ok(Err(e)) => {
tracing::warn!(hook = hook.name(), error = %e, "Hook execution failed");
}
Err(_) => {
tracing::warn!(hook = hook.name(), "Hook execution timed out");
}
}
}
})
.collect();
futures_util::future::join_all(parallel_futures).await;
tracing::debug!(
event_type = event.event_type(),
parallel_hooks_completed = parallel_count,
"✅ Parallel hooks completed"
);
for hook in sequential_hooks {
match tokio::time::timeout(timeout, hook.on_event(event)).await {
Ok(Ok(())) => {}
Ok(Err(e)) => {
tracing::warn!(hook = hook.name(), error = %e, "Hook execution failed");
}
Err(_) => {
tracing::warn!(hook = hook.name(), "Hook execution timed out");
}
}
}
tracing::debug!(
event_type = event.event_type(),
sequential_hooks_completed = sequential_count,
"✅ Sequential hooks completed"
);
}
pub async fn count(&self) -> usize {
let hooks = self.hooks.read().await;
hooks.len()
}
pub async fn clear(&self) {
tracing::debug!("⚙️ Clearing all hooks");
let mut hooks = self.hooks.write().await;
let count = hooks.len();
hooks.clear();
tracing::debug!(hooks_cleared = count, "✅ All hooks cleared");
}
}
impl Default for HookRegistry {
fn default() -> Self {
Self::new()
}
}
impl Clone for HookRegistry {
fn clone(&self) -> Self {
Self {
hooks: self.hooks.clone(),
timeout: self.timeout,
}
}
}
impl std::fmt::Debug for HookRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HookRegistry").field("hooks_count", &"<async>").finish()
}
}
impl std::fmt::Display for HookRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HookRegistry(timeout={}s)", self.timeout.as_secs())
}
}
#[macro_export]
macro_rules! simple_hook {
($name:expr, $filter:expr, $closure:expr) => {{
#[derive(Clone)]
struct SimpleHook<F>
where
F: Fn(&$crate::events::DownloadEvent) -> $crate::events::HookResult + Clone + Send + Sync,
{
name: &'static str,
filter: $crate::events::EventFilter,
closure: F,
}
#[$crate::async_trait::async_trait]
impl<F> $crate::events::EventHook for SimpleHook<F>
where
F: Fn(&$crate::events::DownloadEvent) -> $crate::events::HookResult + Clone + Send + Sync,
{
async fn on_event(&self, event: &$crate::events::DownloadEvent) -> $crate::events::HookResult {
(self.closure)(event)
}
fn filter(&self) -> $crate::events::EventFilter {
self.filter.clone()
}
fn name(&self) -> &'static str {
self.name
}
}
SimpleHook {
name: $name,
filter: $filter,
closure: $closure,
}
}};
}