diff --git a/apps/m3u/signals.py b/apps/m3u/signals.py index e5951ff2..467d0069 100644 --- a/apps/m3u/signals.py +++ b/apps/m3u/signals.py @@ -2,9 +2,12 @@ from django.db.models.signals import post_save, post_delete, pre_save from django.dispatch import receiver from .models import M3UAccount -from .tasks import refresh_single_m3u_account, refresh_m3u_groups +from .tasks import refresh_single_m3u_account, refresh_m3u_groups, delete_m3u_refresh_task_by_id from django_celery_beat.models import PeriodicTask, IntervalSchedule import json +import logging + +logger = logging.getLogger(__name__) @receiver(post_save, sender=M3UAccount) def refresh_account_on_save(sender, instance, created, **kwargs): @@ -65,9 +68,21 @@ def delete_refresh_task(sender, instance, **kwargs): """ Delete the associated Celery Beat periodic task when a Channel is deleted. """ - if instance.refresh_task: - instance.refresh_task.interval.delete() - instance.refresh_task.delete() + try: + # First try the foreign key relationship to find the task ID + task = None + if instance.refresh_task: + logger.info(f"Found task via foreign key: {instance.refresh_task.id} for M3UAccount {instance.id}") + task = instance.refresh_task + + # Use the helper function to delete the task + if task: + delete_m3u_refresh_task_by_id(instance.id) + else: + # Otherwise use the helper function + delete_m3u_refresh_task_by_id(instance.id) + except Exception as e: + logger.error(f"Error in delete_refresh_task signal handler: {str(e)}", exc_info=True) @receiver(pre_save, sender=M3UAccount) def update_status_on_active_change(sender, instance, **kwargs): diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index 8fb16ba5..7bc549e0 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -733,6 +733,60 @@ def refresh_m3u_groups(account_id, use_cache=False, full_refresh=False): return extinf_data, groups +def delete_m3u_refresh_task_by_id(account_id): + """ + Delete the periodic task associated with an M3U account ID. + Can be called directly or from the post_delete signal. + Returns True if a task was found and deleted, False otherwise. + """ + try: + task = None + task_name = f"m3u_account-refresh-{account_id}" + + # Look for task by name + try: + from django_celery_beat.models import PeriodicTask, IntervalSchedule + task = PeriodicTask.objects.get(name=task_name) + logger.info(f"Found task by name: {task.id} for M3UAccount {account_id}") + except PeriodicTask.DoesNotExist: + logger.warning(f"No PeriodicTask found with name {task_name}") + return False + + # Now delete the task and its interval + if task: + # Store interval info before deleting the task + interval_id = None + if hasattr(task, 'interval') and task.interval: + interval_id = task.interval.id + + # Count how many TOTAL tasks use this interval (including this one) + tasks_with_same_interval = PeriodicTask.objects.filter(interval_id=interval_id).count() + logger.info(f"Interval {interval_id} is used by {tasks_with_same_interval} tasks total") + + # Delete the task first + task_id = task.id + task.delete() + logger.info(f"Successfully deleted periodic task {task_id}") + + # Now check if we should delete the interval + # We only delete if it was the ONLY task using this interval + if interval_id and tasks_with_same_interval == 1: + try: + interval = IntervalSchedule.objects.get(id=interval_id) + logger.info(f"Deleting interval schedule {interval_id} (not shared with other tasks)") + interval.delete() + logger.info(f"Successfully deleted interval {interval_id}") + except IntervalSchedule.DoesNotExist: + logger.warning(f"Interval {interval_id} no longer exists") + elif interval_id: + logger.info(f"Not deleting interval {interval_id} as it's shared with {tasks_with_same_interval-1} other tasks") + + return True + return False + except Exception as e: + logger.error(f"Error deleting periodic task for M3UAccount {account_id}: {str(e)}", exc_info=True) + return False + @shared_task def refresh_single_m3u_account(account_id): """Splits M3U processing into chunks and dispatches them as parallel tasks.""" @@ -758,8 +812,17 @@ def refresh_single_m3u_account(account_id): filters = list(account.filters.all()) except M3UAccount.DoesNotExist: + # The M3U account doesn't exist, so delete the periodic task if it exists + logger.warning(f"M3U account with ID {account_id} not found, but task was triggered. Cleaning up orphaned task.") + + # Call the helper function to delete the task + if delete_m3u_refresh_task_by_id(account_id): + logger.info(f"Successfully cleaned up orphaned task for M3U account {account_id}") + else: + logger.info(f"No orphaned task found for M3U account {account_id}") + release_task_lock('refresh_single_m3u_account', account_id) - return f"M3UAccount with ID={account_id} not found or inactive." + return f"M3UAccount with ID={account_id} not found or inactive, task cleaned up" # Fetch M3U lines and handle potential issues extinf_data = []