From def8d293b968c58f7963aaa5e096458b7ab2a162 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Sat, 25 Oct 2025 13:11:26 -0400 Subject: [PATCH] fix: add batch delay --- .env.example | 5 +++++ config/settings.py | 1 + webhooks/webhook_batcher.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 7ef2928..1422453 100644 --- a/.env.example +++ b/.env.example @@ -138,6 +138,11 @@ BATCH_DELAY=5.0 # Maximum concurrent series processing MAX_CONCURRENT_SERIES=3 +# Sequential processing delay for bulk downloads (seconds) +# When multiple episodes are downloaded at once, process them one by one with this delay +# Set to 0 to disable sequential processing (parallel mode) +SEQUENTIAL_DELAY=20.0 + # API timeout in seconds TIMEOUT_SECONDS=45 diff --git a/config/settings.py b/config/settings.py index c9b3505..5ef8290 100644 --- a/config/settings.py +++ b/config/settings.py @@ -61,6 +61,7 @@ class NFOGuardConfig: # Batching and performance self.batch_delay = self._get_float_env("BATCH_DELAY", 5.0, 0.1, 300.0) self.max_concurrent = self._get_int_env("MAX_CONCURRENT_SERIES", 3, 1, 10) + self.sequential_delay = self._get_float_env("SEQUENTIAL_DELAY", 20.0, 0.0, 60.0) # Delay between sequential episodes (default 20s) # Database self.db_type = os.environ.get("DB_TYPE", "sqlite").lower() diff --git a/webhooks/webhook_batcher.py b/webhooks/webhook_batcher.py index ec0c971..d7b97d5 100644 --- a/webhooks/webhook_batcher.py +++ b/webhooks/webhook_batcher.py @@ -3,6 +3,7 @@ Webhook Batching System for NFOGuard Handles batching and processing of webhook events to avoid processing storms """ import threading +import time from pathlib import Path from typing import Dict, Set from concurrent.futures import ThreadPoolExecutor @@ -144,7 +145,13 @@ class WebhookBatcher: if processing_mode == 'targeted' and episodes_data: _log("INFO", f"Using targeted episode processing for {len(episodes_data)} episodes") - self.tv_processor.process_webhook_episodes(path_obj, episodes_data) + + # Handle sequential processing for bulk downloads + if len(episodes_data) > 1 and config.sequential_delay > 0: + _log("INFO", f"Processing {len(episodes_data)} episodes sequentially with {config.sequential_delay}s delay") + self._process_episodes_sequentially(path_obj, episodes_data) + else: + self.tv_processor.process_webhook_episodes(path_obj, episodes_data) else: _log("INFO", f"Using series processing mode (fallback or configured)") self.tv_processor.process_series(path_obj) @@ -164,6 +171,29 @@ class WebhookBatcher: with self.lock: self.processing.discard(key) + def _process_episodes_sequentially(self, path_obj: Path, episodes_data: list): + """Process episodes one by one with delays to avoid API spam""" + total_episodes = len(episodes_data) + for i, episode in enumerate(episodes_data, 1): + try: + season = episode.get('seasonNumber', '?') + episode_num = episode.get('episodeNumber', '?') + _log("INFO", f"Processing episode {i}/{total_episodes}: S{season:02d}E{episode_num:02d}") + + # Process single episode + self.tv_processor.process_webhook_episodes(path_obj, [episode]) + + # Add delay between episodes (except for the last one) + if i < total_episodes and config.sequential_delay > 0: + _log("INFO", f"Waiting {config.sequential_delay}s before next episode...") + time.sleep(config.sequential_delay) + + except Exception as e: + _log("ERROR", f"Error processing episode {i}/{total_episodes}: {e}") + # Continue with next episode even if one fails + + _log("INFO", f"Completed sequential processing of {total_episodes} episodes") + def get_status(self) -> Dict: """Get batch queue status""" with self.lock: