From e185fbcda6ec0815092e0fd22efd79e62a6abf32 Mon Sep 17 00:00:00 2001 From: dekzter Date: Sun, 6 Apr 2025 13:11:28 -0400 Subject: [PATCH] process groups to allow filtering before any stream ingestion, do this in background for better experience --- apps/m3u/signals.py | 10 +++------- apps/m3u/tasks.py | 15 +++++++++++++-- frontend/src/WebSocket.jsx | 11 +++++++++++ frontend/src/components/forms/M3U.jsx | 8 +++++++- frontend/src/components/tables/ChannelsTable.jsx | 16 +++++++++++++++- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/apps/m3u/signals.py b/apps/m3u/signals.py index 22e723e0..6e46a0ff 100644 --- a/apps/m3u/signals.py +++ b/apps/m3u/signals.py @@ -2,9 +2,9 @@ from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from .models import M3UAccount -from .tasks import refresh_m3u_groups +from .tasks import refresh_single_m3u_account, refresh_m3u_groups from django_celery_beat.models import PeriodicTask, IntervalSchedule -import json, gc +import json @receiver(post_save, sender=M3UAccount) def refresh_account_on_save(sender, instance, created, **kwargs): @@ -14,11 +14,7 @@ def refresh_account_on_save(sender, instance, created, **kwargs): if it is active or newly created. """ if created: - extinf_data, groups = refresh_m3u_groups(instance.id) - - # Aggresive GC since we pulled in the whole file - del extinf_data, groups - gc.collect() + refresh_m3u_groups.delay(instance.id) @receiver(post_save, sender=M3UAccount) def create_or_update_refresh_task(sender, instance, **kwargs): diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index 59cf34b2..460bb181 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -273,7 +273,8 @@ def cleanup_streams(account_id): logger.info(f"Cleanup complete") -def refresh_m3u_groups(account_id, use_cache=False): +@shared_task +def refresh_m3u_groups(account_id, use_cache=False, full_refresh=False): if not acquire_task_lock('refresh_m3u_account_groups', account_id): return f"Task already running for account_id={account_id}.", None @@ -311,6 +312,16 @@ def refresh_m3u_groups(account_id, use_cache=False): release_task_lock('refresh_m3u_account_groups', account_id) + if not full_refresh: + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + 'updates', + { + 'type': 'update', + "data": {"success": True, "type": "m3u_group_refresh", "account": account_id} + } + ) + return extinf_data, groups @shared_task @@ -346,7 +357,7 @@ def refresh_single_m3u_account(account_id): if not extinf_data: try: - extinf_data, groups = refresh_m3u_groups(account_id) + extinf_data, groups = refresh_m3u_groups(account_id, full_refresh=True) if not extinf_data or not groups: release_task_lock('refresh_single_m3u_account', account_id) return "Failed to update m3u account, task may already be running" diff --git a/frontend/src/WebSocket.jsx b/frontend/src/WebSocket.jsx index 616236cc..4fa08216 100644 --- a/frontend/src/WebSocket.jsx +++ b/frontend/src/WebSocket.jsx @@ -57,6 +57,17 @@ export const WebsocketProvider = ({ children }) => { socket.onmessage = async (event) => { event = JSON.parse(event.data); switch (event.data.type) { + case 'm3u_group_refresh': + fetchChannelGroups(); + fetchPlaylists(); + + notifications.show({ + title: 'Group processing finished!', + message: 'Refresh M3U or filter out groups to pull in streams.', + color: 'green.5', + }); + break; + case 'm3u_refresh': if (event.data.success) { fetchStreams(); diff --git a/frontend/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx index f8c70698..f0feb28a 100644 --- a/frontend/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -23,6 +23,7 @@ import { import M3UGroupFilter from './M3UGroupFilter'; import useChannelsStore from '../../store/channels'; import usePlaylistsStore from '../../store/playlists'; +import { notifications } from '@mantine/notifications'; const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { const theme = useMantineTheme(); @@ -71,10 +72,15 @@ const M3U = ({ playlist = null, isOpen, onClose, playlistCreated = false }) => { uploaded_file: file, }); - await fetchChannelGroups(); + notifications.show({ + title: 'Fetching M3U Groups', + message: 'Filter out groups or refresh M3U once complete.', + // color: 'green.5', + }); // Don't prompt for group filters, but keeping this here // in case we want to revive it + newPlaylist = null; } resetForm(); diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index c69cc0a8..599e7c4f 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -26,6 +26,7 @@ import { ScanEye, EllipsisVertical, CircleEllipsis, + CopyMinus, } from 'lucide-react'; import ghostImage from '../../images/ghost.svg'; import { @@ -540,6 +541,9 @@ const ChannelsTable = ({}) => { }; const deleteChannel = async (id) => { + if (channelsPageSelection.length > 0) { + return deleteChannels(); + } await API.deleteChannel(id); }; @@ -858,8 +862,18 @@ const ChannelsTable = ({}) => { variant="transparent" color={theme.tailwind.red[6]} onClick={() => deleteChannel(row.original.id)} + disabled={ + channelsPageSelection.length > 0 && + !channelsPageSelection + .map((row) => row.id) + .includes(row.original.id) + } > - + {channelsPageSelection.length === 0 ? ( + + ) : ( + + )}