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)
|
||||
return Result.Failure<GenerateGridResultDto>(ChannelErrors.TemplateNotFound);
|
||||
|
||||
var removed = 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 removed = command.Mode == GridGenerationMode.Rebuild ? RemoveAllSlots(template) : 0;
|
||||
|
||||
var created = 0;
|
||||
foreach (var slot in plan.Slots)
|
||||
@@ -74,6 +69,23 @@ public sealed class GenerateGridCommandHandler(
|
||||
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)
|
||||
{
|
||||
var (name, priority) =
|
||||
|
||||
@@ -110,15 +110,21 @@ public sealed class GridPlanner(
|
||||
private static List<PlannedSlot> BuildWeek(PlanRun run)
|
||||
{
|
||||
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)
|
||||
return slots;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -139,9 +145,8 @@ public sealed class GridPlanner(
|
||||
foreach (var weekday in GridCoverage.Week)
|
||||
{
|
||||
var gaps = GridCoverage.Gaps(existing, weekday, run.DayStart);
|
||||
foreach (
|
||||
var slot in FillDay(run.Profile.Weekdays, gaps, weekday, GridPlanLayer.Main, run)
|
||||
)
|
||||
var target = new SlotTarget(weekday, GridPlanLayer.Main);
|
||||
foreach (var slot in FillDay(run.Profile.Weekdays, gaps, target, run))
|
||||
entries.Add((weekday, slot));
|
||||
}
|
||||
|
||||
@@ -168,8 +173,7 @@ public sealed class GridPlanner(
|
||||
private static List<PlannedSlot> FillDay(
|
||||
IReadOnlyList<GridBand> bands,
|
||||
IReadOnlyList<(int From, int To)> free,
|
||||
int? weekday,
|
||||
GridPlanLayer layer,
|
||||
SlotTarget target,
|
||||
PlanRun run
|
||||
)
|
||||
{
|
||||
@@ -178,21 +182,25 @@ public sealed class GridPlanner(
|
||||
var used = new Dictionary<Guid, int>();
|
||||
var slots = new List<PlannedSlot>();
|
||||
|
||||
foreach (var band in bands)
|
||||
foreach (var (bandFrom, bandTo) in BandPieces(band, run.DayStart))
|
||||
foreach (var (freeFrom, freeTo) in free)
|
||||
{
|
||||
var from = Math.Max(bandFrom, freeFrom);
|
||||
var to = Math.Min(bandTo, freeTo);
|
||||
if (to - from < MinSlotMinutes)
|
||||
continue;
|
||||
// Что реально предстоит застроить: полосы профиля, пересечённые со свободным временем.
|
||||
var pieces =
|
||||
from band in bands
|
||||
from piece in BandPieces(band, run.DayStart)
|
||||
from gap in free
|
||||
let start = Math.Max(piece.From, gap.From)
|
||||
let end = Math.Min(piece.To, gap.To)
|
||||
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))];
|
||||
}
|
||||
|
||||
/// <summary>Куда ложится слот: день недели и слой. Пара ходит вместе через всю раскладку.</summary>
|
||||
private readonly record struct SlotTarget(int? Weekday, GridPlanLayer Layer);
|
||||
|
||||
/// <summary>
|
||||
/// Полоса в смещениях от начала вещательных суток. Начало суток канала произвольно, поэтому
|
||||
/// полоса может перевалить через их конец — тогда она разрезается на два куска.
|
||||
@@ -217,8 +225,7 @@ public sealed class GridPlanner(
|
||||
GridBand band,
|
||||
int from,
|
||||
int to,
|
||||
int? weekday,
|
||||
GridPlanLayer layer,
|
||||
SlotTarget target,
|
||||
PlanRun run,
|
||||
Dictionary<Guid, int> used
|
||||
)
|
||||
@@ -250,7 +257,7 @@ public sealed class GridPlanner(
|
||||
// Якорь — только на первом слоте прайма: жёсткий старт у каждого слота подряд заставил
|
||||
// бы генератор крутить фон перед каждым из них.
|
||||
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)
|
||||
yield return slot;
|
||||
|
||||
@@ -260,8 +267,7 @@ public sealed class GridPlanner(
|
||||
|
||||
private static PlannedSlot? Compose(
|
||||
GridBand band,
|
||||
int? weekday,
|
||||
GridPlanLayer layer,
|
||||
SlotTarget target,
|
||||
int offset,
|
||||
int duration,
|
||||
bool anchor,
|
||||
@@ -295,7 +301,7 @@ public sealed class GridPlanner(
|
||||
}
|
||||
|
||||
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;
|
||||
return New(
|
||||
@@ -316,8 +322,8 @@ public sealed class GridPlanner(
|
||||
RepeatSource? repeat
|
||||
) =>
|
||||
new(
|
||||
layer,
|
||||
weekday,
|
||||
target.Layer,
|
||||
target.Weekday,
|
||||
start,
|
||||
duration,
|
||||
title,
|
||||
@@ -393,52 +399,48 @@ public sealed class GridPlanner(
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
var units = 0;
|
||||
var duration = TimeSpan.Zero;
|
||||
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 (Describe(group, composition, info) is { } candidate)
|
||||
candidates.Add(candidate);
|
||||
}
|
||||
|
||||
// Группа без готовых единиц в эфир не годится: слот встал бы, а место закрыл фон.
|
||||
if (units == 0)
|
||||
continue;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
candidates.Add(
|
||||
new GroupCandidate(
|
||||
/// <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 >= singleUnits ? ShowKind.Series : ShowKind.Single,
|
||||
seriesUnits * 2 >= units ? ShowKind.Series : ShowKind.Single,
|
||||
duration.TotalMinutes / units
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/// <summary>Группа глазами раскладки: чем её можно закрыть и насколько её хватит.</summary>
|
||||
private sealed record GroupCandidate(
|
||||
Guid Id,
|
||||
|
||||
@@ -19,6 +19,20 @@ public static class GridProfiles
|
||||
public static GridProfile Get(GridProfileKind kind) =>
|
||||
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);
|
||||
|
||||
/// <summary>Ночной взрослый блок — общая концовка суток почти у всех профилей.</summary>
|
||||
@@ -46,7 +60,7 @@ public static class GridProfiles
|
||||
At(6),
|
||||
At(10),
|
||||
Daypart.Morning,
|
||||
"Утро",
|
||||
Morning,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
@@ -56,7 +70,7 @@ public static class GridProfiles
|
||||
At(10),
|
||||
At(14),
|
||||
Daypart.Day,
|
||||
"День",
|
||||
Day,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
@@ -66,7 +80,7 @@ public static class GridProfiles
|
||||
At(14),
|
||||
At(18),
|
||||
Daypart.Day,
|
||||
"Дневной блок",
|
||||
DayBlock,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
@@ -86,7 +100,7 @@ public static class GridProfiles
|
||||
At(20),
|
||||
At(23),
|
||||
Daypart.Prime,
|
||||
"Прайм",
|
||||
Prime,
|
||||
ShowKind.Single,
|
||||
BlockMinutes: 150,
|
||||
UnitsPerBlock: 1,
|
||||
@@ -94,7 +108,7 @@ public static class GridProfiles
|
||||
CooldownDays: 14
|
||||
),
|
||||
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(
|
||||
@@ -111,7 +125,7 @@ public static class GridProfiles
|
||||
At(10),
|
||||
At(14),
|
||||
Daypart.Day,
|
||||
"Дневной марафон",
|
||||
DayMarathon,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 240
|
||||
@@ -132,7 +146,7 @@ public static class GridProfiles
|
||||
At(18),
|
||||
At(23),
|
||||
Daypart.Prime,
|
||||
"Кино в прайм",
|
||||
PrimeMovie,
|
||||
ShowKind.Single,
|
||||
BlockMinutes: 150,
|
||||
UnitsPerBlock: 1,
|
||||
@@ -140,7 +154,7 @@ public static class GridProfiles
|
||||
CooldownDays: 21
|
||||
),
|
||||
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(14),
|
||||
Daypart.Day,
|
||||
"Дневной блок",
|
||||
DayBlock,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
@@ -176,7 +190,7 @@ public static class GridProfiles
|
||||
At(14),
|
||||
At(19),
|
||||
Daypart.Day,
|
||||
"Дневной марафон",
|
||||
DayMarathon,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 150,
|
||||
@@ -186,21 +200,21 @@ public static class GridProfiles
|
||||
At(19),
|
||||
At(23),
|
||||
Daypart.Prime,
|
||||
"Прайм",
|
||||
Prime,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
UnitsPerBlock: 4
|
||||
),
|
||||
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(
|
||||
At(6),
|
||||
At(12),
|
||||
Daypart.Morning,
|
||||
"Утренний марафон",
|
||||
MorningMarathon,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 180,
|
||||
@@ -210,7 +224,7 @@ public static class GridProfiles
|
||||
At(12),
|
||||
At(19),
|
||||
Daypart.Day,
|
||||
"Дневной марафон",
|
||||
DayMarathon,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 210
|
||||
@@ -219,14 +233,14 @@ public static class GridProfiles
|
||||
At(19),
|
||||
At(23),
|
||||
Daypart.Prime,
|
||||
"Прайм",
|
||||
Prime,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg13,
|
||||
BlockMinutes: 120,
|
||||
UnitsPerBlock: 4
|
||||
),
|
||||
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(18),
|
||||
Daypart.Day,
|
||||
"Дневной блок",
|
||||
DayBlock,
|
||||
BlockMinutes: 240,
|
||||
Strategy: SlotStrategyType.RandomWithCooldown,
|
||||
CooldownDays: 2
|
||||
@@ -373,7 +387,7 @@ public static class GridProfiles
|
||||
At(2),
|
||||
At(6),
|
||||
Daypart.Night,
|
||||
"Ночь",
|
||||
Night,
|
||||
BlockMinutes: 240,
|
||||
Strategy: SlotStrategyType.RandomWithCooldown,
|
||||
CooldownDays: 1
|
||||
@@ -418,7 +432,7 @@ public static class GridProfiles
|
||||
At(18),
|
||||
At(23),
|
||||
Daypart.Prime,
|
||||
"Кино в прайм",
|
||||
PrimeMovie,
|
||||
ShowKind.Single,
|
||||
BlockMinutes: 150,
|
||||
UnitsPerBlock: 1,
|
||||
@@ -441,7 +455,7 @@ public static class GridProfiles
|
||||
At(2),
|
||||
At(6),
|
||||
Daypart.Night,
|
||||
"Ночной эфир",
|
||||
NightAir,
|
||||
ShowKind.Single,
|
||||
BlockMinutes: 120,
|
||||
UnitsPerBlock: 1,
|
||||
@@ -464,7 +478,7 @@ public static class GridProfiles
|
||||
At(6),
|
||||
At(10),
|
||||
Daypart.Morning,
|
||||
"Утро",
|
||||
Morning,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 90,
|
||||
@@ -474,7 +488,7 @@ public static class GridProfiles
|
||||
At(10),
|
||||
At(14),
|
||||
Daypart.Day,
|
||||
"День",
|
||||
Day,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 90,
|
||||
@@ -484,7 +498,7 @@ public static class GridProfiles
|
||||
At(14),
|
||||
At(18),
|
||||
Daypart.Day,
|
||||
"Дневной блок",
|
||||
DayBlock,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 90,
|
||||
@@ -504,7 +518,7 @@ public static class GridProfiles
|
||||
At(21),
|
||||
At(22),
|
||||
Daypart.Prime,
|
||||
"Спокойной ночи",
|
||||
GoodNight,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 60,
|
||||
@@ -514,7 +528,7 @@ public static class GridProfiles
|
||||
At(22),
|
||||
At(6),
|
||||
Daypart.Night,
|
||||
"Конец вещания",
|
||||
SignOffTitle,
|
||||
SlotKind: SlotKind.SignOff,
|
||||
BlockMinutes: 0
|
||||
),
|
||||
@@ -524,7 +538,7 @@ public static class GridProfiles
|
||||
At(6),
|
||||
At(10),
|
||||
Daypart.Morning,
|
||||
"Утренний марафон",
|
||||
MorningMarathon,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 240
|
||||
@@ -533,7 +547,7 @@ public static class GridProfiles
|
||||
At(10),
|
||||
At(14),
|
||||
Daypart.Day,
|
||||
"День",
|
||||
Day,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 120,
|
||||
@@ -543,7 +557,7 @@ public static class GridProfiles
|
||||
At(14),
|
||||
At(18),
|
||||
Daypart.Day,
|
||||
"Дневной блок",
|
||||
DayBlock,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 120,
|
||||
@@ -565,7 +579,7 @@ public static class GridProfiles
|
||||
At(21),
|
||||
At(22),
|
||||
Daypart.Prime,
|
||||
"Спокойной ночи",
|
||||
GoodNight,
|
||||
ShowKind.Series,
|
||||
ShowAudience.Pg,
|
||||
BlockMinutes: 60,
|
||||
@@ -575,7 +589,7 @@ public static class GridProfiles
|
||||
At(22),
|
||||
At(6),
|
||||
Daypart.Night,
|
||||
"Конец вещания",
|
||||
SignOffTitle,
|
||||
SlotKind: SlotKind.SignOff,
|
||||
BlockMinutes: 0
|
||||
),
|
||||
|
||||
+14
-6
@@ -38,9 +38,14 @@ public sealed class RestoreTemplateCommandHandler(IAppDbContext dbContext)
|
||||
|
||||
// Слои, которых в снимке нет, — заведены после применения. Фоновый не трогаем: он есть
|
||||
// в любом шаблоне и удалению не подлежит.
|
||||
foreach (var layer in template.Layers.ToList())
|
||||
if (!layer.IsBackground && snapshot.Layers.All(l => l.Id != layer.Id))
|
||||
template.RemoveLayer(layer.Id);
|
||||
var staleLayers = template
|
||||
.Layers.Where(layer =>
|
||||
!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)
|
||||
RestoreLayer(template, layerSnapshot, refs);
|
||||
@@ -76,9 +81,12 @@ public sealed class RestoreTemplateCommandHandler(IAppDbContext dbContext)
|
||||
snapshot.IsEnabled
|
||||
);
|
||||
|
||||
foreach (var slot in layer.Slots.ToList())
|
||||
if (snapshot.Slots.All(s => s.Id != slot.Id))
|
||||
layer.RemoveSlot(slot.Id);
|
||||
var staleSlots = layer
|
||||
.Slots.Where(slot => snapshot.Slots.All(s => s.Id != slot.Id))
|
||||
.Select(slot => slot.Id)
|
||||
.ToList();
|
||||
foreach (var slotId in staleSlots)
|
||||
layer.RemoveSlot(slotId);
|
||||
|
||||
foreach (var slotSnapshot in snapshot.Slots)
|
||||
RestoreSlot(layer, slotSnapshot, refs);
|
||||
|
||||
+3
-3
@@ -212,9 +212,9 @@ public sealed class ValidateTemplateQueryHandler(
|
||||
{
|
||||
var slots = layers.SelectMany(l => l.Slots).ToList();
|
||||
|
||||
foreach (var weekday in GridCoverage.Week)
|
||||
foreach (var (from, to) in GridCoverage.Gaps(slots, weekday, dayStart))
|
||||
yield return Gap(weekday, from, to, dayStart);
|
||||
return from weekday in GridCoverage.Week
|
||||
from gap in GridCoverage.Gaps(slots, weekday, dayStart)
|
||||
select Gap(weekday, gap.From, gap.To, 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)
|
||||
{
|
||||
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
|
||||
{
|
||||
File.Delete(file);
|
||||
|
||||
Reference in New Issue
Block a user