Browse Source

settings rework (#144)

* add hdhr tuner count setting

* code cleanup
pull/146/head
Jason Dove 5 years ago committed by GitHub
parent
commit
a6496db58d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCount.cs
  2. 45
      ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs
  3. 6
      ErsatzTV.Application/HDHR/Queries/GetHDHRTunerCount.cs
  4. 21
      ErsatzTV.Application/HDHR/Queries/GetHDHRTunerCountHandler.cs
  5. 2
      ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs
  6. 1
      ErsatzTV.Core/Domain/ConfigElementKey.cs
  7. 2
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  8. 8
      ErsatzTV/Controllers/HdhrController.cs
  9. 71
      ErsatzTV/Pages/FFmpeg.razor
  10. 2
      ErsatzTV/Pages/Movie.razor
  11. 5
      ErsatzTV/Pages/MovieList.razor
  12. 5
      ErsatzTV/Pages/MusicVideoList.razor
  13. 120
      ErsatzTV/Pages/Settings.razor
  14. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  15. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  16. 5
      ErsatzTV/Pages/TelevisionShowList.razor
  17. 5
      ErsatzTV/Shared/FragmentLetterAnchor.razor
  18. 3
      ErsatzTV/Shared/MainLayout.razor

7
ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCount.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.HDHR.Commands
{
public record UpdateHDHRTunerCount(int TunerCount) : MediatR.IRequest<Either<BaseError, Unit>>;
}

45
ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs

@ -0,0 +1,45 @@ @@ -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();
}
}

6
ErsatzTV.Application/HDHR/Queries/GetHDHRTunerCount.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
using MediatR;
namespace ErsatzTV.Application.HDHR.Queries
{
public record GetHDHRTunerCount : IRequest<int>;
}

21
ErsatzTV.Application/HDHR/Queries/GetHDHRTunerCountHandler.cs

@ -0,0 +1,21 @@ @@ -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));
}
}

2
ErsatzTV.Application/Search/Commands/RebuildSearchIndexHandler.cs

@ -39,7 +39,7 @@ namespace ErsatzTV.Application.Search.Commands @@ -39,7 +39,7 @@ namespace ErsatzTV.Application.Search.Commands
bool indexFolderExists = Directory.Exists(FileSystemLayout.SearchIndexFolder);
await _searchIndex.Initialize(_localFileSystem);
if (!indexFolderExists ||
await _configElementRepository.GetValue<int>(ConfigElementKey.SearchIndexVersion) <
_searchIndex.Version)

1
ErsatzTV.Core/Domain/ConfigElementKey.cs

@ -13,5 +13,6 @@ @@ -13,5 +13,6 @@
public static ConfigElementKey FFmpegSaveReports => new("ffmpeg.save_reports");
public static ConfigElementKey FFmpegPreferredLanguageCode => new("ffmpeg.preferred_language_code");
public static ConfigElementKey SearchIndexVersion => new("search_index.version");
public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count");
}
}

2
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -132,7 +132,7 @@ namespace ErsatzTV.Core.Metadata @@ -132,7 +132,7 @@ namespace ErsatzTV.Core.Metadata
await _searchIndex.RemoveItems(ids);
}
}
_searchIndex.Commit();
return Unit.Default;
}

8
ErsatzTV/Controllers/HdhrController.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System.Threading.Tasks;
using ErsatzTV.Application.Channels.Queries;
using ErsatzTV.Application.HDHR.Queries;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Extensions;
using LanguageExt;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@ -20,8 +22,10 @@ namespace ErsatzTV.Controllers @@ -20,8 +22,10 @@ namespace ErsatzTV.Controllers
new OkObjectResult(new DeviceXml(Request.Scheme, Request.Host.ToString()));
[HttpGet("discover.json")]
public IActionResult Discover() =>
new OkObjectResult(new Discover(Request.Scheme, Request.Host.ToString(), 2));
[ResponseCache(NoStore = true)]
public Task<IActionResult> Discover() =>
_mediator.Send(new GetHDHRTunerCount()).Map<int, IActionResult>(
tunerCount => new OkObjectResult(new Discover(Request.Scheme, Request.Host.ToString(), tunerCount)));
[HttpGet("lineup_status.json")]
public IActionResult LineupStatus() =>

71
ErsatzTV/Pages/FFmpeg.razor

@ -2,55 +2,13 @@ @@ -2,55 +2,13 @@
@using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Application.FFmpegProfiles.Commands
@using ErsatzTV.Application.FFmpegProfiles.Queries
@using ErsatzTV.Application.MediaItems.Queries
@using System.Globalization
@using Unit = LanguageExt.Unit
@inject IDialogService Dialog
@inject IMediator Mediator
@inject ILogger<FFmpeg> Logger
@inject ISnackbar Snackbar
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudCard>
<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="@(_ => SaveSettings())">Save Settings</MudButton>
</MudCardActions>
</MudCard>
<MudTable Hover="true" Items="_ffmpegProfiles" Class="mt-8">
<MudTable Hover="true" Items="_ffmpegProfiles">
<ToolBarContent>
<MudText Typo="Typo.h6">FFmpeg Profiles</MudText>
<MudToolBarSpacer></MudToolBarSpacer>
@ -114,34 +72,9 @@ @@ -114,34 +72,9 @@
</MudContainer>
@code {
private bool _success;
private FFmpegSettingsViewModel _ffmpegSettings;
private List<FFmpegProfileViewModel> _ffmpegProfiles;
private List<CultureInfo> _availableCultures;
protected override async Task OnParametersSetAsync()
{
_ffmpegSettings = await Mediator.Send(new GetFFmpegSettings());
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath);
await LoadFFmpegProfilesAsync();
_availableCultures = await Mediator.Send(new GetAllLanguageCodes());
}
private async Task SaveSettings()
{
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 static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null;
protected override async Task OnParametersSetAsync() => await LoadFFmpegProfilesAsync();
private async Task LoadFFmpegProfilesAsync() =>
_ffmpegProfiles = await Mediator.Send(new GetAllFFmpegProfiles());

2
ErsatzTV/Pages/Movie.razor

@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_movie.Poster}")" alt="movie poster"/>
}
<div style="display: flex; flex-direction: column; height: 100%">

5
ErsatzTV/Pages/MovieList.razor

@ -130,7 +130,10 @@ @@ -130,7 +130,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e);
}

5
ErsatzTV/Pages/MusicVideoList.razor

@ -131,7 +131,10 @@ @@ -131,7 +131,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e);
}

120
ErsatzTV/Pages/Settings.razor

@ -0,0 +1,120 @@ @@ -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));
}
}

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
@if (!string.IsNullOrWhiteSpace(_season.Poster))
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_season.Poster}")" alt="show poster"/>
}
<div style="display: flex; flex-direction: column; height: 100%">

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
@if (!string.IsNullOrWhiteSpace(_show.Poster))
{
<img class="mud-elevation-2 mr-6"
style="border-radius: 4px; max-height: 440px; flex-shrink: 0"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"/artwork/posters/{_show.Poster}")" alt="show poster"/>
}
<div style="display: flex; flex-direction: column; height: 100%">

5
ErsatzTV/Pages/TelevisionShowList.razor

@ -130,7 +130,10 @@ @@ -130,7 +130,10 @@
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{
List<MediaCardViewModel> GetSortedItems() => _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
List<MediaCardViewModel> GetSortedItems()
{
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>();
}
SelectClicked(GetSortedItems, card, e);
}

5
ErsatzTV/Shared/FragmentLetterAnchor.razor

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
@using ErsatzTV.Application.MediaCards
@using LanguageExt.UnsafeValueAccess
@using LanguageExt.UnsafeValueAccess
@typeparam TCard
@{ var letters = new System.Collections.Generic.HashSet<char>(); }
@ -25,4 +24,4 @@ @@ -25,4 +24,4 @@
{
@ChildContent
}
}
}

3
ErsatzTV/Shared/MainLayout.razor

@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
<MudDrawer Open="true" Elevation="2" ClipMode="DrawerClipMode.Always">
<MudNavMenu>
<MudNavLink Href="/channels">Channels</MudNavLink>
<MudNavLink Href="/ffmpeg">FFmpeg</MudNavLink>
<MudNavLink Href="/ffmpeg">FFmpeg Profiles</MudNavLink>
<MudNavGroup Title="Media Sources" Expanded="true">
<MudNavLink Href="/media/plex">Plex</MudNavLink>
</MudNavGroup>
@ -52,6 +52,7 @@ @@ -52,6 +52,7 @@
</MudNavGroup>
<MudNavLink Href="/schedules">Schedules</MudNavLink>
<MudNavLink Href="/playouts">Playouts</MudNavLink>
<MudNavLink Href="/settings">Settings</MudNavLink>
<MudNavLink Href="/system/logs">Logs</MudNavLink>
<MudDivider Class="my-6" DividerType="DividerType.Middle"/>
<MudContainer Style="text-align: right" Class="mr-6">

Loading…
Cancel
Save