Delete all auto-synced channels and create new ones.

This commit is contained in:
SergeantPanda 2025-07-13 18:02:26 -05:00
parent b91a2286e4
commit 12c086dcd1
2 changed files with 126 additions and 111 deletions

View file

@ -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

View file

@ -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' } }}
>
<LoadingOverlay visible={isLoading} overlayBlur={2} />
<Stack>
@ -141,78 +160,88 @@ const M3UGroupFilter = ({ playlist = null, isOpen, onClose }) => {
value={groupFilter}
onChange={(event) => setGroupFilter(event.currentTarget.value)}
style={{ flex: 1 }}
size="xs"
/>
<Button variant="default" size="sm" onClick={selectAll}>
<Button variant="default" size="xs" onClick={selectAll}>
Select Visible
</Button>
<Button variant="default" size="sm" onClick={deselectAll}>
<Button variant="default" size="xs" onClick={deselectAll}>
Deselect Visible
</Button>
</Flex>
<Divider label="Groups & Auto Sync Settings" labelPosition="center" />
<Stack spacing="xs" style={{ maxHeight: '60vh', overflowY: 'auto' }}>
{groupStates
.filter((group) =>
group.name.toLowerCase().includes(groupFilter.toLowerCase())
)
.sort((a, b) => a.name.localeCompare(b.name))
.map((group) => (
<Group key={group.channel_group} spacing="md" style={{
padding: '12px',
border: '1px solid #e0e0e0',
borderRadius: '8px',
backgroundColor: group.enabled ? '#f8f9fa' : '#f5f5f5'
}}>
{/* Group Enable/Disable Button */}
<Button
color={group.enabled ? 'green' : 'gray'}
variant="filled"
onClick={() => toggleGroupEnabled(group.channel_group)}
radius="md"
size="sm"
leftSection={
group.enabled ? (
<CircleCheck size={16} />
) : (
<CircleX size={16} />
)
}
style={{ minWidth: '140px' }}
>
<Text size="sm" truncate style={{ maxWidth: '120px' }}>
{group.name}
</Text>
</Button>
<Box style={{ maxHeight: '50vh', overflowY: 'auto' }}>
<SimpleGrid
cols={{ base: 1, sm: 2, md: 3 }}
spacing="xs"
verticalSpacing="xs"
>
{groupStates
.filter((group) =>
group.name.toLowerCase().includes(groupFilter.toLowerCase())
)
.sort((a, b) => a.name.localeCompare(b.name))
.map((group) => (
<Group key={group.channel_group} spacing="xs" style={{
padding: '8px',
border: '1px solid #444',
borderRadius: '8px',
backgroundColor: group.enabled ? '#2A2A2E' : '#1E1E22',
flexDirection: 'column',
alignItems: 'stretch'
}}>
{/* Group Enable/Disable Button */}
<Button
color={group.enabled ? 'green' : 'gray'}
variant="filled"
onClick={() => toggleGroupEnabled(group.channel_group)}
radius="md"
size="xs"
leftSection={
group.enabled ? (
<CircleCheck size={14} />
) : (
<CircleX size={14} />
)
}
fullWidth
>
<Text size="xs" truncate>
{group.name}
</Text>
</Button>
{/* Auto Sync Checkbox */}
<Checkbox
label="Auto Channel Sync"
checked={group.auto_channel_sync && group.enabled}
disabled={!group.enabled}
onChange={() => toggleAutoSync(group.channel_group)}
size="sm"
/>
{/* Auto Sync Controls */}
<Stack spacing={4}>
<Checkbox
label="Auto Channel Sync"
checked={group.auto_channel_sync && group.enabled}
disabled={!group.enabled}
onChange={() => toggleAutoSync(group.channel_group)}
size="xs"
/>
{/* Channel Start Number Input */}
<NumberInput
label="Start Channel #"
value={group.auto_sync_channel_start}
onChange={(value) => updateChannelStart(group.channel_group, value)}
disabled={!group.enabled || !group.auto_channel_sync}
min={1}
step={1}
size="sm"
style={{ width: '120px' }}
precision={1}
/>
</Group>
))}
</Stack>
{group.auto_channel_sync && group.enabled && (
<NumberInput
label="Start Channel #"
value={group.auto_sync_channel_start}
onChange={(value) => updateChannelStart(group.channel_group, value)}
min={1}
step={1}
size="xs"
precision={1}
/>
)}
</Stack>
</Group>
))}
</SimpleGrid>
</Box>
<Flex mih={50} gap="xs" justify="flex-end" align="flex-end">
<Button variant="default" onClick={onClose}>
<Button variant="default" onClick={onClose} size="xs">
Cancel
</Button>
<Button