use std::fmt;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::Instant;
use crate::types::event_callback::{EventCallback, EventData, EventType};
#[derive(Default, Debug, Clone)]
pub struct PipelineStats {
pub pipeline_start_time: Option<Instant>,
pub stats_deleted_objects: u64,
pub stats_deleted_bytes: u64,
pub stats_failed_objects: u64,
pub stats_skipped_objects: u64,
pub stats_error_count: u64,
pub stats_duration_sec: f64,
pub stats_objects_per_sec: f64,
}
impl From<PipelineStats> for EventData {
fn from(stats: PipelineStats) -> Self {
let mut event_data = EventData::new(EventType::STATS_REPORT);
event_data.stats_deleted_objects = Some(stats.stats_deleted_objects);
event_data.stats_deleted_bytes = Some(stats.stats_deleted_bytes);
event_data.stats_failed_objects = Some(stats.stats_failed_objects);
event_data.stats_skipped_objects = Some(stats.stats_skipped_objects);
event_data.stats_error_count = Some(stats.stats_error_count);
event_data.stats_duration_sec = Some(stats.stats_duration_sec);
event_data.stats_objects_per_sec = Some(stats.stats_objects_per_sec);
event_data
}
}
#[derive(Clone)]
pub struct EventManager {
pub event_callback: Option<Arc<Mutex<Box<dyn EventCallback + Send + Sync>>>>,
pub event_flags: EventType,
pub dry_run: bool,
pub pipeline_stats: Arc<Mutex<PipelineStats>>,
}
impl Default for EventManager {
fn default() -> Self {
Self::new()
}
}
impl EventManager {
pub fn new() -> Self {
Self {
event_callback: None,
event_flags: EventType::ALL_EVENTS,
dry_run: false,
pipeline_stats: Arc::new(Mutex::new(PipelineStats::default())),
}
}
pub fn register_callback<T: EventCallback + Send + Sync + 'static>(
&mut self,
events_flag: EventType,
callback: T,
dry_run: bool,
) {
self.event_callback = Some(Arc::new(Mutex::new(Box::new(callback))));
self.event_flags = events_flag;
self.dry_run = dry_run;
}
pub fn is_callback_registered(&self) -> bool {
self.event_callback.is_some()
}
pub async fn trigger_event(&self, mut event_data: EventData) {
self.update_pipeline_stats(&event_data).await;
if let Some(callback) = &self.event_callback {
let event_type = event_data.event_type;
if self.event_flags.contains(event_type) {
event_data.dry_run = self.dry_run;
callback.lock().await.on_event(event_data).await;
}
if event_type == EventType::PIPELINE_END
&& self.event_flags.contains(EventType::STATS_REPORT)
{
let stats = self.pipeline_stats.lock().await.clone();
let mut stats_event: EventData = stats.into();
stats_event.dry_run = self.dry_run;
callback.lock().await.on_event(stats_event).await;
}
}
}
async fn update_pipeline_stats(&self, event_data: &EventData) {
let mut stats = self.pipeline_stats.lock().await;
match event_data.event_type {
EventType::PIPELINE_START => {
stats.pipeline_start_time = Some(Instant::now());
}
EventType::PIPELINE_END => {
if let Some(start) = stats.pipeline_start_time {
stats.stats_duration_sec = start.elapsed().as_secs_f64();
if stats.stats_duration_sec > 1.0 {
stats.stats_objects_per_sec =
stats.stats_deleted_objects as f64 / stats.stats_duration_sec;
} else {
stats.stats_objects_per_sec = stats.stats_deleted_objects as f64;
}
}
}
EventType::DELETE_COMPLETE => {
stats.stats_deleted_objects += 1;
if let Some(size) = event_data.size {
stats.stats_deleted_bytes += size;
}
}
EventType::DELETE_FAILED => {
stats.stats_failed_objects += 1;
}
EventType::DELETE_FILTERED => {
stats.stats_skipped_objects += 1;
}
EventType::PIPELINE_ERROR => {
stats.stats_error_count += 1;
}
_ => {}
}
}
}
impl fmt::Debug for EventManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventManager")
.field("event_flags", &self.event_flags)
.field("callback_registered", &self.event_callback.is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
struct CollectingCallback {
events: Arc<Mutex<Vec<EventData>>>,
}
impl CollectingCallback {
fn new() -> (Self, Arc<Mutex<Vec<EventData>>>) {
let events = Arc::new(Mutex::new(Vec::new()));
(
Self {
events: events.clone(),
},
events,
)
}
}
#[async_trait]
impl EventCallback for CollectingCallback {
async fn on_event(&mut self, event_data: EventData) {
self.events.lock().await.push(event_data);
}
}
#[tokio::test]
async fn new_manager_has_no_callback() {
let manager = EventManager::new();
assert!(!manager.is_callback_registered());
}
#[tokio::test]
async fn register_callback_and_trigger_event() {
let mut manager = EventManager::new();
let (callback, events) = CollectingCallback::new();
manager.register_callback(EventType::ALL_EVENTS, callback, false);
assert!(manager.is_callback_registered());
let event_data = EventData::new(EventType::PIPELINE_START);
manager.trigger_event(event_data).await;
let collected = events.lock().await;
assert_eq!(collected.len(), 1);
assert_eq!(collected[0].event_type, EventType::PIPELINE_START);
}
#[tokio::test]
async fn pipeline_end_sends_stats_report() {
let mut manager = EventManager::new();
let (callback, events) = CollectingCallback::new();
manager.register_callback(EventType::ALL_EVENTS, callback, false);
manager
.trigger_event(EventData::new(EventType::PIPELINE_START))
.await;
let mut delete_event = EventData::new(EventType::DELETE_COMPLETE);
delete_event.size = Some(1024);
manager.trigger_event(delete_event).await;
manager
.trigger_event(EventData::new(EventType::PIPELINE_END))
.await;
let collected = events.lock().await;
assert_eq!(collected.len(), 4);
assert_eq!(collected[3].event_type, EventType::STATS_REPORT);
assert_eq!(collected[3].stats_deleted_objects, Some(1));
assert_eq!(collected[3].stats_deleted_bytes, Some(1024));
}
#[tokio::test]
async fn event_flag_filtering() {
let mut manager = EventManager::new();
let (callback, events) = CollectingCallback::new();
manager.register_callback(EventType::DELETE_COMPLETE, callback, false);
manager
.trigger_event(EventData::new(EventType::PIPELINE_START))
.await;
manager
.trigger_event(EventData::new(EventType::DELETE_COMPLETE))
.await;
let collected = events.lock().await;
assert_eq!(collected.len(), 1);
assert_eq!(collected[0].event_type, EventType::DELETE_COMPLETE);
}
#[tokio::test]
async fn pipeline_end_without_stats_flag_skips_stats_report() {
let mut manager = EventManager::new();
let (callback, events) = CollectingCallback::new();
manager.register_callback(
EventType::PIPELINE_START | EventType::PIPELINE_END,
callback,
false,
);
manager
.trigger_event(EventData::new(EventType::PIPELINE_START))
.await;
manager
.trigger_event(EventData::new(EventType::PIPELINE_END))
.await;
let collected = events.lock().await;
assert_eq!(collected.len(), 2);
assert_eq!(collected[0].event_type, EventType::PIPELINE_START);
assert_eq!(collected[1].event_type, EventType::PIPELINE_END);
}
#[tokio::test]
async fn dry_run_flag_propagated() {
let mut manager = EventManager::new();
let (callback, events) = CollectingCallback::new();
manager.register_callback(EventType::ALL_EVENTS, callback, true);
manager
.trigger_event(EventData::new(EventType::PIPELINE_START))
.await;
let collected = events.lock().await;
assert!(collected[0].dry_run);
}
#[test]
fn debug_format() {
let manager = EventManager::new();
let debug = format!("{manager:?}");
assert!(debug.contains("callback_registered: false"));
}
#[test]
fn pipeline_stats_to_event_data() {
let stats = PipelineStats {
pipeline_start_time: None,
stats_deleted_objects: 100,
stats_deleted_bytes: 50_000,
stats_failed_objects: 5,
stats_skipped_objects: 10,
stats_error_count: 1,
stats_duration_sec: 10.0,
stats_objects_per_sec: 10.0,
};
let event_data: EventData = stats.into();
assert_eq!(event_data.event_type, EventType::STATS_REPORT);
assert_eq!(event_data.stats_deleted_objects, Some(100));
assert_eq!(event_data.stats_deleted_bytes, Some(50_000));
assert_eq!(event_data.stats_failed_objects, Some(5));
assert_eq!(event_data.stats_skipped_objects, Some(10));
}
}