Files
ersatztv/ErsatzTV/Pages/Libraries.razor
T

232 lines
9.3 KiB
Plaintext

@page "/media/libraries"
@using ErsatzTV.Application.Libraries
@using ErsatzTV.Application.MediaSources
@using ErsatzTV.Application.Plex
@using ErsatzTV.Core.Metadata
@using MediatR.Courier
@using PlexLibraryViewModel = ErsatzTV.Application.Libraries.PlexLibraryViewModel
@using ErsatzTV.Application.Jellyfin
@using ErsatzTV.Application.Emby
@implements IDisposable
@inject IMediator _mediator
@inject IEntityLocker _locker
@inject ChannelWriter<IScannerBackgroundServiceRequest> _scannerWorkerChannel;
@inject ICourier _courier
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudTable Hover="true" Items="_libraries" Dense="true">
<ToolBarContent>
<MudText Typo="Typo.h6">Libraries</MudText>
</ToolBarContent>
<ColGroup>
<col/>
<col/>
<col/>
<col style="width: 180px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Library Kind</MudTh>
<MudTh>Name</MudTh>
<MudTh>Media Kind</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Library Kind">@context.LibraryKind</MudTd>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Media Kind">@context.MediaKind</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
@if (_locker.IsLibraryLocked(context.Id))
{
<div style="width: 48px">
@if (_progressByLibrary[context.Id] > 0)
{
<MudText Color="Color.Primary">
@($"{_progressByLibrary[context.Id]} %")
</MudText>
}
</div>
<div style="align-items: center; display: flex; height: 48px; justify-content: center; width: 48px;">
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
</div>
}
else
{
if (context is PlexLibraryViewModel or EmbyLibraryViewModel or JellyfinLibraryViewModel)
{
<MudTooltip Text="Deep Scan Library">
<MudIconButton Icon="@Icons.Material.Filled.FindReplace"
Disabled="@_locker.IsLibraryLocked(context.Id)"
OnClick="@(_ => ScanLibrary(context, true))">
</MudIconButton>
</MudTooltip>
}
else
{
<div style="width: 48px"></div>
}
<MudTooltip Text="Scan Library">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Disabled="@_locker.IsLibraryLocked(context.Id)"
OnClick="@(_ => ScanLibrary(context))">
</MudIconButton>
</MudTooltip>
}
<MudTooltip Text="Search Library">
<MudIconButton Icon="@Icons.Material.Filled.Search"
Link="@($"search?query=library_id%3a{context.Id}")">
</MudIconButton>
</MudTooltip>
</div>
</MudTd>
</RowTemplate>
</MudTable>
</MudContainer>
@if (_externalCollections.Any())
{
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudTable Hover="true" Items="_externalCollections" Dense="true">
<ToolBarContent>
<MudText Typo="Typo.h6">External Collections</MudText>
</ToolBarContent>
<ColGroup>
<col/>
<col style="width: 180px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Library Kind</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Library Kind">@context.LibraryKind</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
<div style="width: 48px"></div>
@if (AreCollectionsLocked(context.LibraryKind))
{
<div style="align-items: center; display: flex; height: 48px; justify-content: center; width: 48px;">
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
</div>
}
else
{
<div style="width: 48px"></div>
<MudTooltip Text="Scan Collections">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Disabled="@AreCollectionsLocked(context.LibraryKind)"
OnClick="@(_ => ScanExternalCollections(context))">
</MudIconButton>
</MudTooltip>
}
<div style="width: 48px"></div>
</div>
</MudTd>
</RowTemplate>
</MudTable>
</MudContainer>
}
@code {
private readonly CancellationTokenSource _cts = new();
private IList<LibraryViewModel> _libraries;
private IList<LibraryViewModel> _externalCollections;
private Dictionary<int, int> _progressByLibrary;
protected override void OnInitialized()
{
_locker.OnLibraryChanged += LockChanged;
_locker.OnEmbyCollectionsChanged += LockChanged;
_courier.Subscribe<LibraryScanProgress>(HandleScanProgress);
}
protected override async Task OnParametersSetAsync() => await LoadLibraries(_cts.Token);
private async Task LoadLibraries(CancellationToken cancellationToken)
{
_libraries = await _mediator.Send(new GetConfiguredLibraries(), cancellationToken);
_externalCollections = await _mediator.Send(new GetExternalCollections(), cancellationToken);
_progressByLibrary = _libraries.ToDictionary(vm => vm.Id, _ => 0);
}
private async Task ScanLibrary(LibraryViewModel library, bool deepScan = false)
{
if (_locker.LockLibrary(library.Id))
{
switch (library)
{
case LocalLibraryViewModel:
await _scannerWorkerChannel.WriteAsync(new ForceScanLocalLibrary(library.Id), _cts.Token);
break;
case PlexLibraryViewModel:
await _scannerWorkerChannel.WriteAsync(new ForceSynchronizePlexLibraryById(library.Id, deepScan), _cts.Token);
break;
case JellyfinLibraryViewModel:
await _scannerWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(library.MediaSourceId), _cts.Token);
await _scannerWorkerChannel.WriteAsync(new ForceSynchronizeJellyfinLibraryById(library.Id, deepScan), _cts.Token);
break;
case EmbyLibraryViewModel:
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(library.MediaSourceId), _cts.Token);
await _scannerWorkerChannel.WriteAsync(new ForceSynchronizeEmbyLibraryById(library.Id, deepScan), _cts.Token);
break;
}
StateHasChanged();
}
}
private async Task ScanExternalCollections(LibraryViewModel library)
{
switch (library.LibraryKind.ToLowerInvariant())
{
case "emby":
if (_locker.LockEmbyCollections())
{
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyCollections(library.MediaSourceId, true));
}
break;
}
}
private void LockChanged(object sender, EventArgs e) =>
InvokeAsync(StateHasChanged);
private bool AreCollectionsLocked(string libraryKind)
{
switch (libraryKind.ToLowerInvariant())
{
case "emby":
return _locker.AreEmbyCollectionsLocked();
}
return false;
}
private async Task HandleScanProgress(LibraryScanProgress libraryScanProgress, CancellationToken cancellationToken)
{
try
{
if (_progressByLibrary != null && _progressByLibrary.ContainsKey(libraryScanProgress.LibraryId))
{
_progressByLibrary[libraryScanProgress.LibraryId] = (int)(libraryScanProgress.Progress * 100);
await InvokeAsync(StateHasChanged);
}
}
catch (Exception)
{
// ignore
}
}
void IDisposable.Dispose()
{
_locker.OnLibraryChanged -= LockChanged;
_locker.OnEmbyCollectionsChanged -= LockChanged;
_courier.UnSubscribe<LibraryScanProgress>(HandleScanProgress);
_cts.Cancel();
_cts.Dispose();
}
}