mirror of https://github.com/ErsatzTV/ErsatzTV.git
18 changed files with 229 additions and 83 deletions
@ -0,0 +1,7 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.HDHR.Commands |
||||||
|
{ |
||||||
|
public record UpdateHDHRTunerCount(int TunerCount) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||||
|
} |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using static LanguageExt.Prelude; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.HDHR.Commands |
||||||
|
{ |
||||||
|
public class UpdateHDHRTunerCountHandler : MediatR.IRequestHandler<UpdateHDHRTunerCount, Either<BaseError, Unit>> |
||||||
|
{ |
||||||
|
private readonly IConfigElementRepository _configElementRepository; |
||||||
|
|
||||||
|
public UpdateHDHRTunerCountHandler(IConfigElementRepository configElementRepository) => |
||||||
|
_configElementRepository = configElementRepository; |
||||||
|
|
||||||
|
public Task<Either<BaseError, Unit>> Handle( |
||||||
|
UpdateHDHRTunerCount request, |
||||||
|
CancellationToken cancellationToken) => |
||||||
|
Validate(request) |
||||||
|
.MapT(_ => Upsert(ConfigElementKey.HDHRTunerCount, request.TunerCount.ToString())) |
||||||
|
.Bind(v => v.ToEitherAsync()); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, Unit>> Validate(UpdateHDHRTunerCount request) => |
||||||
|
Optional(request.TunerCount) |
||||||
|
.Filter(tc => tc > 0) |
||||||
|
.Map(_ => Unit.Default) |
||||||
|
.ToValidation<BaseError>("Tuner count must be greater than zero") |
||||||
|
.AsTask(); |
||||||
|
|
||||||
|
private Task<Unit> Upsert(ConfigElementKey key, string value) => |
||||||
|
_configElementRepository.Get(key).Match( |
||||||
|
ce => |
||||||
|
{ |
||||||
|
ce.Value = value; |
||||||
|
return _configElementRepository.Update(ce); |
||||||
|
}, |
||||||
|
() => |
||||||
|
{ |
||||||
|
var ce = new ConfigElement { Key = key.Key, Value = value }; |
||||||
|
return _configElementRepository.Add(ce); |
||||||
|
}).ToUnit(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.HDHR.Queries |
||||||
|
{ |
||||||
|
public record GetHDHRTunerCount : IRequest<int>; |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.HDHR.Queries |
||||||
|
{ |
||||||
|
public class GetHDHRTunerCountHandler : IRequestHandler<GetHDHRTunerCount, int> |
||||||
|
{ |
||||||
|
private readonly IConfigElementRepository _configElementRepository; |
||||||
|
|
||||||
|
public GetHDHRTunerCountHandler(IConfigElementRepository configElementRepository) => |
||||||
|
_configElementRepository = configElementRepository; |
||||||
|
|
||||||
|
public Task<int> Handle(GetHDHRTunerCount request, CancellationToken cancellationToken) => |
||||||
|
_configElementRepository.GetValue<int>(ConfigElementKey.HDHRTunerCount) |
||||||
|
.Map(result => result.IfNone(2)); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,120 @@ |
|||||||
|
@page "/settings" |
||||||
|
@using ErsatzTV.Application.FFmpegProfiles |
||||||
|
@using ErsatzTV.Application.FFmpegProfiles.Commands |
||||||
|
@using ErsatzTV.Application.FFmpegProfiles.Queries |
||||||
|
@using ErsatzTV.Application.HDHR.Commands |
||||||
|
@using ErsatzTV.Application.HDHR.Queries |
||||||
|
@using ErsatzTV.Application.MediaItems.Queries |
||||||
|
@using System.Globalization |
||||||
|
@using Unit = LanguageExt.Unit |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject ILogger<Settings> Logger |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row"> |
||||||
|
<MudCard Class="mr-6" Style="max-width: 400px"> |
||||||
|
<MudCardHeader> |
||||||
|
<CardHeaderContent> |
||||||
|
<MudText Typo="Typo.h6">FFmpeg Settings</MudText> |
||||||
|
</CardHeaderContent> |
||||||
|
</MudCardHeader> |
||||||
|
<MudCardContent> |
||||||
|
<MudForm @bind-IsValid="@_success"> |
||||||
|
<MudTextField T="string" Label="FFmpeg Path" @bind-Value="_ffmpegSettings.FFmpegPath" Validation="@(new Func<string, string>(ValidatePathExists))" Required="true" RequiredError="FFmpeg path is required!"/> |
||||||
|
<MudElement HtmlTag="div" Class="mt-3"> |
||||||
|
<MudTextField T="string" Label="FFprobe Path" @bind-Value="_ffmpegSettings.FFprobePath" Validation="@(new Func<string, string>(ValidatePathExists))" Required="true" RequiredError="FFprobe path is required!"/> |
||||||
|
</MudElement> |
||||||
|
<MudElement HtmlTag="div" Class="mt-3"> |
||||||
|
<MudSelect Label="Default Profile" @bind-Value="_ffmpegSettings.DefaultFFmpegProfileId" For="@(() => _ffmpegSettings.DefaultFFmpegProfileId)"> |
||||||
|
@foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@profile.Id">@profile.Name</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
</MudElement> |
||||||
|
<MudSelect Class="mt-3" Label="Preferred Language" @bind-Value="_ffmpegSettings.PreferredLanguageCode" For="@(() => _ffmpegSettings.PreferredLanguageCode)" Required="true" RequiredError="Preferred Language Code is required!"> |
||||||
|
@foreach (CultureInfo culture in _availableCultures) |
||||||
|
{ |
||||||
|
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> |
||||||
|
} |
||||||
|
</MudSelect> |
||||||
|
<MudElement HtmlTag="div" Class="mt-3"> |
||||||
|
<MudSwitch T="bool" |
||||||
|
Label="Save troubleshooting reports to disk" |
||||||
|
Color="Color.Primary" |
||||||
|
@bind-Checked="@_ffmpegSettings.SaveReports"/> |
||||||
|
</MudElement> |
||||||
|
</MudForm> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_success)" OnClick="@(_ => SaveFFmpegSettings())">Save Settings</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
<MudCard Style="width: 350px"> |
||||||
|
<MudCardHeader> |
||||||
|
<CardHeaderContent> |
||||||
|
<MudText Typo="Typo.h6">HDHomeRun Settings</MudText> |
||||||
|
</CardHeaderContent> |
||||||
|
</MudCardHeader> |
||||||
|
<MudCardContent> |
||||||
|
<MudForm @bind-IsValid="@_hdhrSuccess"> |
||||||
|
<MudTextField T="int" Label="Tuner Count" @bind-Value="_tunerCount" Validation="@(new Func<int, string>(ValidateTunerCount))" Required="true" RequiredError="Tuner count is required!"/> |
||||||
|
</MudForm> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_hdhrSuccess)" OnClick="@(_ => SaveHDHRSettings())">Save Settings</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
private bool _success; |
||||||
|
private bool _hdhrSuccess; |
||||||
|
private List<FFmpegProfileViewModel> _ffmpegProfiles; |
||||||
|
private FFmpegSettingsViewModel _ffmpegSettings; |
||||||
|
private List<CultureInfo> _availableCultures; |
||||||
|
private int _tunerCount; |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
await LoadFFmpegProfilesAsync(); |
||||||
|
|
||||||
|
_ffmpegSettings = await Mediator.Send(new GetFFmpegSettings()); |
||||||
|
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath); |
||||||
|
_availableCultures = await Mediator.Send(new GetAllLanguageCodes()); |
||||||
|
_tunerCount = await Mediator.Send(new GetHDHRTunerCount()); |
||||||
|
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount)); |
||||||
|
} |
||||||
|
|
||||||
|
private static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null; |
||||||
|
|
||||||
|
private static string ValidateTunerCount(int tunerCount) => tunerCount <= 0 ? "Tuner count must be greater than zero" : null; |
||||||
|
|
||||||
|
private async Task LoadFFmpegProfilesAsync() => |
||||||
|
_ffmpegProfiles = await Mediator.Send(new GetAllFFmpegProfiles()); |
||||||
|
|
||||||
|
private async Task SaveFFmpegSettings() |
||||||
|
{ |
||||||
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings)); |
||||||
|
result.Match( |
||||||
|
Left: error => |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving FFmpeg settings: {Error}", error.Value); |
||||||
|
}, |
||||||
|
Right: _ => Snackbar.Add("Successfully saved FFmpeg settings", Severity.Success)); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task SaveHDHRSettings() |
||||||
|
{ |
||||||
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateHDHRTunerCount(_tunerCount)); |
||||||
|
result.Match( |
||||||
|
Left: error => |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving HDHomeRun settings: {Error}", error.Value); |
||||||
|
}, |
||||||
|
Right: _ => Snackbar.Add("Successfully saved HDHomeRun settings", Severity.Success)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue