272 lines
11 KiB
Plaintext
272 lines
11 KiB
Plaintext
@page "/media/libraries"
|
|
@using ErsatzTV.Application.Emby
|
|
@using ErsatzTV.Application.Jellyfin
|
|
@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
|
|
@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/>
|
|
@if (_showServerNames)
|
|
{
|
|
<col/>
|
|
}
|
|
<col/>
|
|
<col/>
|
|
<col style="width: 180px;"/>
|
|
</ColGroup>
|
|
<HeaderContent>
|
|
<MudTh>Library Kind</MudTh>
|
|
@if (_showServerNames)
|
|
{
|
|
<MudTh>Server Name</MudTh>
|
|
}
|
|
<MudTh>Library Name</MudTh>
|
|
<MudTh>Media Kind</MudTh>
|
|
<MudTh/>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Library Kind">@context.LibraryKind</MudTd>
|
|
@if (_showServerNames)
|
|
{
|
|
<MudTd DataLabel="Server Name">@context.MediaSourceName</MudTd>
|
|
}
|
|
<MudTd DataLabel="Library 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"
|
|
Href="@($"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 = new List<LibraryViewModel>();
|
|
private IList<LibraryViewModel> _externalCollections = new List<LibraryViewModel>();
|
|
private Dictionary<int, int> _progressByLibrary = new();
|
|
private bool _showServerNames;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
Locker.OnLibraryChanged += LockChanged;
|
|
Locker.OnEmbyCollectionsChanged += LockChanged;
|
|
Locker.OnJellyfinCollectionsChanged += LockChanged;
|
|
Locker.OnPlexCollectionsChanged += 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);
|
|
_showServerNames = _libraries.Any(l => l is PlexLibraryViewModel);
|
|
|
|
_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 SynchronizePlexLibraries(library.MediaSourceId), _cts.Token);
|
|
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;
|
|
case "jellyfin":
|
|
if (Locker.LockJellyfinCollections())
|
|
{
|
|
await ScannerWorkerChannel.WriteAsync(new SynchronizeJellyfinCollections(library.MediaSourceId, true));
|
|
}
|
|
|
|
break;
|
|
case "plex":
|
|
if (Locker.LockPlexCollections())
|
|
{
|
|
await ScannerWorkerChannel.WriteAsync(new SynchronizePlexCollections(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();
|
|
case "jellyfin":
|
|
return Locker.AreJellyfinCollectionsLocked();
|
|
case "plex":
|
|
return Locker.ArePlexCollectionsLocked();
|
|
}
|
|
|
|
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;
|
|
Locker.OnJellyfinCollectionsChanged -= LockChanged;
|
|
Locker.OnPlexCollectionsChanged -= LockChanged;
|
|
Courier.UnSubscribe<LibraryScanProgress>(HandleScanProgress);
|
|
|
|
_cts.Cancel();
|
|
_cts.Dispose();
|
|
}
|
|
|
|
} |