diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d970235..51bb00951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Do not allow deleting ffmpeg profiles that are used by channels +- Do not allow deleting default ffmpeg profile - Allow ffmpeg profiles using VAAPI accel to set h264 video profile - Fix HLS Direct playback, and make it accessible on separate streaming port - Fix playback troubleshooting when using multiple watermarks or multiple graphics elements diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs index 6023241d3..a7cff4b38 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/DeleteFFmpegProfileHandler.cs @@ -1,5 +1,6 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -7,7 +8,10 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.FFmpegProfiles; -public class DeleteFFmpegProfileHandler(IDbContextFactory dbContextFactory, ISearchTargets searchTargets) +public class DeleteFFmpegProfileHandler( + IDbContextFactory dbContextFactory, + IConfigElementRepository configElementRepository, + ISearchTargets searchTargets) : IRequestHandler> { public async Task> Handle( @@ -15,9 +19,7 @@ public class DeleteFFmpegProfileHandler(IDbContextFactory dbContextFa CancellationToken cancellationToken) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); - Validation validation = - await FFmpegProfileMustNotBeUsed(dbContext, request, cancellationToken) - .BindT(_ => FFmpegProfileMustExist(dbContext, request, cancellationToken)); + Validation validation = await Validate(dbContext, request, cancellationToken); return await validation.Apply(p => DoDeletion(dbContext, p)); } @@ -29,6 +31,15 @@ public class DeleteFFmpegProfileHandler(IDbContextFactory dbContextFa return Unit.Default; } + private async Task> Validate( + TvContext dbContext, + DeleteFFmpegProfile request, + CancellationToken cancellationToken) => + (await FFmpegProfileMustNotBeUsed(dbContext, request, cancellationToken), + await FFmpegProfileMustNotBeDefault(request, cancellationToken), + await FFmpegProfileMustExist(dbContext, request, cancellationToken)) + .Apply((_, _, ffmpegProfile) => ffmpegProfile); + private static Task> FFmpegProfileMustExist( TvContext dbContext, DeleteFFmpegProfile request, @@ -49,7 +60,23 @@ public class DeleteFFmpegProfileHandler(IDbContextFactory dbContextFa if (count > 0) { - return BaseError.New($"Cannot delete FFmpegProfile {request.FFmpegProfileId} that is used by {count} {(count > 1 ? "channels" : "channel")}"); + return BaseError.New( + $"Cannot delete FFmpeg Profile that is used by {count} {(count > 1 ? "channels" : "channel")}"); + } + + return Unit.Default; + } + + private async Task> FFmpegProfileMustNotBeDefault( + DeleteFFmpegProfile request, + CancellationToken cancellationToken) + { + Option defaultFFmpegProfileId = + await configElementRepository.GetValue(ConfigElementKey.FFmpegDefaultProfileId, cancellationToken); + + if (defaultFFmpegProfileId.Any(id => id == request.FFmpegProfileId)) + { + return BaseError.New("Cannot delete default FFmpeg Profile"); } return Unit.Default; diff --git a/ErsatzTV.Application/Scheduling/BlockTreeBlockGroupViewModel.cs b/ErsatzTV.Application/Scheduling/BlockTreeBlockGroupViewModel.cs deleted file mode 100644 index 796ebcd3c..000000000 --- a/ErsatzTV.Application/Scheduling/BlockTreeBlockGroupViewModel.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace ErsatzTV.Application.Scheduling; - -public record BlockTreeBlockGroupViewModel(int Id, string Name, List Blocks); diff --git a/ErsatzTV.Application/Scheduling/BlockTreeBlockViewModel.cs b/ErsatzTV.Application/Scheduling/BlockTreeBlockViewModel.cs deleted file mode 100644 index 7f4db1a99..000000000 --- a/ErsatzTV.Application/Scheduling/BlockTreeBlockViewModel.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace ErsatzTV.Application.Scheduling; - -public record BlockTreeBlockViewModel(int Id, string Name, int Minutes); diff --git a/ErsatzTV.Application/Scheduling/BlockTreeViewModel.cs b/ErsatzTV.Application/Scheduling/BlockTreeViewModel.cs deleted file mode 100644 index 6d6e7552e..000000000 --- a/ErsatzTV.Application/Scheduling/BlockTreeViewModel.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace ErsatzTV.Application.Scheduling; - -public record BlockTreeViewModel(List Groups); diff --git a/ErsatzTV.Application/Scheduling/Mapper.cs b/ErsatzTV.Application/Scheduling/Mapper.cs index 5adc9a8e7..8322a0333 100644 --- a/ErsatzTV.Application/Scheduling/Mapper.cs +++ b/ErsatzTV.Application/Scheduling/Mapper.cs @@ -28,15 +28,6 @@ internal static class Mapper tg.Name, tg.Templates.OrderBy(t => t.Name).Map(t => new TreeItemViewModel(t.Id, t.Name)).ToList())).ToList()); - internal static BlockTreeViewModel ProjectToViewModel(List blockGroups) => - new( - blockGroups.OrderBy(bg => bg.Name).Map(bg => new BlockTreeBlockGroupViewModel( - bg.Id, - bg.Name, - bg.Blocks.OrderBy(b => b.Name).Map(b => new BlockTreeBlockViewModel(b.Id, b.Name, b.Minutes)) - .ToList())) - .ToList()); - internal static BlockGroupViewModel ProjectToViewModel(BlockGroup blockGroup) => new(blockGroup.Id, blockGroup.Name); diff --git a/ErsatzTV.Application/Scheduling/Queries/GetBlockTree.cs b/ErsatzTV.Application/Scheduling/Queries/GetBlockTree.cs deleted file mode 100644 index 8e0093886..000000000 --- a/ErsatzTV.Application/Scheduling/Queries/GetBlockTree.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace ErsatzTV.Application.Scheduling; - -public record GetBlockTree : IRequest; diff --git a/ErsatzTV.Application/Scheduling/Queries/GetBlockTreeHandler.cs b/ErsatzTV.Application/Scheduling/Queries/GetBlockTreeHandler.cs deleted file mode 100644 index 95523a00a..000000000 --- a/ErsatzTV.Application/Scheduling/Queries/GetBlockTreeHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Infrastructure.Data; -using Microsoft.EntityFrameworkCore; - -namespace ErsatzTV.Application.Scheduling; - -public class GetBlockTreeHandler(IDbContextFactory dbContextFactory) - : IRequestHandler -{ - public async Task Handle(GetBlockTree request, CancellationToken cancellationToken) - { - await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); - - List blockGroups = await dbContext.BlockGroups - .AsNoTracking() - .Include(g => g.Blocks) - .ToListAsync(cancellationToken); - - return Mapper.ProjectToViewModel(blockGroups); - } -} diff --git a/ErsatzTV/ViewModels/BlockTreeItemViewModel.cs b/ErsatzTV/ViewModels/BlockTreeItemViewModel.cs deleted file mode 100644 index b32b48fb9..000000000 --- a/ErsatzTV/ViewModels/BlockTreeItemViewModel.cs +++ /dev/null @@ -1,84 +0,0 @@ -using ErsatzTV.Application.Scheduling; -using MudBlazor; - -namespace ErsatzTV.ViewModels; - -public class BlockTreeItemViewModel -{ - public BlockTreeItemViewModel(BlockGroupViewModel blockGroup) - { - Text = blockGroup.Name; - EndText = string.Empty; - TreeItems = []; - BlockGroupId = blockGroup.Id; - Icon = Icons.Material.Filled.Folder; - } - - public BlockTreeItemViewModel(BlockTreeBlockGroupViewModel blockGroup) - { - Text = blockGroup.Name; - EndText = string.Empty; - TreeItems = blockGroup.Blocks.Map(b => new TreeItemData - { Value = new BlockTreeItemViewModel(b) }).ToList(); - BlockGroupId = blockGroup.Id; - Icon = Icons.Material.Filled.Folder; - } - - public BlockTreeItemViewModel(BlockViewModel block) - { - Text = block.Name; - if (block.Minutes / 60 >= 1) - { - string plural = block.Minutes / 60 >= 2 ? "s" : string.Empty; - EndText = $"{block.Minutes / 60} hour{plural}"; - if (block.Minutes % 60 != 0) - { - EndText += $", {block.Minutes % 60} minutes"; - } - } - else - { - EndText = $"{block.Minutes} minutes"; - } - - TreeItems = []; - CanExpand = false; - BlockId = block.Id; - } - - public BlockTreeItemViewModel(BlockTreeBlockViewModel block) - { - Text = block.Name; - if (block.Minutes / 60 >= 1) - { - string plural = block.Minutes / 60 >= 2 ? "s" : string.Empty; - EndText = $"{block.Minutes / 60} hour{plural}"; - if (block.Minutes % 60 != 0) - { - EndText += $", {block.Minutes % 60} minutes"; - } - } - else - { - EndText = $"{block.Minutes} minutes"; - } - - TreeItems = []; - CanExpand = false; - BlockId = block.Id; - } - - public string Text { get; } - - public string EndText { get; } - - public string Icon { get; } - - public bool CanExpand { get; } - - public int? BlockId { get; } - - public int? BlockGroupId { get; } - - public List> TreeItems { get; } -}