diff --git a/apps/m3u/migrations/0008_m3uaccount_stale_stream_days.py b/apps/m3u/migrations/0008_m3uaccount_stale_stream_days.py new file mode 100644 index 00000000..69a1397d --- /dev/null +++ b/apps/m3u/migrations/0008_m3uaccount_stale_stream_days.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.6 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('m3u', '0007_remove_m3uaccount_uploaded_file_m3uaccount_file_path'), + ] + + operations = [ + migrations.AddField( + model_name='m3uaccount', + name='stale_stream_days', + field=models.PositiveIntegerField(default=7, help_text='Number of days after which a stream will be removed if not seen in the M3U source.'), + ), + ] diff --git a/apps/m3u/models.py b/apps/m3u/models.py index 25a332c6..06c206f6 100644 --- a/apps/m3u/models.py +++ b/apps/m3u/models.py @@ -74,6 +74,10 @@ class M3UAccount(models.Model): refresh_task = models.ForeignKey( PeriodicTask, on_delete=models.SET_NULL, null=True, blank=True ) + stale_stream_days = models.PositiveIntegerField( + default=7, + help_text="Number of days after which a stream will be removed if not seen in the M3U source." + ) def __str__(self): return self.name diff --git a/apps/m3u/serializers.py b/apps/m3u/serializers.py index d79b0117..43015713 100644 --- a/apps/m3u/serializers.py +++ b/apps/m3u/serializers.py @@ -65,7 +65,7 @@ class M3UAccountSerializer(serializers.ModelSerializer): model = M3UAccount fields = [ 'id', 'name', 'server_url', 'file_path', 'server_group', - 'max_streams', 'is_active', 'created_at', 'updated_at', 'filters', 'user_agent', 'profiles', 'locked', + 'max_streams', 'is_active', 'stale_stream_days', 'created_at', 'updated_at', 'filters', 'user_agent', 'profiles', 'locked', 'channel_groups', 'refresh_interval' ] diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index c6393123..20ed1acb 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -312,18 +312,31 @@ def cleanup_streams(account_id): m3u_account__enabled=True, ).values_list('id', flat=True) logger.info(f"Found {len(existing_groups)} active groups") - streams = Stream.objects.filter(m3u_account=account) + # Calculate cutoff date for stale streams + stale_cutoff = timezone.now() - timezone.timedelta(days=account.stale_stream_days) + logger.info(f"Removing streams not seen since {stale_cutoff}") + + # Delete streams that are not in active groups streams_to_delete = Stream.objects.filter( m3u_account=account ).exclude( - channel_group__in=existing_groups # Exclude products having any of the excluded tags + channel_group__in=existing_groups ) - # Delete the filtered products - streams_to_delete.delete() + # Also delete streams that haven't been seen for longer than stale_stream_days + stale_streams = Stream.objects.filter( + m3u_account=account, + last_seen__lt=stale_cutoff + ) - logger.info(f"Cleanup complete") + deleted_count = streams_to_delete.count() + stale_count = stale_streams.count() + + streams_to_delete.delete() + stale_streams.delete() + + logger.info(f"Cleanup complete: {deleted_count} streams removed due to group filter, {stale_count} removed as stale") @shared_task def refresh_m3u_groups(account_id, use_cache=False, full_refresh=False): diff --git a/frontend/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx index bd9cf3c8..8fee0d9b 100644 --- a/frontend/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -52,6 +52,7 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { is_active: true, max_streams: 0, refresh_interval: 24, + stale_stream_days: 7, }, validate: { @@ -65,10 +66,11 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { if (playlist) { form.setValues({ name: playlist.name, - server_url: playlist.server_url, + server_url: playlist.server_url || '', max_streams: playlist.max_streams, user_agent: playlist.user_agent ? `${playlist.user_agent}` : '0', is_active: playlist.is_active, + stale_stream_days: playlist.stale_stream_days || 7, refresh_interval: playlist.refresh_interval, }); } else { @@ -202,6 +204,14 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { key={form.key('refresh_interval')} /> + +