Enhance grid import/export functionality to support dynamic groups and collections
Updated the grid import/export process to allow for the inclusion of groups and collections directly from the configuration file. This enhancement ensures that missing groups and collections are created during import, improving the flexibility of the grid management system. Adjusted related classes and methods to accommodate these changes, ensuring a cohesive integration. Enhanced documentation and user prompts to clarify the new functionality and its usage.
This commit is contained in:
+133
-7
@@ -3,6 +3,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Broadcast;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
using TeleWave.Application.Programming.Groups;
|
||||
using TeleWave.Domain.Library;
|
||||
using TeleWave.Domain.Programming;
|
||||
|
||||
namespace TeleWave.Application.Programming.Templates.Transfer;
|
||||
@@ -11,10 +13,17 @@ namespace TeleWave.Application.Programming.Templates.Transfer;
|
||||
/// Загружает сетку из файла. Ссылки в файле — имена, поэтому импорт их разрешает: незнакомое имя
|
||||
/// не валит загрузку, а становится замечанием, и админ видит списком, чего в библиотеке не нашлось.
|
||||
///
|
||||
/// Недостающие группы и коллекции файл может принести с собой: сетку, собранную под ещё не
|
||||
/// разобранную библиотеку, иначе некуда положить — слот ссылается на группу, а групп нет. Шоу при
|
||||
/// этом не создаются никогда: их приносит загрузка медиа, и выдумать их импорт не может.
|
||||
///
|
||||
/// Эфир при этом не двигается — как и любая правка сетки, импорт только помечает шаблон изменённым.
|
||||
/// </summary>
|
||||
public sealed class ImportGridCommandHandler(IAppDbContext dbContext, SlotWriter writer)
|
||||
: ICommandHandler<ImportGridCommand, Result<GridImportResultDto>>
|
||||
public sealed class ImportGridCommandHandler(
|
||||
IAppDbContext dbContext,
|
||||
SlotWriter writer,
|
||||
GroupStatsService stats
|
||||
) : ICommandHandler<ImportGridCommand, Result<GridImportResultDto>>
|
||||
{
|
||||
public async Task<Result<GridImportResultDto>> Handle(
|
||||
ImportGridCommand command,
|
||||
@@ -29,16 +38,22 @@ public sealed class ImportGridCommandHandler(IAppDbContext dbContext, SlotWriter
|
||||
if (template is null)
|
||||
return Result.Failure<GridImportResultDto>(ChannelErrors.TemplateNotFound);
|
||||
|
||||
var groups = await NamesAsync(
|
||||
dbContext.Groups.AsNoTracking().Select(g => new NamedRef(g.Id, g.Name)),
|
||||
cancellationToken
|
||||
);
|
||||
var junctions = await NamesAsync(
|
||||
dbContext.JunctionTemplates.AsNoTracking().Select(j => new NamedRef(j.Id, j.Name)),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
var warnings = new List<string>();
|
||||
|
||||
// Библиотеку наполняем до слотов: слот ссылается на группу по имени, и группы из файла
|
||||
// обязаны существовать к моменту, когда до него дойдёт очередь.
|
||||
var collections = await EnsureCollectionsAsync(command.Config, warnings, cancellationToken);
|
||||
var (groups, createdGroups) = await EnsureGroupsAsync(
|
||||
command.Config,
|
||||
collections.Names,
|
||||
warnings,
|
||||
cancellationToken
|
||||
);
|
||||
var removed = command.Replace ? Clear(template) : 0;
|
||||
var created = 0;
|
||||
var skipped = 0;
|
||||
@@ -79,11 +94,122 @@ public sealed class ImportGridCommandHandler(IAppDbContext dbContext, SlotWriter
|
||||
created,
|
||||
skipped,
|
||||
removed,
|
||||
createdGroups,
|
||||
collections.Created,
|
||||
warnings
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Заводит коллекции файла, которых нет в библиотеке. Существующая по имени не трогается: имя —
|
||||
/// это ключ узнаваемости, и дописывать в чужую коллекцию свои шоу импорт не вправе.
|
||||
/// </summary>
|
||||
private async Task<(Dictionary<string, Guid> Names, int Created)> EnsureCollectionsAsync(
|
||||
GridConfig config,
|
||||
List<string> warnings,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var names = await NamesAsync(
|
||||
dbContext.Collections.AsNoTracking().Select(c => new NamedRef(c.Id, c.Name)),
|
||||
cancellationToken
|
||||
);
|
||||
if (config.Collections is not { Count: > 0 } wanted)
|
||||
return (names, 0);
|
||||
|
||||
var shows = await ShowNamesAsync(cancellationToken);
|
||||
var created = 0;
|
||||
|
||||
foreach (var configCollection in wanted)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configCollection.Name))
|
||||
continue;
|
||||
if (names.ContainsKey(configCollection.Name.Trim()))
|
||||
continue;
|
||||
|
||||
var collection = Collection.Create(configCollection.Name, configCollection.Description);
|
||||
foreach (var showName in configCollection.Shows ?? [])
|
||||
{
|
||||
if (Resolve(showName, shows, "Шоу", warnings) is { } showId)
|
||||
collection.AddShow(showId);
|
||||
}
|
||||
|
||||
dbContext.Collections.Add(collection);
|
||||
names[collection.Name] = collection.Id;
|
||||
created++;
|
||||
}
|
||||
|
||||
return (names, created);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Заводит группы файла, которых нет в библиотеке, и возвращает справочник имён для слотов.
|
||||
/// Существующая группа переиспользуется как есть: состав чужой группы импорт не переписывает,
|
||||
/// иначе один принесённый файл молча перекроил бы и соседний канал, где та же группа стоит
|
||||
/// в слотах.
|
||||
/// </summary>
|
||||
private async Task<(Dictionary<string, Guid> Names, int Created)> EnsureGroupsAsync(
|
||||
GridConfig config,
|
||||
IReadOnlyDictionary<string, Guid> collections,
|
||||
List<string> warnings,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var names = await NamesAsync(
|
||||
dbContext.Groups.AsNoTracking().Select(g => new NamedRef(g.Id, g.Name)),
|
||||
cancellationToken
|
||||
);
|
||||
if (config.Groups is not { Count: > 0 } wanted)
|
||||
return (names, 0);
|
||||
|
||||
var shows = await ShowNamesAsync(cancellationToken);
|
||||
var created = 0;
|
||||
|
||||
foreach (var configGroup in wanted)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configGroup.Name))
|
||||
continue;
|
||||
if (names.ContainsKey(configGroup.Name.Trim()))
|
||||
continue;
|
||||
|
||||
var group = Group.Create(configGroup.Name, configGroup.Description);
|
||||
|
||||
foreach (var showName in configGroup.Shows ?? [])
|
||||
{
|
||||
if (Resolve(showName, shows, "Шоу", warnings) is { } showId)
|
||||
group.AddElement(GroupElementKind.Show, showId);
|
||||
}
|
||||
|
||||
foreach (var collectionName in configGroup.Collections ?? [])
|
||||
{
|
||||
if (Resolve(collectionName, collections, "Коллекция", warnings) is { } id)
|
||||
group.AddElement(GroupElementKind.Collection, id);
|
||||
}
|
||||
|
||||
if (group.Items.Count == 0)
|
||||
warnings.Add(
|
||||
$"Группа «{group.Name}» создана пустой — слоты по ней закроет аварийный фон."
|
||||
);
|
||||
|
||||
dbContext.Groups.Add(group);
|
||||
// Статистика — кэш для карточки группы. Коллекция, заведённая этим же импортом, ещё не
|
||||
// сохранена и в счёт не попадёт; цифры сойдутся на первой правке состава.
|
||||
await stats.RecomputeAsync(group, cancellationToken);
|
||||
names[group.Name] = group.Id;
|
||||
created++;
|
||||
}
|
||||
|
||||
return (names, created);
|
||||
}
|
||||
|
||||
/// <summary>Шоу по названиям. Одинаковые названия схлопываются в первое найденное.</summary>
|
||||
private Task<Dictionary<string, Guid>> ShowNamesAsync(CancellationToken cancellationToken) =>
|
||||
NamesAsync(
|
||||
dbContext.Shows.AsNoTracking().Select(s => new NamedRef(s.Id, s.Name)),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
/// <summary>Снимает все слоты шаблона и возвращает их число — режим замены.</summary>
|
||||
private static int Clear(ScheduleTemplate template)
|
||||
{
|
||||
@@ -214,7 +340,7 @@ public sealed class ImportGridCommandHandler(IAppDbContext dbContext, SlotWriter
|
||||
if (known.TryGetValue(name.Trim(), out var id))
|
||||
return id;
|
||||
|
||||
var warning = $"{subject} «{name}» не найдена в библиотеке.";
|
||||
var warning = $"{subject} «{name}»: в библиотеке такого нет.";
|
||||
if (!warnings.Contains(warning, StringComparer.Ordinal))
|
||||
warnings.Add(warning);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user