Refactor GenerateGridCommandHandler and GridPlanner for improved slot management and readability
Updated the GenerateGridCommandHandler to streamline slot removal during grid generation by introducing a new RemoveAllSlots method. Enhanced the GridPlanner class by refactoring the FillDay method to utilize a SlotTarget struct for better clarity and organization. These changes improve code maintainability and readability while ensuring efficient slot handling across grid generation processes.
This commit is contained in:
+18
-6
@@ -48,12 +48,7 @@ public sealed class GenerateGridCommandHandler(
|
|||||||
if (template is null)
|
if (template is null)
|
||||||
return Result.Failure<GenerateGridResultDto>(ChannelErrors.TemplateNotFound);
|
return Result.Failure<GenerateGridResultDto>(ChannelErrors.TemplateNotFound);
|
||||||
|
|
||||||
var removed = 0;
|
var removed = command.Mode == GridGenerationMode.Rebuild ? RemoveAllSlots(template) : 0;
|
||||||
if (command.Mode == GridGenerationMode.Rebuild)
|
|
||||||
foreach (var layer in template.Layers)
|
|
||||||
foreach (var slot in layer.Slots.ToList())
|
|
||||||
if (layer.RemoveSlot(slot.Id))
|
|
||||||
removed++;
|
|
||||||
|
|
||||||
var created = 0;
|
var created = 0;
|
||||||
foreach (var slot in plan.Slots)
|
foreach (var slot in plan.Slots)
|
||||||
@@ -74,6 +69,23 @@ public sealed class GenerateGridCommandHandler(
|
|||||||
return Result.Success(new GenerateGridResultDto(created, removed));
|
return Result.Success(new GenerateGridResultDto(created, removed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Снимает все слоты шаблона и возвращает их число — режим «собрать с нуля».</summary>
|
||||||
|
private static int RemoveAllSlots(ScheduleTemplate template)
|
||||||
|
{
|
||||||
|
var removed = 0;
|
||||||
|
|
||||||
|
foreach (var layer in template.Layers)
|
||||||
|
{
|
||||||
|
// Идентификаторы снимаем заранее: удаление правит ту самую коллекцию, по которой идём.
|
||||||
|
var slotIds = layer.Slots.Select(s => s.Id).ToList();
|
||||||
|
foreach (var slotId in slotIds)
|
||||||
|
layer.RemoveSlot(slotId);
|
||||||
|
removed += slotIds.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
private static GridLayer EnsureLayer(ScheduleTemplate template, GridPlanLayer kind)
|
private static GridLayer EnsureLayer(ScheduleTemplate template, GridPlanLayer kind)
|
||||||
{
|
{
|
||||||
var (name, priority) =
|
var (name, priority) =
|
||||||
|
|||||||
@@ -110,15 +110,21 @@ public sealed class GridPlanner(
|
|||||||
private static List<PlannedSlot> BuildWeek(PlanRun run)
|
private static List<PlannedSlot> BuildWeek(PlanRun run)
|
||||||
{
|
{
|
||||||
var wholeDay = new List<(int From, int To)> { (0, GridCoverage.MinutesInDay) };
|
var wholeDay = new List<(int From, int To)> { (0, GridCoverage.MinutesInDay) };
|
||||||
var slots = FillDay(run.Profile.Weekdays, wholeDay, null, GridPlanLayer.Main, run);
|
var slots = FillDay(
|
||||||
|
run.Profile.Weekdays,
|
||||||
|
wholeDay,
|
||||||
|
new SlotTarget(null, GridPlanLayer.Main),
|
||||||
|
run
|
||||||
|
);
|
||||||
|
|
||||||
if (run.Profile.Weekend.Count == 0)
|
if (run.Profile.Weekend.Count == 0)
|
||||||
return slots;
|
return slots;
|
||||||
|
|
||||||
foreach (var weekday in new[] { 6, 0 })
|
foreach (var weekday in new[] { 6, 0 })
|
||||||
slots.AddRange(
|
{
|
||||||
FillDay(run.Profile.Weekend, wholeDay, weekday, GridPlanLayer.Weekend, run)
|
var target = new SlotTarget(weekday, GridPlanLayer.Weekend);
|
||||||
);
|
slots.AddRange(FillDay(run.Profile.Weekend, wholeDay, target, run));
|
||||||
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -139,9 +145,8 @@ public sealed class GridPlanner(
|
|||||||
foreach (var weekday in GridCoverage.Week)
|
foreach (var weekday in GridCoverage.Week)
|
||||||
{
|
{
|
||||||
var gaps = GridCoverage.Gaps(existing, weekday, run.DayStart);
|
var gaps = GridCoverage.Gaps(existing, weekday, run.DayStart);
|
||||||
foreach (
|
var target = new SlotTarget(weekday, GridPlanLayer.Main);
|
||||||
var slot in FillDay(run.Profile.Weekdays, gaps, weekday, GridPlanLayer.Main, run)
|
foreach (var slot in FillDay(run.Profile.Weekdays, gaps, target, run))
|
||||||
)
|
|
||||||
entries.Add((weekday, slot));
|
entries.Add((weekday, slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,8 +173,7 @@ public sealed class GridPlanner(
|
|||||||
private static List<PlannedSlot> FillDay(
|
private static List<PlannedSlot> FillDay(
|
||||||
IReadOnlyList<GridBand> bands,
|
IReadOnlyList<GridBand> bands,
|
||||||
IReadOnlyList<(int From, int To)> free,
|
IReadOnlyList<(int From, int To)> free,
|
||||||
int? weekday,
|
SlotTarget target,
|
||||||
GridPlanLayer layer,
|
|
||||||
PlanRun run
|
PlanRun run
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -178,21 +182,25 @@ public sealed class GridPlanner(
|
|||||||
var used = new Dictionary<Guid, int>();
|
var used = new Dictionary<Guid, int>();
|
||||||
var slots = new List<PlannedSlot>();
|
var slots = new List<PlannedSlot>();
|
||||||
|
|
||||||
foreach (var band in bands)
|
// Что реально предстоит застроить: полосы профиля, пересечённые со свободным временем.
|
||||||
foreach (var (bandFrom, bandTo) in BandPieces(band, run.DayStart))
|
var pieces =
|
||||||
foreach (var (freeFrom, freeTo) in free)
|
from band in bands
|
||||||
{
|
from piece in BandPieces(band, run.DayStart)
|
||||||
var from = Math.Max(bandFrom, freeFrom);
|
from gap in free
|
||||||
var to = Math.Min(bandTo, freeTo);
|
let start = Math.Max(piece.From, gap.From)
|
||||||
if (to - from < MinSlotMinutes)
|
let end = Math.Min(piece.To, gap.To)
|
||||||
continue;
|
where end - start >= MinSlotMinutes
|
||||||
|
select (Band: band, From: start, To: end);
|
||||||
|
|
||||||
slots.AddRange(FillPiece(band, from, to, weekday, layer, run, used));
|
foreach (var piece in pieces)
|
||||||
}
|
slots.AddRange(FillPiece(piece.Band, piece.From, piece.To, target, run, used));
|
||||||
|
|
||||||
return [.. slots.OrderBy(s => GridCoverage.OffsetInDay(s.Start, run.DayStart))];
|
return [.. slots.OrderBy(s => GridCoverage.OffsetInDay(s.Start, run.DayStart))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Куда ложится слот: день недели и слой. Пара ходит вместе через всю раскладку.</summary>
|
||||||
|
private readonly record struct SlotTarget(int? Weekday, GridPlanLayer Layer);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Полоса в смещениях от начала вещательных суток. Начало суток канала произвольно, поэтому
|
/// Полоса в смещениях от начала вещательных суток. Начало суток канала произвольно, поэтому
|
||||||
/// полоса может перевалить через их конец — тогда она разрезается на два куска.
|
/// полоса может перевалить через их конец — тогда она разрезается на два куска.
|
||||||
@@ -217,8 +225,7 @@ public sealed class GridPlanner(
|
|||||||
GridBand band,
|
GridBand band,
|
||||||
int from,
|
int from,
|
||||||
int to,
|
int to,
|
||||||
int? weekday,
|
SlotTarget target,
|
||||||
GridPlanLayer layer,
|
|
||||||
PlanRun run,
|
PlanRun run,
|
||||||
Dictionary<Guid, int> used
|
Dictionary<Guid, int> used
|
||||||
)
|
)
|
||||||
@@ -250,7 +257,7 @@ public sealed class GridPlanner(
|
|||||||
// Якорь — только на первом слоте прайма: жёсткий старт у каждого слота подряд заставил
|
// Якорь — только на первом слоте прайма: жёсткий старт у каждого слота подряд заставил
|
||||||
// бы генератор крутить фон перед каждым из них.
|
// бы генератор крутить фон перед каждым из них.
|
||||||
var anchor = cursor == from && band.Daypart == Daypart.Prime;
|
var anchor = cursor == from && band.Daypart == Daypart.Prime;
|
||||||
var slot = Compose(band, weekday, layer, cursor, duration, anchor, run, used);
|
var slot = Compose(band, target, cursor, duration, anchor, run, used);
|
||||||
if (slot is not null)
|
if (slot is not null)
|
||||||
yield return slot;
|
yield return slot;
|
||||||
|
|
||||||
@@ -260,8 +267,7 @@ public sealed class GridPlanner(
|
|||||||
|
|
||||||
private static PlannedSlot? Compose(
|
private static PlannedSlot? Compose(
|
||||||
GridBand band,
|
GridBand band,
|
||||||
int? weekday,
|
SlotTarget target,
|
||||||
GridPlanLayer layer,
|
|
||||||
int offset,
|
int offset,
|
||||||
int duration,
|
int duration,
|
||||||
bool anchor,
|
bool anchor,
|
||||||
@@ -295,7 +301,7 @@ public sealed class GridPlanner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
used[group.Id] = used.GetValueOrDefault(group.Id) + 1;
|
used[group.Id] = used.GetValueOrDefault(group.Id) + 1;
|
||||||
run.WarnIfThin(band, group, weekday);
|
run.WarnIfThin(band, group, target.Weekday);
|
||||||
|
|
||||||
var blockMode = band.UnitsPerBlock > 0 ? SlotBlockMode.Count : SlotBlockMode.FillSlot;
|
var blockMode = band.UnitsPerBlock > 0 ? SlotBlockMode.Count : SlotBlockMode.FillSlot;
|
||||||
return New(
|
return New(
|
||||||
@@ -316,8 +322,8 @@ public sealed class GridPlanner(
|
|||||||
RepeatSource? repeat
|
RepeatSource? repeat
|
||||||
) =>
|
) =>
|
||||||
new(
|
new(
|
||||||
layer,
|
target.Layer,
|
||||||
weekday,
|
target.Weekday,
|
||||||
start,
|
start,
|
||||||
duration,
|
duration,
|
||||||
title,
|
title,
|
||||||
@@ -393,52 +399,48 @@ public sealed class GridPlanner(
|
|||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
var units = 0;
|
if (Describe(group, composition, info) is { } candidate)
|
||||||
var duration = TimeSpan.Zero;
|
candidates.Add(candidate);
|
||||||
var seriesUnits = 0;
|
|
||||||
var singleUnits = 0;
|
|
||||||
ShowAudience? strictest = null;
|
|
||||||
|
|
||||||
foreach (var element in composition)
|
|
||||||
{
|
|
||||||
if (!info.TryGetValue((element.Kind, element.Id), out var resolved))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
units += resolved.UnitCount;
|
|
||||||
duration += resolved.TotalDuration;
|
|
||||||
|
|
||||||
// У коллекции своего типа нет: франшиза — это почти всегда полнометражки.
|
|
||||||
if (resolved.ShowKind == ShowKind.Series)
|
|
||||||
seriesUnits += resolved.UnitCount;
|
|
||||||
else
|
|
||||||
singleUnits += resolved.UnitCount;
|
|
||||||
|
|
||||||
if (
|
|
||||||
resolved.Audience is { } audience
|
|
||||||
&& (strictest is null || audience > strictest)
|
|
||||||
)
|
|
||||||
strictest = audience;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Группа без готовых единиц в эфир не годится: слот встал бы, а место закрыл фон.
|
|
||||||
if (units == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
candidates.Add(
|
|
||||||
new GroupCandidate(
|
|
||||||
group.Id,
|
|
||||||
group.Name,
|
|
||||||
units,
|
|
||||||
strictest,
|
|
||||||
seriesUnits >= singleUnits ? ShowKind.Series : ShowKind.Single,
|
|
||||||
duration.TotalMinutes / units
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сводит вычисленный состав к тому, что нужно раскладке: сколько эфира группа даёт, какой она
|
||||||
|
/// в основном природы и насколько строгий в ней контент. Группа без готовых единиц не годится
|
||||||
|
/// вовсе — слот по ней встал бы, а место закрыл фон.
|
||||||
|
/// </summary>
|
||||||
|
private static GroupCandidate? Describe(
|
||||||
|
Group group,
|
||||||
|
IReadOnlyList<ResolvedElement> composition,
|
||||||
|
IReadOnlyDictionary<(GroupElementKind Kind, Guid Id), GroupElementInfo> info
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var resolved = composition
|
||||||
|
.Select(e => info.GetValueOrDefault((e.Kind, e.Id)))
|
||||||
|
.OfType<GroupElementInfo>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var units = resolved.Sum(r => r.UnitCount);
|
||||||
|
if (units == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// У коллекции своего типа нет: франшиза — это почти всегда полнометражки.
|
||||||
|
var seriesUnits = resolved.Where(r => r.ShowKind == ShowKind.Series).Sum(r => r.UnitCount);
|
||||||
|
var duration = resolved.Aggregate(TimeSpan.Zero, (sum, r) => sum + r.TotalDuration);
|
||||||
|
var strictest = resolved.Max(r => r.Audience);
|
||||||
|
|
||||||
|
return new GroupCandidate(
|
||||||
|
group.Id,
|
||||||
|
group.Name,
|
||||||
|
units,
|
||||||
|
strictest,
|
||||||
|
seriesUnits * 2 >= units ? ShowKind.Series : ShowKind.Single,
|
||||||
|
duration.TotalMinutes / units
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Группа глазами раскладки: чем её можно закрыть и насколько её хватит.</summary>
|
/// <summary>Группа глазами раскладки: чем её можно закрыть и насколько её хватит.</summary>
|
||||||
private sealed record GroupCandidate(
|
private sealed record GroupCandidate(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
|
|||||||
@@ -19,6 +19,20 @@ public static class GridProfiles
|
|||||||
public static GridProfile Get(GridProfileKind kind) =>
|
public static GridProfile Get(GridProfileKind kind) =>
|
||||||
All.FirstOrDefault(p => p.Kind == kind) ?? All[0];
|
All.FirstOrDefault(p => p.Kind == kind) ?? All[0];
|
||||||
|
|
||||||
|
// Названия полос, встречающиеся у нескольких профилей. Полосы — данные, и повторяющийся
|
||||||
|
// заголовок должен быть один: он попадает в название слота и виден админу в сетке.
|
||||||
|
private const string Morning = "Утро";
|
||||||
|
private const string Day = "День";
|
||||||
|
private const string DayBlock = "Дневной блок";
|
||||||
|
private const string DayMarathon = "Дневной марафон";
|
||||||
|
private const string Prime = "Прайм";
|
||||||
|
private const string Night = "Ночь";
|
||||||
|
private const string NightAir = "Ночной эфир";
|
||||||
|
private const string MorningMarathon = "Утренний марафон";
|
||||||
|
private const string GoodNight = "Спокойной ночи";
|
||||||
|
private const string SignOffTitle = "Конец вещания";
|
||||||
|
private const string PrimeMovie = "Кино в прайм";
|
||||||
|
|
||||||
private static TimeOnly At(int hour, int minute = 0) => new(hour, minute);
|
private static TimeOnly At(int hour, int minute = 0) => new(hour, minute);
|
||||||
|
|
||||||
/// <summary>Ночной взрослый блок — общая концовка суток почти у всех профилей.</summary>
|
/// <summary>Ночной взрослый блок — общая концовка суток почти у всех профилей.</summary>
|
||||||
@@ -46,7 +60,7 @@ public static class GridProfiles
|
|||||||
At(6),
|
At(6),
|
||||||
At(10),
|
At(10),
|
||||||
Daypart.Morning,
|
Daypart.Morning,
|
||||||
"Утро",
|
Morning,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -56,7 +70,7 @@ public static class GridProfiles
|
|||||||
At(10),
|
At(10),
|
||||||
At(14),
|
At(14),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"День",
|
Day,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -66,7 +80,7 @@ public static class GridProfiles
|
|||||||
At(14),
|
At(14),
|
||||||
At(18),
|
At(18),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной блок",
|
DayBlock,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -86,7 +100,7 @@ public static class GridProfiles
|
|||||||
At(20),
|
At(20),
|
||||||
At(23),
|
At(23),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Прайм",
|
Prime,
|
||||||
ShowKind.Single,
|
ShowKind.Single,
|
||||||
BlockMinutes: 150,
|
BlockMinutes: 150,
|
||||||
UnitsPerBlock: 1,
|
UnitsPerBlock: 1,
|
||||||
@@ -94,7 +108,7 @@ public static class GridProfiles
|
|||||||
CooldownDays: 14
|
CooldownDays: 14
|
||||||
),
|
),
|
||||||
AdultNight(At(23), At(2)),
|
AdultNight(At(23), At(2)),
|
||||||
new(At(2), At(6), Daypart.Night, "Ночной эфир", BlockMinutes: 240),
|
new(At(2), At(6), Daypart.Night, NightAir, BlockMinutes: 240),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
new(
|
new(
|
||||||
@@ -111,7 +125,7 @@ public static class GridProfiles
|
|||||||
At(10),
|
At(10),
|
||||||
At(14),
|
At(14),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной марафон",
|
DayMarathon,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 240
|
BlockMinutes: 240
|
||||||
@@ -132,7 +146,7 @@ public static class GridProfiles
|
|||||||
At(18),
|
At(18),
|
||||||
At(23),
|
At(23),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Кино в прайм",
|
PrimeMovie,
|
||||||
ShowKind.Single,
|
ShowKind.Single,
|
||||||
BlockMinutes: 150,
|
BlockMinutes: 150,
|
||||||
UnitsPerBlock: 1,
|
UnitsPerBlock: 1,
|
||||||
@@ -140,7 +154,7 @@ public static class GridProfiles
|
|||||||
CooldownDays: 21
|
CooldownDays: 21
|
||||||
),
|
),
|
||||||
AdultNight(At(23), At(2)),
|
AdultNight(At(23), At(2)),
|
||||||
new(At(2), At(6), Daypart.Night, "Ночной эфир", BlockMinutes: 240),
|
new(At(2), At(6), Daypart.Night, NightAir, BlockMinutes: 240),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -166,7 +180,7 @@ public static class GridProfiles
|
|||||||
At(10),
|
At(10),
|
||||||
At(14),
|
At(14),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной блок",
|
DayBlock,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -176,7 +190,7 @@ public static class GridProfiles
|
|||||||
At(14),
|
At(14),
|
||||||
At(19),
|
At(19),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной марафон",
|
DayMarathon,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 150,
|
BlockMinutes: 150,
|
||||||
@@ -186,21 +200,21 @@ public static class GridProfiles
|
|||||||
At(19),
|
At(19),
|
||||||
At(23),
|
At(23),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Прайм",
|
Prime,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
UnitsPerBlock: 4
|
UnitsPerBlock: 4
|
||||||
),
|
),
|
||||||
AdultNight(At(23), At(2)),
|
AdultNight(At(23), At(2)),
|
||||||
new(At(2), At(6), Daypart.Night, "Ночь", ShowKind.Series, BlockMinutes: 240),
|
new(At(2), At(6), Daypart.Night, Night, ShowKind.Series, BlockMinutes: 240),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
new(
|
new(
|
||||||
At(6),
|
At(6),
|
||||||
At(12),
|
At(12),
|
||||||
Daypart.Morning,
|
Daypart.Morning,
|
||||||
"Утренний марафон",
|
MorningMarathon,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 180,
|
BlockMinutes: 180,
|
||||||
@@ -210,7 +224,7 @@ public static class GridProfiles
|
|||||||
At(12),
|
At(12),
|
||||||
At(19),
|
At(19),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной марафон",
|
DayMarathon,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 210
|
BlockMinutes: 210
|
||||||
@@ -219,14 +233,14 @@ public static class GridProfiles
|
|||||||
At(19),
|
At(19),
|
||||||
At(23),
|
At(23),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Прайм",
|
Prime,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg13,
|
ShowAudience.Pg13,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
UnitsPerBlock: 4
|
UnitsPerBlock: 4
|
||||||
),
|
),
|
||||||
AdultNight(At(23), At(3)),
|
AdultNight(At(23), At(3)),
|
||||||
new(At(3), At(6), Daypart.Night, "Ночь", ShowKind.Series, BlockMinutes: 180),
|
new(At(3), At(6), Daypart.Night, Night, ShowKind.Series, BlockMinutes: 180),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -346,7 +360,7 @@ public static class GridProfiles
|
|||||||
At(14),
|
At(14),
|
||||||
At(18),
|
At(18),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной блок",
|
DayBlock,
|
||||||
BlockMinutes: 240,
|
BlockMinutes: 240,
|
||||||
Strategy: SlotStrategyType.RandomWithCooldown,
|
Strategy: SlotStrategyType.RandomWithCooldown,
|
||||||
CooldownDays: 2
|
CooldownDays: 2
|
||||||
@@ -373,7 +387,7 @@ public static class GridProfiles
|
|||||||
At(2),
|
At(2),
|
||||||
At(6),
|
At(6),
|
||||||
Daypart.Night,
|
Daypart.Night,
|
||||||
"Ночь",
|
Night,
|
||||||
BlockMinutes: 240,
|
BlockMinutes: 240,
|
||||||
Strategy: SlotStrategyType.RandomWithCooldown,
|
Strategy: SlotStrategyType.RandomWithCooldown,
|
||||||
CooldownDays: 1
|
CooldownDays: 1
|
||||||
@@ -418,7 +432,7 @@ public static class GridProfiles
|
|||||||
At(18),
|
At(18),
|
||||||
At(23),
|
At(23),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Кино в прайм",
|
PrimeMovie,
|
||||||
ShowKind.Single,
|
ShowKind.Single,
|
||||||
BlockMinutes: 150,
|
BlockMinutes: 150,
|
||||||
UnitsPerBlock: 1,
|
UnitsPerBlock: 1,
|
||||||
@@ -441,7 +455,7 @@ public static class GridProfiles
|
|||||||
At(2),
|
At(2),
|
||||||
At(6),
|
At(6),
|
||||||
Daypart.Night,
|
Daypart.Night,
|
||||||
"Ночной эфир",
|
NightAir,
|
||||||
ShowKind.Single,
|
ShowKind.Single,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
UnitsPerBlock: 1,
|
UnitsPerBlock: 1,
|
||||||
@@ -464,7 +478,7 @@ public static class GridProfiles
|
|||||||
At(6),
|
At(6),
|
||||||
At(10),
|
At(10),
|
||||||
Daypart.Morning,
|
Daypart.Morning,
|
||||||
"Утро",
|
Morning,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 90,
|
BlockMinutes: 90,
|
||||||
@@ -474,7 +488,7 @@ public static class GridProfiles
|
|||||||
At(10),
|
At(10),
|
||||||
At(14),
|
At(14),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"День",
|
Day,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 90,
|
BlockMinutes: 90,
|
||||||
@@ -484,7 +498,7 @@ public static class GridProfiles
|
|||||||
At(14),
|
At(14),
|
||||||
At(18),
|
At(18),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной блок",
|
DayBlock,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 90,
|
BlockMinutes: 90,
|
||||||
@@ -504,7 +518,7 @@ public static class GridProfiles
|
|||||||
At(21),
|
At(21),
|
||||||
At(22),
|
At(22),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Спокойной ночи",
|
GoodNight,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 60,
|
BlockMinutes: 60,
|
||||||
@@ -514,7 +528,7 @@ public static class GridProfiles
|
|||||||
At(22),
|
At(22),
|
||||||
At(6),
|
At(6),
|
||||||
Daypart.Night,
|
Daypart.Night,
|
||||||
"Конец вещания",
|
SignOffTitle,
|
||||||
SlotKind: SlotKind.SignOff,
|
SlotKind: SlotKind.SignOff,
|
||||||
BlockMinutes: 0
|
BlockMinutes: 0
|
||||||
),
|
),
|
||||||
@@ -524,7 +538,7 @@ public static class GridProfiles
|
|||||||
At(6),
|
At(6),
|
||||||
At(10),
|
At(10),
|
||||||
Daypart.Morning,
|
Daypart.Morning,
|
||||||
"Утренний марафон",
|
MorningMarathon,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 240
|
BlockMinutes: 240
|
||||||
@@ -533,7 +547,7 @@ public static class GridProfiles
|
|||||||
At(10),
|
At(10),
|
||||||
At(14),
|
At(14),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"День",
|
Day,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -543,7 +557,7 @@ public static class GridProfiles
|
|||||||
At(14),
|
At(14),
|
||||||
At(18),
|
At(18),
|
||||||
Daypart.Day,
|
Daypart.Day,
|
||||||
"Дневной блок",
|
DayBlock,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 120,
|
BlockMinutes: 120,
|
||||||
@@ -565,7 +579,7 @@ public static class GridProfiles
|
|||||||
At(21),
|
At(21),
|
||||||
At(22),
|
At(22),
|
||||||
Daypart.Prime,
|
Daypart.Prime,
|
||||||
"Спокойной ночи",
|
GoodNight,
|
||||||
ShowKind.Series,
|
ShowKind.Series,
|
||||||
ShowAudience.Pg,
|
ShowAudience.Pg,
|
||||||
BlockMinutes: 60,
|
BlockMinutes: 60,
|
||||||
@@ -575,7 +589,7 @@ public static class GridProfiles
|
|||||||
At(22),
|
At(22),
|
||||||
At(6),
|
At(6),
|
||||||
Daypart.Night,
|
Daypart.Night,
|
||||||
"Конец вещания",
|
SignOffTitle,
|
||||||
SlotKind: SlotKind.SignOff,
|
SlotKind: SlotKind.SignOff,
|
||||||
BlockMinutes: 0
|
BlockMinutes: 0
|
||||||
),
|
),
|
||||||
|
|||||||
+14
-6
@@ -38,9 +38,14 @@ public sealed class RestoreTemplateCommandHandler(IAppDbContext dbContext)
|
|||||||
|
|
||||||
// Слои, которых в снимке нет, — заведены после применения. Фоновый не трогаем: он есть
|
// Слои, которых в снимке нет, — заведены после применения. Фоновый не трогаем: он есть
|
||||||
// в любом шаблоне и удалению не подлежит.
|
// в любом шаблоне и удалению не подлежит.
|
||||||
foreach (var layer in template.Layers.ToList())
|
var staleLayers = template
|
||||||
if (!layer.IsBackground && snapshot.Layers.All(l => l.Id != layer.Id))
|
.Layers.Where(layer =>
|
||||||
template.RemoveLayer(layer.Id);
|
!layer.IsBackground && snapshot.Layers.All(l => l.Id != layer.Id)
|
||||||
|
)
|
||||||
|
.Select(layer => layer.Id)
|
||||||
|
.ToList();
|
||||||
|
foreach (var layerId in staleLayers)
|
||||||
|
template.RemoveLayer(layerId);
|
||||||
|
|
||||||
foreach (var layerSnapshot in snapshot.Layers)
|
foreach (var layerSnapshot in snapshot.Layers)
|
||||||
RestoreLayer(template, layerSnapshot, refs);
|
RestoreLayer(template, layerSnapshot, refs);
|
||||||
@@ -76,9 +81,12 @@ public sealed class RestoreTemplateCommandHandler(IAppDbContext dbContext)
|
|||||||
snapshot.IsEnabled
|
snapshot.IsEnabled
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach (var slot in layer.Slots.ToList())
|
var staleSlots = layer
|
||||||
if (snapshot.Slots.All(s => s.Id != slot.Id))
|
.Slots.Where(slot => snapshot.Slots.All(s => s.Id != slot.Id))
|
||||||
layer.RemoveSlot(slot.Id);
|
.Select(slot => slot.Id)
|
||||||
|
.ToList();
|
||||||
|
foreach (var slotId in staleSlots)
|
||||||
|
layer.RemoveSlot(slotId);
|
||||||
|
|
||||||
foreach (var slotSnapshot in snapshot.Slots)
|
foreach (var slotSnapshot in snapshot.Slots)
|
||||||
RestoreSlot(layer, slotSnapshot, refs);
|
RestoreSlot(layer, slotSnapshot, refs);
|
||||||
|
|||||||
+3
-3
@@ -212,9 +212,9 @@ public sealed class ValidateTemplateQueryHandler(
|
|||||||
{
|
{
|
||||||
var slots = layers.SelectMany(l => l.Slots).ToList();
|
var slots = layers.SelectMany(l => l.Slots).ToList();
|
||||||
|
|
||||||
foreach (var weekday in GridCoverage.Week)
|
return from weekday in GridCoverage.Week
|
||||||
foreach (var (from, to) in GridCoverage.Gaps(slots, weekday, dayStart))
|
from gap in GridCoverage.Gaps(slots, weekday, dayStart)
|
||||||
yield return Gap(weekday, from, to, dayStart);
|
select Gap(weekday, gap.From, gap.To, dayStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TemplateIssueDto Gap(int weekday, int from, int to, TimeOnly dayStart)
|
private static TemplateIssueDto Gap(int weekday, int from, int to, TimeOnly dayStart)
|
||||||
|
|||||||
@@ -152,10 +152,13 @@ public sealed class FileSystemMediaStorage(MediaPathResolver paths) : IMediaStor
|
|||||||
|
|
||||||
private static void DeleteCompanions(string directory, CompanionMatcher companion)
|
private static void DeleteCompanions(string directory, CompanionMatcher companion)
|
||||||
{
|
{
|
||||||
foreach (var file in Directory.EnumerateFiles(directory))
|
var companions = Directory
|
||||||
|
.EnumerateFiles(directory)
|
||||||
|
.Where(file => companion.Matches(Path.GetFileName(file)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var file in companions)
|
||||||
{
|
{
|
||||||
if (!companion.Matches(Path.GetFileName(file)))
|
|
||||||
continue;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user