Refactor GridScheduleGenerator for improved channel handling and entry writing
ci / build-backend (push) Successful in 1m32s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m48s
ci / sonar (push) Successful in 5m17s

Simplified the logic for handling disabled channels by introducing the SilentChannelAsync method, which manages future entry deletions more efficiently. Refactored the entry writing process into a separate WriteEntries method to enhance code clarity and maintainability. These changes streamline the scheduling process and improve overall functionality in the GridScheduleGenerator class.
This commit is contained in:
Leonid Pershin
2026-07-28 12:57:14 +03:00
parent 30a38d163c
commit f895e1ddbc
@@ -64,20 +64,8 @@ public sealed class GridScheduleGenerator(
if (channel is null)
return new GenerationReport(0, [], ChannelSkipped: true);
// Канал выключен или без сетки — вещать нечем. По таймеру это просто пропуск, но по кнопке
// «Применить» админ ждёт, что лента придёт в соответствие с настройками: раз эфира нет,
// не должно остаться и будущих записей. Иначе они висят вечно — пересобрать их нечем,
// а удалить шоу, которое в них стоит, невозможно.
if (!channel.IsEnabled || channel.TemplateId is null)
{
if (!rebuildFuture)
return new GenerationReport(0, [], ChannelSkipped: true);
await dbContext
.ScheduleEntries.Where(e => e.ChannelId == channelId && e.StartsAtUtc >= now)
.ExecuteDeleteAsync(cancellationToken);
return new GenerationReport(0, []);
}
return await SilentChannelAsync(channelId, now, rebuildFuture, cancellationToken);
await using var transaction = await dbContext.BeginTransactionAsync(cancellationToken);
await dbContext.AcquireChannelLockAsync(channelId, cancellationToken);
@@ -93,9 +81,7 @@ public sealed class GridScheduleGenerator(
await CleanupAsync(channelId, now, cancellationToken);
if (rebuildFuture)
await dbContext
.ScheduleEntries.Where(e => e.ChannelId == channelId && e.StartsAtUtc >= now)
.ExecuteDeleteAsync(cancellationToken);
await DropFutureAsync(channelId, now, cancellationToken);
// Точка продолжения — конец последней сохранённой записи. При пересборке будущее уже удалено,
// поэтому она укажет на границу неизменяемого прошлого.
@@ -139,37 +125,7 @@ public sealed class GridScheduleGenerator(
cancellationToken
);
var added = 0;
for (var index = 0; index < result.Items.Count; index++)
{
var item = result.Items[index];
var assetId = item.MediaAssetId;
if (
item.Kind == PlannedItemKind.Bumper
&& !bumperAssets.TryGetValue(index, out assetId)
)
continue; // Без ассета запись стала бы дырой в ленте.
dbContext.ScheduleEntries.Add(
ScheduleEntry.FromSlot(
channel.Id,
assetId,
ToEntryKind(item.Kind),
item.StartsAtUtc,
item.EndsAtUtc,
new ScheduleEntryOrigin(
item.ShowId,
item.UnitIndex,
item.SlotId,
item.Trace is null
? null
: JsonSerializer.Serialize(item.Trace, TraceJsonOptions),
item.CollectionId
)
)
);
added++;
}
var added = WriteEntries(channel.Id, result.Items, bumperAssets);
await SaveCursorsAsync(result.Cursors, cancellationToken);
@@ -231,6 +187,82 @@ public sealed class GridScheduleGenerator(
/// Чистит прошлое сверх окна хранения. Окно должно покрывать самое долгое остывание среди правил —
/// история показов берётся из самой ленты, отдельного журнала нет.
/// </summary>
/// <summary>
/// Канал выключен или без сетки — вещать нечем. По таймеру это просто пропуск, но по кнопке
/// «Применить» админ ждёт, что лента придёт в соответствие с настройками: раз эфира нет,
/// не должно остаться и будущих записей. Иначе они висят вечно — пересобрать их нечем,
/// а удалить шоу, которое в них стоит, невозможно.
/// </summary>
private async Task<GenerationReport> SilentChannelAsync(
Guid channelId,
DateTimeOffset now,
bool rebuildFuture,
CancellationToken cancellationToken
)
{
if (!rebuildFuture)
return new GenerationReport(0, [], ChannelSkipped: true);
await DropFutureAsync(channelId, now, cancellationToken);
return new GenerationReport(0, []);
}
/// <summary>Снимает будущий хвост ленты. Прошлое и идущую запись не трогает никогда.</summary>
private Task DropFutureAsync(
Guid channelId,
DateTimeOffset now,
CancellationToken cancellationToken
) =>
dbContext
.ScheduleEntries.Where(e => e.ChannelId == channelId && e.StartsAtUtc >= now)
.ExecuteDeleteAsync(cancellationToken);
/// <summary>
/// Переносит собранную ленту в записи расписания. Заставка без отрендеренного ассета
/// пропускается: запись без файла стала бы дырой в эфире.
/// </summary>
private int WriteEntries(
Guid channelId,
IReadOnlyList<PlannedItem> items,
IReadOnlyDictionary<int, Guid> bumperAssets
)
{
var added = 0;
for (var index = 0; index < items.Count; index++)
{
var item = items[index];
var assetId = item.MediaAssetId;
if (
item.Kind == PlannedItemKind.Bumper
&& !bumperAssets.TryGetValue(index, out assetId)
)
continue;
dbContext.ScheduleEntries.Add(
ScheduleEntry.FromSlot(
channelId,
assetId,
ToEntryKind(item.Kind),
item.StartsAtUtc,
item.EndsAtUtc,
new ScheduleEntryOrigin(
item.ShowId,
item.UnitIndex,
item.SlotId,
item.Trace is null
? null
: JsonSerializer.Serialize(item.Trace, TraceJsonOptions),
item.CollectionId
)
)
);
added++;
}
return added;
}
private Task CleanupAsync(
Guid channelId,
DateTimeOffset now,