globalization fixes (#2014)
* fix crashes caused by decimal separator * improvements to playout reset ui * remove code quality workflow
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
name: Qodana
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
|
||||
fetch-depth: 0 # a full history is required for pull request analysis
|
||||
- name: 'Qodana Scan'
|
||||
uses: JetBrains/qodana-action@v2024.1
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
@@ -20,6 +20,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
### Fixed
|
||||
- Fix error message about synchronizing Plex collections from a Plex server that has zero collections
|
||||
- Fix navigation after form submission when using `ETV_BASE_URL` environment variable
|
||||
- Fix UI crashes when channel numbers contain a period `.` in locales that have a different decimal separator (e.g. `,`)
|
||||
- Fix playout detail table to only reload once when resetting a playout
|
||||
- Fix date formatting in playout detail table on reload (will now respect browser's `Accept-Language` header)
|
||||
|
||||
## [25.1.0] - 2025-01-10
|
||||
### Added
|
||||
|
||||
@@ -69,7 +69,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityLocker.LockPlayout(playout.Id);
|
||||
await _entityLocker.LockPlayout(playout.Id);
|
||||
|
||||
switch (playout.ProgramSchedulePlayoutType)
|
||||
{
|
||||
@@ -135,7 +135,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entityLocker.UnlockPlayout(playout.Id);
|
||||
await _entityLocker.UnlockPlayout(playout.Id);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
|
||||
@@ -135,7 +135,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
|
||||
foreach (int playoutId in playoutIdsToCheck)
|
||||
{
|
||||
_entityLocker.LockPlayout(playoutId);
|
||||
await _entityLocker.LockPlayout(playoutId);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Checking playouts {PlayoutIds} for text subtitles to extract", playoutIdsToCheck);
|
||||
@@ -193,7 +193,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
|
||||
|
||||
foreach (int playoutId in playoutIdsToCheck)
|
||||
{
|
||||
_entityLocker.UnlockPlayout(playoutId);
|
||||
await _entityLocker.UnlockPlayout(playoutId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
|
||||
|
||||
@@ -9,7 +9,6 @@ public interface IEntityLocker
|
||||
event EventHandler OnEmbyCollectionsChanged;
|
||||
event EventHandler OnJellyfinCollectionsChanged;
|
||||
event EventHandler OnPlexCollectionsChanged;
|
||||
event EventHandler<int> OnPlayoutChanged;
|
||||
bool LockLibrary(int libraryId);
|
||||
bool UnlockLibrary(int libraryId);
|
||||
bool IsLibraryLocked(int libraryId);
|
||||
@@ -31,7 +30,7 @@ public interface IEntityLocker
|
||||
bool LockPlexCollections();
|
||||
bool UnlockPlexCollections();
|
||||
bool ArePlexCollectionsLocked();
|
||||
bool LockPlayout(int playoutId);
|
||||
bool UnlockPlayout(int playoutId);
|
||||
Task<bool> LockPlayout(int playoutId);
|
||||
Task<bool> UnlockPlayout(int playoutId);
|
||||
bool IsPlayoutLocked(int playoutId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Core.Notifications;
|
||||
|
||||
public record PlayoutUpdatedNotification(int PlayoutId, bool IsLocked) : INotification;
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Locking;
|
||||
|
||||
public class EntityLocker : IEntityLocker
|
||||
public class EntityLocker(IMediator mediator) : IEntityLocker
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, byte> _lockedLibraries = new();
|
||||
private readonly ConcurrentDictionary<int, byte> _lockedPlayouts = new();
|
||||
@@ -21,7 +23,6 @@ public class EntityLocker : IEntityLocker
|
||||
public event EventHandler OnEmbyCollectionsChanged;
|
||||
public event EventHandler OnJellyfinCollectionsChanged;
|
||||
public event EventHandler OnPlexCollectionsChanged;
|
||||
public event EventHandler<int> OnPlayoutChanged;
|
||||
|
||||
public bool LockLibrary(int libraryId)
|
||||
{
|
||||
@@ -208,22 +209,22 @@ public class EntityLocker : IEntityLocker
|
||||
|
||||
public bool ArePlexCollectionsLocked() => _plexCollections;
|
||||
|
||||
public bool LockPlayout(int playoutId)
|
||||
public async Task<bool> LockPlayout(int playoutId)
|
||||
{
|
||||
if (!_lockedPlayouts.ContainsKey(playoutId) && _lockedPlayouts.TryAdd(playoutId, 0))
|
||||
{
|
||||
OnPlayoutChanged?.Invoke(this, playoutId);
|
||||
await mediator.Publish(new PlayoutUpdatedNotification(playoutId, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool UnlockPlayout(int playoutId)
|
||||
public async Task<bool> UnlockPlayout(int playoutId)
|
||||
{
|
||||
if (_lockedPlayouts.TryRemove(playoutId, out byte _))
|
||||
{
|
||||
OnPlayoutChanged?.Invoke(this, playoutId);
|
||||
await mediator.Publish(new PlayoutUpdatedNotification(playoutId, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Ascending" SortBy="new Func<ChannelViewModel, object>(x => decimal.Parse(x.Number))">Number</MudTableSortLabel>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Ascending" SortBy="new Func<ChannelViewModel, object>(x => decimal.Parse(x.Number, CultureInfo.InvariantCulture))">Number</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>Logo</MudTh>
|
||||
<MudTh>
|
||||
@@ -219,7 +219,7 @@
|
||||
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.ChannelsPageSize, state.PageSize.ToString()), _cts.Token);
|
||||
|
||||
List<ChannelViewModel> channels = await Mediator.Send(new GetAllChannels(), _cts.Token);
|
||||
IOrderedEnumerable<ChannelViewModel> sorted = channels.OrderBy(c => decimal.Parse(c.Number));
|
||||
IOrderedEnumerable<ChannelViewModel> sorted = channels.OrderBy(c => decimal.Parse(c.Number, CultureInfo.InvariantCulture));
|
||||
|
||||
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
|
||||
var processedChannels = new List<ChannelViewModel>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/playouts/add"
|
||||
@page "/playouts/add/{kind}"
|
||||
@using System.Globalization
|
||||
@using ErsatzTV.Application.Channels
|
||||
@using ErsatzTV.Application.ProgramSchedules
|
||||
@implements IDisposable
|
||||
@@ -99,7 +100,7 @@
|
||||
_model.Kind = Kind;
|
||||
|
||||
_channels = await Mediator.Send(new GetAllChannels(), _cts.Token)
|
||||
.Map(list => list.OrderBy(vm => decimal.Parse(vm.Number)).ToList());
|
||||
.Map(list => list.OrderBy(vm => decimal.Parse(vm.Number, CultureInfo.InvariantCulture)).ToList());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Kind))
|
||||
{
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
@page "/playouts"
|
||||
@page "/playouts"
|
||||
@using System.Globalization
|
||||
@using ErsatzTV.Application.Configuration
|
||||
@using ErsatzTV.Application.Playouts
|
||||
@using ErsatzTV.Core.Scheduling
|
||||
@using ErsatzTV.Core.Notifications
|
||||
@using MediatR.Courier
|
||||
@implements IDisposable
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> WorkerChannel
|
||||
@inject IEntityLocker EntityLocker;
|
||||
@inject ICourier Courier;
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div>
|
||||
@@ -50,7 +54,7 @@
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<PlayoutViewModel, object>(x => decimal.Parse(x.Channel.Number))">
|
||||
<MudTableSortLabel SortBy="new Func<PlayoutViewModel, object>(x => decimal.Parse(x.Channel.Number, CultureInfo.InvariantCulture))">
|
||||
Channel
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
@@ -199,8 +203,8 @@
|
||||
<MudTh>Duration</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start">@context.Start.ToString("G")</MudTd>
|
||||
<MudTd DataLabel="Finish">@context.Finish.ToString("G")</MudTd>
|
||||
<MudTd DataLabel="Start">@context.Start.ToString("G", _dtf)</MudTd>
|
||||
<MudTd DataLabel="Finish">@context.Finish.ToString("G", _dtf)</MudTd>
|
||||
<MudTd DataLabel="Media Item">@context.Title</MudTd>
|
||||
<MudTd DataLabel="Duration">@context.Duration</MudTd>
|
||||
</RowTemplate>
|
||||
@@ -214,6 +218,8 @@
|
||||
@code {
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
private readonly DateTimeFormatInfo _dtf = CultureInfo.CurrentUICulture.DateTimeFormat;
|
||||
|
||||
private MudTable<PlayoutNameViewModel> _table;
|
||||
private MudTable<PlayoutItemViewModel> _detailTable;
|
||||
private int _rowsPerPage = 10;
|
||||
@@ -237,15 +243,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Courier.Subscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EntityLocker.OnPlayoutChanged -= ReloadDetailsIfNeeded;
|
||||
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
}
|
||||
|
||||
protected override void OnInitialized() => EntityLocker.OnPlayoutChanged += ReloadDetailsIfNeeded;
|
||||
public async Task HandlePlayoutUpdated(PlayoutUpdatedNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
// only refresh detail table on unlock operations (after playout has been modified)
|
||||
if (notification.IsLocked == false)
|
||||
{
|
||||
if (notification.PlayoutId == _selectedPlayoutId && _detailTable is not null)
|
||||
{
|
||||
await InvokeAsync(() => _detailTable.ReloadServerData());
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -320,15 +341,6 @@
|
||||
private async Task ResetPlayout(PlayoutNameViewModel playout)
|
||||
{
|
||||
await WorkerChannel.WriteAsync(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token);
|
||||
if (_table != null)
|
||||
{
|
||||
await _table.ReloadServerData();
|
||||
}
|
||||
|
||||
if (_selectedPlayoutId == playout.PlayoutId)
|
||||
{
|
||||
await PlayoutSelected(playout);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScheduleReset(PlayoutNameViewModel playout)
|
||||
@@ -356,7 +368,7 @@
|
||||
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsPageSize, state.PageSize.ToString()), _cts.Token);
|
||||
|
||||
List<PlayoutNameViewModel> playouts = await Mediator.Send(new GetAllPlayouts(), _cts.Token);
|
||||
IOrderedEnumerable<PlayoutNameViewModel> sorted = playouts.OrderBy(p => decimal.Parse(p.ChannelNumber));
|
||||
IOrderedEnumerable<PlayoutNameViewModel> sorted = playouts.OrderBy(p => decimal.Parse(p.ChannelNumber, CultureInfo.InvariantCulture));
|
||||
|
||||
// TODO: properly page this data
|
||||
return new TableData<PlayoutNameViewModel>
|
||||
@@ -366,16 +378,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
private async void ReloadDetailsIfNeeded(object sender, int playoutId)
|
||||
{
|
||||
if (playoutId == _selectedPlayoutId && _detailTable is not null)
|
||||
{
|
||||
await InvokeAsync(() => _detailTable.ReloadServerData());
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task<TableData<PlayoutItemViewModel>> DetailServerReload(TableState state, CancellationToken cancellationToken)
|
||||
{
|
||||
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PlayoutsDetailPageSize, state.PageSize.ToString()), _cts.Token);
|
||||
|
||||
Reference in New Issue
Block a user