add plex network metadata (#2085)

* initial plumbing

* scan for plex networks

* save network contents to db as tags

* eliminate network tag id churn

* add network and show_network to search index

* update last networks scan

* show networks on tv show page

* update changelog
This commit is contained in:
Jason Dove
2025-06-28 12:49:26 +00:00
committed by GitHub
parent 583cbf7b14
commit 0f795e4e2f
42 changed files with 24134 additions and 33 deletions
+1
View File
@@ -180,6 +180,7 @@
case PlexLibraryViewModel:
await ScannerWorkerChannel.WriteAsync(new SynchronizePlexLibraries(library.MediaSourceId), _cts.Token);
await ScannerWorkerChannel.WriteAsync(new ForceSynchronizePlexLibraryById(library.Id, deepScan), _cts.Token);
await ScannerWorkerChannel.WriteAsync(new SynchronizePlexNetworks(library.Id, true), _cts.Token);
break;
case JellyfinLibraryViewModel:
await ScannerWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(library.MediaSourceId), _cts.Token);
+29 -16
View File
@@ -116,6 +116,18 @@
}
</div>
}
@if (_sortedNetworks.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Networks:&nbsp;</MudText>
<MudLink Href="@(@$"network:""{_sortedNetworks.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedNetworks.Head()</MudLink>
@foreach (string network in _sortedNetworks.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@(@$"network:""{network.ToLowerInvariant()}""".GetRelativeSearchQuery())">@network</MudLink>
}
</div>
}
@if (_sortedGenres.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
@@ -176,11 +188,12 @@
public int ShowId { get; set; }
private TelevisionShowViewModel _show;
private List<string> _sortedContentRatings = new();
private List<CultureInfo> _sortedLanguages = new();
private List<string> _sortedStudios = new();
private List<string> _sortedGenres = new();
private List<string> _sortedTags = new();
private List<string> _sortedContentRatings = [];
private List<CultureInfo> _sortedLanguages = [];
private List<string> _sortedStudios = [];
private List<string> _sortedNetworks = [];
private List<string> _sortedGenres = [];
private List<string> _sortedTags = [];
private int _pageSize => 100;
private readonly int _pageNumber = 1;
@@ -197,17 +210,17 @@
private async Task RefreshData()
{
await Mediator.Send(new GetTelevisionShowById(ShowId), _cts.Token)
.IfSomeAsync(
vm =>
{
_show = vm;
_sortedContentRatings = _show.ContentRatings.OrderBy(cr => cr).ToList();
_sortedLanguages = _show.Languages.OrderBy(ci => ci.EnglishName).ToList();
_sortedStudios = _show.Studios.OrderBy(s => s).ToList();
_sortedGenres = _show.Genres.OrderBy(g => g).ToList();
_sortedTags = _show.Tags.OrderBy(t => t).ToList();
});
Option<TelevisionShowViewModel> maybeShow = await Mediator.Send(new GetTelevisionShowById(ShowId), _cts.Token);
foreach (TelevisionShowViewModel show in maybeShow)
{
_show = show;
_sortedContentRatings = _show.ContentRatings.OrderBy(cr => cr).ToList();
_sortedLanguages = _show.Languages.OrderBy(ci => ci.EnglishName).ToList();
_sortedStudios = _show.Studios.OrderBy(s => s).ToList();
_sortedGenres = _show.Genres.OrderBy(g => g).ToList();
_sortedTags = _show.Tags.OrderBy(t => t).ToList();
_sortedNetworks = _show.Networks.OrderBy(n => n).ToList();
}
_data = await Mediator.Send(new GetTelevisionSeasonCards(ShowId, _pageNumber, _pageSize), _cts.Token);
}
+37
View File
@@ -57,6 +57,9 @@ public class ScannerService : BackgroundService
case SynchronizePlexCollections synchronizePlexCollections:
requestTask = SynchronizePlexCollections(synchronizePlexCollections, stoppingToken);
break;
case SynchronizePlexNetworks synchronizePlexNetworks:
requestTask = SynchronizePlexNetworks(synchronizePlexNetworks, stoppingToken);
break;
case SynchronizeJellyfinLibraries synchronizeJellyfinLibraries:
requestTask = SynchronizeLibraries(synchronizeJellyfinLibraries, stoppingToken);
break;
@@ -220,6 +223,40 @@ public class ScannerService : BackgroundService
}
}
private async Task SynchronizePlexNetworks(
SynchronizePlexNetworks request,
CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
IEntityLocker entityLocker = scope.ServiceProvider.GetRequiredService<IEntityLocker>();
Either<BaseError, Unit> result = await mediator.Send(request, cancellationToken);
result.BiIter(
_ => _logger.LogDebug("Done synchronizing plex networks for library {LibraryId}", request.PlexLibraryId),
error =>
{
if (error is ScanIsNotRequired)
{
_logger.LogDebug(
"Scan is not required for plex networks in library {LibraryId} at this time",
request.PlexLibraryId);
}
else
{
_logger.LogWarning(
"Unable to synchronize plex networks for library {LibraryId}: {Error}",
request.PlexLibraryId,
error.Value);
}
});
if (entityLocker.IsLibraryLocked(request.PlexLibraryId))
{
entityLocker.UnlockLibrary(request.PlexLibraryId);
}
}
private async Task SynchronizeLibraries(SynchronizeJellyfinLibraries request, CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
+7
View File
@@ -247,6 +247,13 @@ public class SchedulerService : BackgroundService
await _scannerWorkerChannel.WriteAsync(
new SynchronizePlexLibraryByIdIfNeeded(library.Id),
cancellationToken);
if (library.MediaKind is LibraryMediaKind.Shows)
{
await _scannerWorkerChannel.WriteAsync(
new SynchronizePlexNetworks(library.Id, false),
cancellationToken);
}
}
}
+1 -1
View File
@@ -480,7 +480,7 @@ public class Startup
app.UseCors("AllowAll");
app.UseForwardedHeaders();
// app.UseHttpLogging();
//app.UseHttpLogging();
app.UseSerilogRequestLogging(
options =>
{