diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index 97a12808..02128ac0 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -868,42 +868,38 @@ def sync_auto_channels(account_id): logger.info(f"Processing auto sync for group: {channel_group.name} (start: {start_number})") - # Get all streams in this group for this M3U account - current_streams = Stream.objects.filter( - m3u_account=account, - channel_group=channel_group - ) - - # Get existing auto-created channels in this group that were created by this M3U account + # Step 1: Delete all existing auto-created channels for this account in this group existing_auto_channels = Channel.objects.filter( channel_group=channel_group, auto_created=True, auto_created_by=account - ).distinct() + ) - # Create a mapping of stream hashes to existing channels - existing_channel_streams = {} - for channel in existing_auto_channels: - for stream in channel.streams.filter(m3u_account=account): - existing_channel_streams[stream.stream_hash] = channel + deleted_count = existing_auto_channels.count() + if deleted_count > 0: + logger.debug(f"Deleting {deleted_count} existing auto-created channels in group {channel_group.name}") + existing_auto_channels.delete() + channels_deleted += deleted_count - # Track which channels should exist (based on current streams) - channels_to_keep = set() + # Step 2: Get all current streams in this group for this M3U account + current_streams = Stream.objects.filter( + m3u_account=account, + channel_group=channel_group + ).order_by('name') # Sort by name for consistent ordering + + if not current_streams.exists(): + logger.debug(f"No streams found in group {channel_group.name}, skipping channel creation") + continue + + # Step 3: Create new channels for all streams current_channel_number = start_number - # Create channels for streams that don't have them - for stream in current_streams.order_by('name'): - if stream.stream_hash in existing_channel_streams: - # Channel already exists for this stream - channels_to_keep.add(existing_channel_streams[stream.stream_hash].id) - continue - - # Find next available channel number - while Channel.objects.filter(channel_number=current_channel_number).exists(): - current_channel_number += 0.1 - - # Create new channel + for stream in current_streams: try: + # Find next available channel number + while Channel.objects.filter(channel_number=current_channel_number).exists(): + current_channel_number += 0.1 + # Parse custom properties for additional info stream_custom_props = json.loads(stream.custom_properties) if stream.custom_properties else {} @@ -946,7 +942,6 @@ def sync_auto_channels(account_id): channel.logo = logo channel.save(update_fields=['logo']) - channels_to_keep.add(channel.id) channels_created += 1 current_channel_number += 1.0 @@ -956,19 +951,6 @@ def sync_auto_channels(account_id): logger.error(f"Error creating auto channel for stream {stream.name}: {str(e)}") continue - # Delete channels that no longer have corresponding streams - # Only delete auto-created channels that were created by this M3U account - channels_to_delete = existing_auto_channels.exclude(id__in=channels_to_keep) - - for channel in channels_to_delete: - # Double-check that this is an auto-created channel by this account - if channel.auto_created and channel.auto_created_by_id == account.id: - logger.debug(f"Deleting auto-created channel: {channel.channel_number} - {channel.name}") - channel.delete() - channels_deleted += 1 - else: - logger.warning(f"Skipping deletion of non-auto-created channel: {channel.channel_number} - {channel.name}") - logger.info(f"Auto channel sync complete for account {account.name}: {channels_created} created, {channels_deleted} deleted") return f"Auto sync: {channels_created} channels created, {channels_deleted} deleted" @@ -1230,6 +1212,16 @@ def refresh_single_m3u_account(account_id): # Now run cleanup streams_deleted = cleanup_streams(account_id, refresh_start_timestamp) + # Run auto channel sync after successful refresh + auto_sync_message = "" + try: + sync_result = sync_auto_channels(account_id) + logger.info(f"Auto channel sync result for account {account_id}: {sync_result}") + if sync_result and "created" in sync_result: + auto_sync_message = f" {sync_result}." + except Exception as e: + logger.error(f"Error running auto channel sync for account {account_id}: {str(e)}") + # Calculate elapsed time elapsed_time = time.time() - start_time @@ -1238,7 +1230,7 @@ def refresh_single_m3u_account(account_id): account.last_message = ( f"Processing completed in {elapsed_time:.1f} seconds. " f"Streams: {streams_created} created, {streams_updated} updated, {streams_deleted} removed. " - f"Total processed: {streams_processed}." + f"Total processed: {streams_processed}.{auto_sync_message}" ) account.updated_at = timezone.now() account.save(update_fields=['status', 'last_message', 'updated_at']) @@ -1258,12 +1250,6 @@ def refresh_single_m3u_account(account_id): message=account.last_message ) - # Run auto channel sync after successful refresh - try: - sync_result = sync_auto_channels(account_id) - logger.info(f"Auto channel sync result for account {account_id}: {sync_result}") - except Exception as e: - logger.error(f"Error running auto channel sync for account {account_id}: {str(e)}") except Exception as e: logger.error(f"Error processing M3U for account {account_id}: {str(e)}") account.status = M3UAccount.Status.ERROR diff --git a/frontend/src/components/forms/M3UGroupFilter.jsx b/frontend/src/components/forms/M3UGroupFilter.jsx index 0213eeee..e0a204ab 100644 --- a/frontend/src/components/forms/M3UGroupFilter.jsx +++ b/frontend/src/components/forms/M3UGroupFilter.jsx @@ -24,10 +24,12 @@ import { NumberInput, Divider, Alert, + Box, } from '@mantine/core'; import { Info } from 'lucide-react'; import useChannelsStore from '../../store/channels'; import { CircleCheck, CircleX } from 'lucide-react'; +import { notifications } from '@mantine/notifications'; const M3UGroupFilter = ({ playlist = null, isOpen, onClose }) => { const channelGroups = useChannelsStore((s) => s.channelGroups); @@ -80,11 +82,27 @@ const M3UGroupFilter = ({ playlist = null, isOpen, onClose }) => { const submit = async () => { setIsLoading(true); try { - // Update group settings via new API endpoint + // Update group settings via API endpoint await API.updateM3UGroupSettings(playlist.id, groupStates); - // Refresh the playlist - API.refreshPlaylist(playlist.id); + // Show notification about the refresh process + notifications.show({ + title: 'Group Settings Updated', + message: 'Settings saved. Starting M3U refresh to apply changes...', + color: 'green', + autoClose: 3000, + }); + + // Refresh the playlist - this will handle channel sync automatically at the end + await API.refreshPlaylist(playlist.id); + + notifications.show({ + title: 'M3U Refresh Started', + message: 'The M3U account is being refreshed. Channel sync will occur automatically after parsing completes.', + color: 'blue', + autoClose: 5000, + }); + onClose(); } catch (error) { console.error('Error updating group settings:', error); @@ -124,7 +142,8 @@ const M3UGroupFilter = ({ playlist = null, isOpen, onClose }) => { opened={isOpen} onClose={onClose} title="M3U Group Filter & Auto Channel Sync" - size={1200} + size={1000} + styles={{ content: { '--mantine-color-body': '#27272A' } }} > @@ -141,78 +160,88 @@ const M3UGroupFilter = ({ playlist = null, isOpen, onClose }) => { value={groupFilter} onChange={(event) => setGroupFilter(event.currentTarget.value)} style={{ flex: 1 }} + size="xs" /> - - - - {groupStates - .filter((group) => - group.name.toLowerCase().includes(groupFilter.toLowerCase()) - ) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((group) => ( - - {/* Group Enable/Disable Button */} - + + + {groupStates + .filter((group) => + group.name.toLowerCase().includes(groupFilter.toLowerCase()) + ) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((group) => ( + + {/* Group Enable/Disable Button */} + - {/* Auto Sync Checkbox */} - toggleAutoSync(group.channel_group)} - size="sm" - /> + {/* Auto Sync Controls */} + + toggleAutoSync(group.channel_group)} + size="xs" + /> - {/* Channel Start Number Input */} - updateChannelStart(group.channel_group, value)} - disabled={!group.enabled || !group.auto_channel_sync} - min={1} - step={1} - size="sm" - style={{ width: '120px' }} - precision={1} - /> - - ))} - + {group.auto_channel_sync && group.enabled && ( + updateChannelStart(group.channel_group, value)} + min={1} + step={1} + size="xs" + precision={1} + /> + )} + + + ))} + + -