Skip to content

Commit 66b7540

Browse files
committed
Add Prowlarr statistics refresher thread
- Introduced a background thread to refresh Prowlarr statistics every 5 minutes. - Implemented start and shutdown logic for the new thread. - Enhanced logging for thread operations to improve monitoring and error handling.
1 parent fdc30ca commit 66b7540

File tree

1 file changed

+82
-4
lines changed

1 file changed

+82
-4
lines changed

src/primary/background.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
# Swaparr processing thread
4545
swaparr_thread = None
4646

47+
# Background refresher for Prowlarr statistics
48+
prowlarr_stats_thread = None
49+
4750
# Define which apps have background processing cycles
4851
CYCLICAL_APP_TYPES = ["sonarr", "radarr", "lidarr", "readarr", "whisparr", "eros"]
4952

@@ -693,6 +696,16 @@ def shutdown_threads():
693696
else:
694697
logger.info("Hourly API cap scheduler stopped")
695698

699+
# Stop the Prowlarr stats refresher
700+
global prowlarr_stats_thread
701+
if prowlarr_stats_thread and prowlarr_stats_thread.is_alive():
702+
logger.info("Waiting for Prowlarr stats refresher to stop...")
703+
prowlarr_stats_thread.join(timeout=5.0)
704+
if prowlarr_stats_thread.is_alive():
705+
logger.warning("Prowlarr stats refresher did not stop gracefully")
706+
else:
707+
logger.info("Prowlarr stats refresher stopped")
708+
696709
# Stop the Swaparr processing thread
697710
global swaparr_thread
698711
if swaparr_thread and swaparr_thread.is_alive():
@@ -776,6 +789,52 @@ def hourly_cap_scheduler_loop():
776789

777790
logger.info("Hourly API cap scheduler stopped")
778791

792+
def prowlarr_stats_loop():
793+
"""Background loop to refresh Prowlarr statistics cache every 5 minutes.
794+
Runs independently of the frontend and does nothing if Prowlarr is not configured or disabled.
795+
"""
796+
refresher_logger = get_logger("prowlarr")
797+
refresher_logger.info("Prowlarr stats refresher thread started")
798+
try:
799+
from src.primary.settings_manager import load_settings
800+
# Import inside loop target to avoid circular issues at module import time
801+
from src.primary.apps import prowlarr_routes as prow
802+
803+
refresh_interval_seconds = 300 # 5 minutes
804+
805+
# Do an immediate pass on start
806+
while not stop_event.is_set():
807+
try:
808+
settings = load_settings("prowlarr")
809+
api_url = (settings.get("api_url", "") or "").strip()
810+
api_key = (settings.get("api_key", "") or "").strip()
811+
enabled = settings.get("enabled", True)
812+
813+
if not api_url or not api_key or not enabled:
814+
# Not configured or disabled; sleep a bit and check again
815+
if stop_event.wait(60):
816+
break
817+
continue
818+
819+
# Trigger cache update (safe even if cache is warm)
820+
try:
821+
prow._update_stats_cache()
822+
except Exception as e:
823+
refresher_logger.error(f"Prowlarr stats refresh error: {e}", exc_info=True)
824+
825+
# Sleep until next refresh or until stop requested
826+
if stop_event.wait(refresh_interval_seconds):
827+
break
828+
829+
except Exception as loop_error:
830+
refresher_logger.error(f"Unexpected error in Prowlarr stats refresher: {loop_error}", exc_info=True)
831+
# Back off briefly to avoid tight error loops
832+
if stop_event.wait(60):
833+
break
834+
finally:
835+
refresher_logger.info("Prowlarr stats refresher thread stopped")
836+
837+
779838
def swaparr_app_loop():
780839
"""Dedicated Swaparr processing loop that follows same patterns as other apps"""
781840
swaparr_logger = get_logger("swaparr")
@@ -864,7 +923,7 @@ def swaparr_app_loop():
864923
# Log progress every 30 seconds (like other apps)
865924
if elapsed > 0 and elapsed % 30 == 0:
866925
swaparr_logger.debug(f"Still sleeping, {sleep_duration - elapsed} seconds remaining before next cycle...")
867-
926+
868927
except Exception as e:
869928
swaparr_logger.error(f"Unexpected error in Swaparr loop: {e}", exc_info=True)
870929
# Sleep briefly to avoid spinning in case of repeated errors
@@ -875,8 +934,6 @@ def swaparr_app_loop():
875934

876935
swaparr_logger.info("Swaparr thread stopped")
877936

878-
879-
880937
def start_hourly_cap_scheduler():
881938
"""Start the hourly API cap scheduler thread"""
882939
global hourly_cap_scheduler_thread
@@ -895,6 +952,20 @@ def start_hourly_cap_scheduler():
895952

896953
logger.info(f"Hourly API cap scheduler started. Thread is alive: {hourly_cap_scheduler_thread.is_alive()}")
897954

955+
def start_prowlarr_stats_thread():
956+
"""Start the Prowlarr statistics refresher thread (5-minute cadence)."""
957+
global prowlarr_stats_thread
958+
if prowlarr_stats_thread and prowlarr_stats_thread.is_alive():
959+
logger.info("Prowlarr stats refresher already running")
960+
return
961+
prowlarr_stats_thread = threading.Thread(
962+
target=prowlarr_stats_loop,
963+
name="ProwlarrStatsRefresher",
964+
daemon=True,
965+
)
966+
prowlarr_stats_thread.start()
967+
logger.info(f"Prowlarr stats refresher started. Thread is alive: {prowlarr_stats_thread.is_alive()}")
968+
898969
def start_swaparr_thread():
899970
"""Start the dedicated Swaparr processing thread"""
900971
global swaparr_thread
@@ -933,7 +1004,14 @@ def start_huntarr():
9331004
logger.info("Swaparr thread started successfully")
9341005
except Exception as e:
9351006
logger.error(f"Failed to start Swaparr thread: {e}")
936-
1007+
1008+
# Start the Prowlarr stats refresher
1009+
try:
1010+
start_prowlarr_stats_thread()
1011+
logger.info("Prowlarr stats refresher started successfully")
1012+
except Exception as e:
1013+
logger.error(f"Failed to start Prowlarr stats refresher: {e}")
1014+
9371015
# Start the scheduler engine
9381016
try:
9391017
start_scheduler()

0 commit comments

Comments
 (0)