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')}
/>
+
+