* Used a UUID in HDHomeRun config to allow multiple instances on a same network * tweak some async calls * try to fix line endings --------- Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
517 lines
26 KiB
Plaintext
517 lines
26 KiB
Plaintext
@page "/settings"
|
|
@using ErsatzTV.Application.Configuration
|
|
@using ErsatzTV.Application.FFmpegProfiles
|
|
@using ErsatzTV.Application.Filler
|
|
@using ErsatzTV.Application.HDHR
|
|
@using ErsatzTV.Application.MediaItems
|
|
@using ErsatzTV.Application.Resolutions
|
|
@using ErsatzTV.Application.Watermarks
|
|
@using ErsatzTV.Core.Domain.Filler
|
|
@using ErsatzTV.FFmpeg.OutputFormat
|
|
@using Serilog.Events
|
|
@implements IDisposable
|
|
@inject IMediator Mediator
|
|
@inject ISnackbar Snackbar
|
|
@inject ILogger<Settings> Logger
|
|
@inject IDialogService Dialog
|
|
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row">
|
|
<MudGrid>
|
|
<MudCard Class="mr-6 mb-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 Audio Language" @bind-Value="_ffmpegSettings.PreferredAudioLanguageCode" For="@(() => _ffmpegSettings.PreferredAudioLanguageCode)" Required="true" RequiredError="Preferred Language Code is required!">
|
|
@foreach (LanguageCodeViewModel culture in _availableCultures)
|
|
{
|
|
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudSwitch T="bool"
|
|
Label="Use embedded subtitles"
|
|
Color="Color.Primary"
|
|
@bind-Value="@_ffmpegSettings.UseEmbeddedSubtitles"/>
|
|
</MudElement>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudSwitch T="bool"
|
|
Label="Extract and use embedded (text) subtitles"
|
|
Color="Color.Primary"
|
|
@bind-Value="@_ffmpegSettings.ExtractEmbeddedSubtitles"
|
|
Disabled="@(_ffmpegSettings.UseEmbeddedSubtitles == false)"/>
|
|
</MudElement>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudSwitch T="bool"
|
|
Label="Save troubleshooting reports to disk"
|
|
Color="Color.Primary"
|
|
@bind-Value="@_ffmpegSettings.SaveReports"/>
|
|
</MudElement>
|
|
<MudSelect Class="mt-3"
|
|
Label="Global Watermark"
|
|
@bind-Value="_ffmpegSettings.GlobalWatermarkId"
|
|
For="@(() => _ffmpegSettings.GlobalWatermarkId)"
|
|
Clearable="true">
|
|
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
|
@foreach (WatermarkViewModel watermark in _watermarks)
|
|
{
|
|
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudSelect Class="mt-3"
|
|
Label="Global Fallback Filler"
|
|
@bind-Value="_ffmpegSettings.GlobalFallbackFillerId"
|
|
For="@(() => _ffmpegSettings.GlobalFallbackFillerId)"
|
|
Clearable="true">
|
|
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
|
@foreach (FillerPresetViewModel fillerPreset in _fillerPresets)
|
|
{
|
|
<MudSelectItem T="int?" Value="@fillerPreset.Id">@fillerPreset.Name</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudTextField T="int"
|
|
Label="HLS Segmenter Idle Timeout"
|
|
@bind-Value="_ffmpegSettings.HlsSegmenterIdleTimeout"
|
|
Validation="@(new Func<int, string>(ValidateHlsSegmenterIdleTimeout))"
|
|
Required="true"
|
|
RequiredError="HLS Segmenter idle timeout is required!"
|
|
Adornment="Adornment.End"
|
|
AdornmentText="Seconds"/>
|
|
</MudElement>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudTextField T="int"
|
|
Label="Work-Ahead HLS Segmenter Limit"
|
|
@bind-Value="_ffmpegSettings.WorkAheadSegmenterLimit"
|
|
Validation="@(new Func<int, string>(ValidateWorkAheadSegmenterLimit))"
|
|
Required="true"
|
|
RequiredError="Work-ahead HLS Segmenter limit is required!"/>
|
|
</MudElement>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudTextField T="int"
|
|
Label="HLS Segmenter Initial Segment Count"
|
|
@bind-Value="_ffmpegSettings.InitialSegmentCount"
|
|
Validation="@(new Func<int, string>(ValidateInitialSegmentCount))"
|
|
Required="true"
|
|
RequiredError="HLS Segmenter initial segment count is required!"/>
|
|
</MudElement>
|
|
<MudSelect Class="mt-3"
|
|
Label="HLS Direct Output Format"
|
|
@bind-Value="_ffmpegSettings.HlsDirectOutputFormat"
|
|
For="@(() => _ffmpegSettings.HlsDirectOutputFormat)">
|
|
<MudSelectItem T="OutputFormatKind" Value="@OutputFormatKind.MpegTs">MPEG-TS</MudSelectItem>
|
|
<MudSelectItem T="OutputFormatKind" Value="@OutputFormatKind.Mp4">MP4</MudSelectItem>
|
|
<MudSelectItem T="OutputFormatKind" Value="@OutputFormatKind.Mkv">MKV</MudSelectItem>
|
|
</MudSelect>
|
|
</MudForm>
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_success)" OnClick="@(_ => SaveFFmpegSettings())">Save Settings</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
<MudStack Class="mr-6">
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">General Settings</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
<MudForm>
|
|
<MudSelect Class="mt-3"
|
|
Label="Default Minimum Log Level"
|
|
@bind-Value="_generalSettings.DefaultMinimumLogLevel"
|
|
For="@(() => _generalSettings.DefaultMinimumLogLevel)">
|
|
<MudSelectItem Value="@LogEventLevel.Debug">Debug</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Information">Information</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Warning">Warning</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Error">Error</MudSelectItem>
|
|
</MudSelect>
|
|
<MudSelect Class="mt-3"
|
|
Label="Scanning Minimum Log Level"
|
|
@bind-Value="_generalSettings.ScanningMinimumLogLevel"
|
|
For="@(() => _generalSettings.ScanningMinimumLogLevel)">
|
|
<MudSelectItem Value="@LogEventLevel.Debug">Debug</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Information">Information</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Warning">Warning</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Error">Error</MudSelectItem>
|
|
</MudSelect>
|
|
<MudSelect Class="mt-3"
|
|
Label="Scheduling Minimum Log Level"
|
|
@bind-Value="_generalSettings.SchedulingMinimumLogLevel"
|
|
For="@(() => _generalSettings.SchedulingMinimumLogLevel)">
|
|
<MudSelectItem Value="@LogEventLevel.Debug">Debug</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Information">Information</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Warning">Warning</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Error">Error</MudSelectItem>
|
|
</MudSelect>
|
|
<MudSelect Class="mt-3"
|
|
Label="Streaming Minimum Log Level"
|
|
@bind-Value="_generalSettings.StreamingMinimumLogLevel"
|
|
For="@(() => _generalSettings.StreamingMinimumLogLevel)">
|
|
<MudSelectItem Value="@LogEventLevel.Debug">Debug</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Information">Information</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Warning">Warning</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Error">Error</MudSelectItem>
|
|
</MudSelect>
|
|
<MudSelect Class="mt-3"
|
|
Label="Request Logging Minimum Log Level"
|
|
@bind-Value="_generalSettings.HttpMinimumLogLevel"
|
|
For="@(() => _generalSettings.HttpMinimumLogLevel)">
|
|
<MudSelectItem Value="@LogEventLevel.Debug">Debug</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Information">Information</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Warning">Warning</MudSelectItem>
|
|
<MudSelectItem Value="@LogEventLevel.Error">Error</MudSelectItem>
|
|
</MudSelect>
|
|
</MudForm>
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveGeneralSettings())" StartIcon="@Icons.Material.Filled.Save">
|
|
Save Settings
|
|
</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">HDHomeRun Settings</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
<MudTextField T="Guid" Label="UUID" @bind-Value="_uuid" ReadOnly="true" Disabled="true" />
|
|
</MudCardContent>
|
|
<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>
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">Scanner Settings</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
<MudForm @bind-IsValid="@_scannerSuccess">
|
|
<MudTextField T="int"
|
|
Label="Library Refresh Interval"
|
|
@bind-Value="_libraryRefreshInterval"
|
|
Validation="@(new Func<int, string>(ValidateLibraryRefreshInterval))"
|
|
Required="true"
|
|
RequiredError="Library refresh interval is required!"
|
|
Adornment="Adornment.End"
|
|
AdornmentText="Hours"/>
|
|
</MudForm>
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_scannerSuccess)" OnClick="@(_ => SaveScannerSettings())">Save Settings</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">Playout Settings</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
<MudForm @bind-IsValid="@_playoutSuccess">
|
|
<MudTextField T="int"
|
|
Label="Days To Build"
|
|
@bind-Value="_playoutSettings.DaysToBuild"
|
|
Validation="@(new Func<int, string>(ValidatePlayoutDaysToBuild))"
|
|
Required="true"
|
|
RequiredError="Playout days to build is required!"
|
|
Adornment="Adornment.End"
|
|
AdornmentText="Days"/>
|
|
<MudElement HtmlTag="div" Class="mt-3">
|
|
<MudTooltip Text="Controls whether file-not-found or unavailable items should be included in playouts">
|
|
<MudCheckBox Label="Skip Missing Items"
|
|
@bind-Value="_playoutSettings.SkipMissingItems"
|
|
For="@(() => _playoutSettings.SkipMissingItems)"/>
|
|
</MudTooltip>
|
|
</MudElement>
|
|
</MudForm>
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_playoutSuccess)" OnClick="@(_ => SavePlayoutSettings())">Save Settings</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
</MudStack>
|
|
<MudStack Class="mr-6">
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">Custom Resolutions</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
@if (_customResolutions.Any())
|
|
{
|
|
<MudTable Hover="true" Items="_customResolutions" Dense="true" Class="mt-6">
|
|
<ColGroup>
|
|
<col/>
|
|
<col style="width: 60px;"/>
|
|
</ColGroup>
|
|
<HeaderContent>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Resolution">@context.Name</MudTd>
|
|
<MudTd>
|
|
<div style="align-items: center; display: flex;">
|
|
<MudTooltip Text="Delete Custom Resolution">
|
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
|
OnClick="@(() => DeleteCustomResolution(context))">
|
|
</MudIconButton>
|
|
</MudTooltip>
|
|
</div>
|
|
</MudTd>
|
|
</RowTemplate>
|
|
</MudTable>
|
|
}
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => AddCustomResolution())" Class="ml-2">
|
|
Add Custom Resolution
|
|
</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
<MudCard Class="mb-6" Style="width: 350px">
|
|
<MudCardHeader>
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h6">Program Guide (XMLTV)</MudText>
|
|
</CardHeaderContent>
|
|
</MudCardHeader>
|
|
<MudCardContent>
|
|
<MudTextField T="int"
|
|
Label="Days To Build"
|
|
@bind-Value="_xmltvSettings.DaysToBuild"
|
|
Validation="@(new Func<int, string>(ValidateXmltvDaysToBuild))"
|
|
Required="true"
|
|
RequiredError="XMLTV days to build is required!"
|
|
Adornment="Adornment.End"
|
|
AdornmentText="Days"/>
|
|
<MudSelect Class="mt-3"
|
|
Label="XMLTV Time Zone"
|
|
@bind-Value="_xmltvSettings.TimeZone"
|
|
For="@(() => _xmltvSettings.TimeZone)">
|
|
<MudSelectItem Value="@XmltvTimeZone.Local">Local</MudSelectItem>
|
|
<MudSelectItem Value="@XmltvTimeZone.Utc">UTC</MudSelectItem>
|
|
</MudSelect>
|
|
</MudCardContent>
|
|
<MudCardActions>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveXmltvSettings())" StartIcon="@Icons.Material.Filled.Save">
|
|
Save Settings
|
|
</MudButton>
|
|
</MudCardActions>
|
|
</MudCard>
|
|
</MudStack>
|
|
</MudGrid>
|
|
</MudContainer>
|
|
|
|
@code {
|
|
private readonly CancellationTokenSource _cts = new();
|
|
|
|
private bool _success;
|
|
private bool _hdhrSuccess;
|
|
private bool _scannerSuccess;
|
|
private bool _playoutSuccess;
|
|
private List<FFmpegProfileViewModel> _ffmpegProfiles = new();
|
|
private FFmpegSettingsViewModel _ffmpegSettings = new();
|
|
private List<LanguageCodeViewModel> _availableCultures = new();
|
|
private List<WatermarkViewModel> _watermarks = new();
|
|
private List<FillerPresetViewModel> _fillerPresets = new();
|
|
private List<ResolutionViewModel> _customResolutions = new();
|
|
private int _tunerCount;
|
|
private int _libraryRefreshInterval;
|
|
private PlayoutSettingsViewModel _playoutSettings = new();
|
|
private GeneralSettingsViewModel _generalSettings = new();
|
|
private XmltvSettingsViewModel _xmltvSettings = new();
|
|
private Guid _uuid;
|
|
|
|
public void Dispose()
|
|
{
|
|
_cts.Cancel();
|
|
_cts.Dispose();
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
await LoadFFmpegProfilesAsync();
|
|
|
|
_ffmpegSettings = await Mediator.Send(new GetFFmpegSettings(), _cts.Token);
|
|
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath);
|
|
_availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token);
|
|
_watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token);
|
|
_fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token)
|
|
.Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList());
|
|
_tunerCount = await Mediator.Send(new GetHDHRTunerCount(), _cts.Token);
|
|
_uuid = await Mediator.Send(new GetHDHRUUID(), _cts.Token);
|
|
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount));
|
|
_libraryRefreshInterval = await Mediator.Send(new GetLibraryRefreshInterval(), _cts.Token);
|
|
_scannerSuccess = _libraryRefreshInterval is >= 0 and < 1_000_000;
|
|
_playoutSettings = await Mediator.Send(new GetPlayoutSettings(), _cts.Token);
|
|
_playoutSuccess = _playoutSettings.DaysToBuild > 0;
|
|
_generalSettings = await Mediator.Send(new GetGeneralSettings(), _cts.Token);
|
|
_xmltvSettings = await Mediator.Send(new GetXmltvSettings(), _cts.Token);
|
|
|
|
await RefreshCustomResolutions();
|
|
}
|
|
|
|
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 static string ValidateLibraryRefreshInterval(int libraryRefreshInterval) => libraryRefreshInterval switch
|
|
{
|
|
<= -1 => "Library refresh interval must be 0 (do not refresh) or greater than zero",
|
|
>= 1_000_000 => "Library refresh interval must be less than 1,000,000. Use 0 to disable automatic refresh",
|
|
_ => null
|
|
};
|
|
|
|
private static string ValidateXmltvDaysToBuild(int daysToBuild) => daysToBuild <= 0 ? "XMLTV days to build must be greater than zero" : null;
|
|
|
|
private static string ValidatePlayoutDaysToBuild(int daysToBuild) => daysToBuild <= 0 ? "Playout days to build must be greater than zero" : null;
|
|
|
|
private static string ValidateHlsSegmenterIdleTimeout(int idleTimeout) => idleTimeout < 30 ? "HLS Segmenter idle timeout must be greater than or equal to 30" : null;
|
|
|
|
private static string ValidateWorkAheadSegmenterLimit(int limit) => limit < 0 ? "Work-Ahead HLS Segmenter limit must be greater than or equal to 0" : null;
|
|
|
|
private static string ValidateInitialSegmentCount(int count) => count < 1 ? "HLS Segmenter initial segment count must be greater than or equal to 1" : null;
|
|
|
|
private async Task LoadFFmpegProfilesAsync() =>
|
|
_ffmpegProfiles = await Mediator.Send(new GetAllFFmpegProfiles(), _cts.Token);
|
|
|
|
private async Task SaveFFmpegSettings()
|
|
{
|
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings), _cts.Token);
|
|
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), _cts.Token);
|
|
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));
|
|
}
|
|
|
|
private async Task SaveScannerSettings()
|
|
{
|
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateLibraryRefreshInterval(_libraryRefreshInterval), _cts.Token);
|
|
result.Match(
|
|
Left: error =>
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error saving scanner settings: {Error}", error.Value);
|
|
},
|
|
Right: _ => Snackbar.Add("Successfully saved scanner settings", Severity.Success));
|
|
}
|
|
|
|
private async Task SavePlayoutSettings()
|
|
{
|
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdatePlayoutSettings(_playoutSettings), _cts.Token);
|
|
result.Match(
|
|
Left: error =>
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error saving playout settings: {Error}", error.Value);
|
|
},
|
|
Right: _ => Snackbar.Add("Successfully saved playout settings", Severity.Success));
|
|
}
|
|
|
|
private async Task SaveGeneralSettings()
|
|
{
|
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateGeneralSettings(_generalSettings), _cts.Token);
|
|
result.Match(
|
|
Left: error =>
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error saving general settings: {Error}", error.Value);
|
|
},
|
|
Right: _ => Snackbar.Add("Successfully saved general settings", Severity.Success));
|
|
}
|
|
|
|
private async Task SaveXmltvSettings()
|
|
{
|
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateXmltvSettings(_xmltvSettings), _cts.Token);
|
|
result.Match(
|
|
Left: error =>
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error saving xmltv settings: {Error}", error.Value);
|
|
},
|
|
Right: _ => Snackbar.Add("Successfully saved xmltv settings", Severity.Success));
|
|
}
|
|
|
|
private async Task RefreshCustomResolutions() => _customResolutions = await Mediator.Send(new GetAllResolutions(), _cts.Token)
|
|
.Map(list => list.Filter(r => r.IsCustom).ToList());
|
|
|
|
private async Task AddCustomResolution()
|
|
{
|
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small };
|
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<AddCustomResolutionDialog>("Add Custom Resolution", options);
|
|
DialogResult result = await dialog.Result;
|
|
if (!result.Canceled && result.Data is ResolutionEditViewModel resolution)
|
|
{
|
|
Option<BaseError> saveResult = await Mediator.Send(
|
|
new CreateCustomResolution(resolution.Width, resolution.Height),
|
|
_cts.Token);
|
|
foreach (BaseError error in saveResult)
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error adding custom resolution: {Error}", error.Value);
|
|
}
|
|
|
|
if (saveResult.IsNone)
|
|
{
|
|
await RefreshCustomResolutions();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task DeleteCustomResolution(ResolutionViewModel resolution)
|
|
{
|
|
Option<BaseError> result = await Mediator.Send(new DeleteCustomResolution(resolution.Id), _cts.Token);
|
|
foreach (BaseError error in result)
|
|
{
|
|
Snackbar.Add(error.Value, Severity.Error);
|
|
Logger.LogError("Unexpected error deleting custom resolution: {Error}", error.Value);
|
|
}
|
|
|
|
if (result.IsNone)
|
|
{
|
|
await RefreshCustomResolutions();
|
|
}
|
|
}
|
|
|
|
} |