mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-22 18:28:00 +00:00
Bypass redis for management commands.
This commit is contained in:
parent
071efaf017
commit
3c3961bb3a
5 changed files with 124 additions and 10 deletions
|
|
@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
|
|||
from core.models import StreamProfile
|
||||
from django.conf import settings
|
||||
from core.models import StreamProfile, CoreSettings
|
||||
from core.utils import redis_client
|
||||
from core.utils import redis_client, execute_redis_command
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
|
@ -15,6 +15,17 @@ logger = logging.getLogger(__name__)
|
|||
# If you have an M3UAccount model in apps.m3u, you can still import it:
|
||||
from apps.m3u.models import M3UAccount
|
||||
|
||||
# Add fallback functions if Redis isn't available
|
||||
def get_total_viewers(channel_id):
|
||||
"""Get viewer count from Redis or return 0 if Redis isn't available"""
|
||||
if redis_client is None:
|
||||
return 0
|
||||
|
||||
try:
|
||||
return int(redis_client.get(f"channel:{channel_id}:viewers") or 0)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
class ChannelGroup(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
|
||||
|
|
|
|||
34
core/command_utils.py
Normal file
34
core/command_utils.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
def is_management_command(excluded_commands=None):
|
||||
"""
|
||||
Detect if we're running a Django management command like migrate, collectstatic, etc.
|
||||
|
||||
Args:
|
||||
excluded_commands: List of commands that should still use Redis (e.g. runserver)
|
||||
|
||||
Returns:
|
||||
bool: True if we're running a management command
|
||||
"""
|
||||
# First check if we're in build mode
|
||||
if os.environ.get("DISPATCHARR_BUILD") == "1":
|
||||
return True
|
||||
|
||||
if excluded_commands is None:
|
||||
excluded_commands = ['runserver', 'runworker', 'daphne']
|
||||
|
||||
# Check if we're running via manage.py
|
||||
if not ('manage.py' in sys.argv[0]):
|
||||
return False
|
||||
|
||||
# Check if we have a command argument
|
||||
if len(sys.argv) > 1:
|
||||
command = sys.argv[1]
|
||||
# Return False if command is in excluded list - these commands DO need Redis
|
||||
if command in excluded_commands:
|
||||
return False
|
||||
# Otherwise it's a command that should work without Redis
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
@ -10,6 +10,23 @@ from redis.exceptions import ConnectionError, TimeoutError
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DummyPubSub:
|
||||
"""Dummy PubSub implementation when Redis isn't available"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def subscribe(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def psubscribe(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_message(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
class RedisPubSubManager:
|
||||
"""
|
||||
A robust Redis PubSub manager that handles disconnections and reconnections.
|
||||
|
|
@ -23,9 +40,7 @@ class RedisPubSubManager:
|
|||
redis_client: An existing Redis client to use
|
||||
auto_reconnect: Whether to automatically reconnect on failure
|
||||
"""
|
||||
from .utils import get_redis_client
|
||||
|
||||
self.redis_client = redis_client or get_redis_client()
|
||||
self.redis_client = redis_client
|
||||
self.pubsub = None
|
||||
self.subscriptions = set()
|
||||
self.pattern_subscriptions = set()
|
||||
|
|
@ -34,6 +49,7 @@ class RedisPubSubManager:
|
|||
self.lock = threading.RLock()
|
||||
self.message_handlers = {} # Map of channels to handler functions
|
||||
self.message_thread = None
|
||||
self.is_dummy = redis_client is None
|
||||
|
||||
def subscribe(self, channel, handler=None):
|
||||
"""
|
||||
|
|
@ -43,6 +59,9 @@ class RedisPubSubManager:
|
|||
channel: The channel to subscribe to
|
||||
handler: Optional function to call when messages are received
|
||||
"""
|
||||
if self.is_dummy:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
self.subscriptions.add(channel)
|
||||
if handler:
|
||||
|
|
@ -60,6 +79,9 @@ class RedisPubSubManager:
|
|||
pattern: The pattern to subscribe to
|
||||
handler: Optional function to call when messages are received
|
||||
"""
|
||||
if self.is_dummy:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
self.pattern_subscriptions.add(pattern)
|
||||
if handler:
|
||||
|
|
@ -80,6 +102,9 @@ class RedisPubSubManager:
|
|||
Returns:
|
||||
Number of clients that received the message
|
||||
"""
|
||||
if self.is_dummy:
|
||||
return 0
|
||||
|
||||
try:
|
||||
if not isinstance(message, str):
|
||||
message = json.dumps(message)
|
||||
|
|
@ -92,6 +117,10 @@ class RedisPubSubManager:
|
|||
"""
|
||||
Start listening for messages in a background thread.
|
||||
"""
|
||||
if self.is_dummy:
|
||||
logger.debug("Running with dummy Redis client - not starting listener")
|
||||
return
|
||||
|
||||
if not self.message_thread:
|
||||
self._connect()
|
||||
self.message_thread = threading.Thread(
|
||||
|
|
@ -106,6 +135,9 @@ class RedisPubSubManager:
|
|||
"""
|
||||
Stop listening and clean up resources.
|
||||
"""
|
||||
if self.is_dummy:
|
||||
return
|
||||
|
||||
self.running = False
|
||||
if self.pubsub:
|
||||
try:
|
||||
|
|
@ -118,6 +150,10 @@ class RedisPubSubManager:
|
|||
"""
|
||||
Establish a new PubSub connection and subscribe to all channels.
|
||||
"""
|
||||
if self.is_dummy:
|
||||
self.pubsub = DummyPubSub()
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
# Close any existing connection
|
||||
if self.pubsub:
|
||||
|
|
@ -144,6 +180,9 @@ class RedisPubSubManager:
|
|||
"""
|
||||
Background thread that listens for messages and handles reconnections.
|
||||
"""
|
||||
if self.is_dummy:
|
||||
return
|
||||
|
||||
consecutive_errors = 0
|
||||
|
||||
while self.running:
|
||||
|
|
@ -218,6 +257,11 @@ def get_pubsub_manager(redis_client=None):
|
|||
|
||||
if pubsub_manager is None:
|
||||
pubsub_manager = RedisPubSubManager(redis_client)
|
||||
pubsub_manager.start_listening()
|
||||
# Only start if redis_client is not None
|
||||
if redis_client is not None:
|
||||
try:
|
||||
pubsub_manager.start_listening()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start PubSub listener: {e}")
|
||||
|
||||
return pubsub_manager
|
||||
|
|
|
|||
|
|
@ -8,8 +8,16 @@ from redis.exceptions import ConnectionError, TimeoutError
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Import the command detector
|
||||
from .command_utils import is_management_command
|
||||
|
||||
def get_redis_client(max_retries=5, retry_interval=1):
|
||||
"""Get Redis client with connection validation and retry logic"""
|
||||
# Skip Redis connection for management commands like collectstatic
|
||||
if is_management_command():
|
||||
logger.info("Running as management command - skipping Redis initialization")
|
||||
return None
|
||||
|
||||
retry_count = 0
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
|
|
@ -59,6 +67,11 @@ def get_redis_client(max_retries=5, retry_interval=1):
|
|||
|
||||
def get_redis_pubsub_client(max_retries=5, retry_interval=1):
|
||||
"""Get Redis client optimized for PubSub operations"""
|
||||
# Skip Redis connection for management commands like collectstatic
|
||||
if is_management_command():
|
||||
logger.info("Running as management command - skipping Redis PubSub initialization")
|
||||
return None
|
||||
|
||||
retry_count = 0
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
|
|
@ -133,9 +146,20 @@ def execute_redis_command(redis_client, command_func, default_return=None):
|
|||
return default_return
|
||||
|
||||
# Initialize the global clients with retry logic
|
||||
redis_client = get_redis_client()
|
||||
redis_pubsub_client = get_redis_pubsub_client()
|
||||
# Skip Redis initialization if running as a management command
|
||||
if is_management_command():
|
||||
redis_client = None
|
||||
redis_pubsub_client = None
|
||||
logger.info("Running as management command - Redis clients set to None")
|
||||
else:
|
||||
redis_client = get_redis_client()
|
||||
redis_pubsub_client = get_redis_pubsub_client()
|
||||
|
||||
# Import and initialize the PubSub manager
|
||||
from .redis_pubsub import get_pubsub_manager
|
||||
pubsub_manager = get_pubsub_manager(redis_client)
|
||||
# Skip if running as management command or if Redis client is None
|
||||
if not is_management_command() and redis_client is not None:
|
||||
from .redis_pubsub import get_pubsub_manager
|
||||
pubsub_manager = get_pubsub_manager(redis_client)
|
||||
else:
|
||||
logger.info("PubSub manager not initialized (running as management command or Redis not available)")
|
||||
pubsub_manager = None
|
||||
|
|
@ -3,7 +3,8 @@ FROM python:3.13-slim AS builder
|
|||
ENV PATH="/dispatcharrpy/bin:$PATH" \
|
||||
VIRTUAL_ENV=/dispatcharrpy \
|
||||
DJANGO_SETTINGS_MODULE=dispatcharr.settings \
|
||||
PYTHONUNBUFFERED=1
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DISPATCHARR_BUILD=1
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue