Browse Source

remove jellyfin admin user id requirement (#2025)

pull/2027/head
Jason Dove 1 year ago committed by GitHub
parent
commit
da1cfab5f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs
  3. 6
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserId.cs
  4. 107
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs
  5. 3
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs
  6. 1
      ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs
  7. 14
      ErsatzTV.Infrastructure/Jellyfin/IJellyfinApi.cs
  8. 82
      ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs
  9. 5
      ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserId.cs
  10. 103
      ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs
  11. 11
      ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs
  12. 11
      ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs
  13. 21
      ErsatzTV/Services/ScannerService.cs

1
CHANGELOG.md

@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- When the connection is no longer cached, a ping will be sent to the last used address for Plex (the last address that had a successful ping) - When the connection is no longer cached, a ping will be sent to the last used address for Plex (the last address that had a successful ping)
- If the ping is successful, the address will be cached for another 30 seconds - If the ping is successful, the address will be cached for another 30 seconds
- If the ping is not successful, all addresses will be checked again, and the first address to return a successful ping will be cached for 30 seconds - If the ping is not successful, all addresses will be checked again, and the first address to return a successful ping will be cached for 30 seconds
- Remove requirement to have Jellyfin admin user; user id is no longer required on requests to latest Jellyfin server
### Fixed ### Fixed
- Fix error message about synchronizing Plex collections from a Plex server that has zero collections - Fix error message about synchronizing Plex collections from a Plex server that has zero collections

1
ErsatzTV.Application/Emby/Commands/SynchronizeEmbyMediaSourcesHandler.cs

@ -26,7 +26,6 @@ public class SynchronizeEmbyMediaSourcesHandler : IRequestHandler<SynchronizeEmb
List<EmbyMediaSource> mediaSources = await _mediaSourceRepository.GetAllEmby(); List<EmbyMediaSource> mediaSources = await _mediaSourceRepository.GetAllEmby();
foreach (EmbyMediaSource mediaSource in mediaSources) foreach (EmbyMediaSource mediaSource in mediaSources)
{ {
// await _channel.WriteAsync(new SynchronizeEmbyAdminUserId(mediaSource.Id), cancellationToken);
await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(mediaSource.Id), cancellationToken); await _scannerWorkerChannel.WriteAsync(new SynchronizeEmbyLibraries(mediaSource.Id), cancellationToken);
} }

6
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserId.cs

@ -1,6 +0,0 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Jellyfin;
public record SynchronizeJellyfinAdminUserId(int JellyfinMediaSourceId) : IRequest<Either<BaseError, Unit>>,
IScannerBackgroundServiceRequest;

107
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs

@ -1,107 +0,0 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Jellyfin;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Jellyfin;
public class
SynchronizeJellyfinAdminUserIdHandler : IRequestHandler<SynchronizeJellyfinAdminUserId,
Either<BaseError, Unit>>
{
private readonly IJellyfinApiClient _jellyfinApiClient;
private readonly IJellyfinSecretStore _jellyfinSecretStore;
private readonly ILogger<SynchronizeJellyfinAdminUserIdHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMemoryCache _memoryCache;
public SynchronizeJellyfinAdminUserIdHandler(
IMemoryCache memoryCache,
IMediaSourceRepository mediaSourceRepository,
IJellyfinSecretStore jellyfinSecretStore,
IJellyfinApiClient jellyfinApiClient,
ILogger<SynchronizeJellyfinAdminUserIdHandler> logger)
{
_memoryCache = memoryCache;
_mediaSourceRepository = mediaSourceRepository;
_jellyfinSecretStore = jellyfinSecretStore;
_jellyfinApiClient = jellyfinApiClient;
_logger = logger;
}
public Task<Either<BaseError, Unit>> Handle(
SynchronizeJellyfinAdminUserId request,
CancellationToken cancellationToken) =>
Validate(request)
.Map(v => v.ToEither<ConnectionParameters>())
.BindT(PerformSync);
private async Task<Either<BaseError, Unit>> PerformSync(ConnectionParameters parameters)
{
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{parameters.JellyfinMediaSource.Id}", out string _))
{
return Unit.Default;
}
Either<BaseError, string> maybeUserId = await _jellyfinApiClient.GetAdminUserId(
parameters.ActiveConnection.Address,
parameters.ApiKey);
return await maybeUserId.Match(
userId =>
{
// _logger.LogDebug("Jellyfin admin user id is {UserId}", userId);
_memoryCache.Set($"jellyfin_admin_user_id.{parameters.JellyfinMediaSource.Id}", userId);
return Task.FromResult<Either<BaseError, Unit>>(Unit.Default);
},
async error =>
{
// clear api key if unable to sync with jellyfin
if (error.Value.Contains("Unauthorized"))
{
await _jellyfinSecretStore.SaveSecrets(
new JellyfinSecrets { Address = parameters.ActiveConnection.Address, ApiKey = null });
}
return Left<BaseError, Unit>(error);
});
}
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizeJellyfinAdminUserId request) =>
MediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, JellyfinMediaSource>> MediaSourceMustExist(
SynchronizeJellyfinAdminUserId request) =>
_mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId)
.Map(o => o.ToValidation<BaseError>("Jellyfin media source does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
JellyfinMediaSource jellyfinMediaSource)
{
Option<JellyfinConnection> maybeConnection = jellyfinMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(jellyfinMediaSource, connection))
.ToValidation<BaseError>("Jellyfin media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
JellyfinSecrets secrets = await _jellyfinSecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Jellyfin media source requires an api key");
}
private sealed record ConnectionParameters(
JellyfinMediaSource JellyfinMediaSource,
JellyfinConnection ActiveConnection)
{
public string ApiKey { get; set; }
}
}

3
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinMediaSourcesHandler.cs

@ -26,9 +26,6 @@ public class SynchronizeJellyfinMediaSourcesHandler : IRequestHandler<Synchroniz
List<JellyfinMediaSource> mediaSources = await _mediaSourceRepository.GetAllJellyfin(); List<JellyfinMediaSource> mediaSources = await _mediaSourceRepository.GetAllJellyfin();
foreach (JellyfinMediaSource mediaSource in mediaSources) foreach (JellyfinMediaSource mediaSource in mediaSources)
{ {
await _scannerWorkerChannel.WriteAsync(
new SynchronizeJellyfinAdminUserId(mediaSource.Id),
cancellationToken);
await _scannerWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(mediaSource.Id), cancellationToken); await _scannerWorkerChannel.WriteAsync(new SynchronizeJellyfinLibraries(mediaSource.Id), cancellationToken);
} }

1
ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs

@ -7,7 +7,6 @@ public interface IJellyfinApiClient
{ {
Task<Either<BaseError, JellyfinServerInformation>> GetServerInformation(string address, string apiKey); Task<Either<BaseError, JellyfinServerInformation>> GetServerInformation(string address, string apiKey);
Task<Either<BaseError, List<JellyfinLibrary>>> GetLibraries(string address, string apiKey); Task<Either<BaseError, List<JellyfinLibrary>>> GetLibraries(string address, string apiKey);
Task<Either<BaseError, string>> GetAdminUserId(string address, string apiKey);
IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(string address, string apiKey, JellyfinLibrary library); IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(string address, string apiKey, JellyfinLibrary library);

14
ErsatzTV.Infrastructure/Jellyfin/IJellyfinApi.cs

@ -27,8 +27,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = string fields =
@ -49,8 +47,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = string fields =
@ -69,8 +65,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = "Path,DateCreated,Etag,Taglines,ProviderIds", string fields = "Path,DateCreated,Etag,Taglines,ProviderIds",
@ -88,8 +82,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,ProviderIds,People,Chapters", string fields = "Path,Genres,Tags,DateCreated,Etag,Overview,ProviderIds,People,Chapters",
@ -107,8 +99,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = "Etag", string fields = "Etag",
@ -126,8 +116,6 @@ public interface IJellyfinApi
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query] [Query]
string userId,
[Query]
string parentId, string parentId,
[Query] [Query]
string fields = "Etag", string fields = "Etag",
@ -144,7 +132,5 @@ public interface IJellyfinApi
public Task<JellyfinPlaybackInfoResponse> GetPlaybackInfo( public Task<JellyfinPlaybackInfoResponse> GetPlaybackInfo(
[Header("X-Emby-Token")] [Header("X-Emby-Token")]
string apiKey, string apiKey,
[Query]
string userId,
string itemId); string itemId);
} }

82
ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs

@ -73,26 +73,6 @@ public class JellyfinApiClient : IJellyfinApiClient
} }
} }
public async Task<Either<BaseError, string>> GetAdminUserId(string address, string apiKey)
{
try
{
IJellyfinApi service = RestService.For<IJellyfinApi>(address);
List<JellyfinUserResponse> users = await service.GetUsers(apiKey);
Option<string> maybeUserId = users
.Filter(user => user.Policy.IsAdministrator && !user.Policy.IsDisabled && user.Policy.EnableAllFolders)
.Map(user => user.Id)
.HeadOrNone();
return maybeUserId.ToEither(BaseError.New("Unable to locate jellyfin admin user"));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting jellyfin admin user id");
return BaseError.New(ex.Message);
}
}
public IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems( public IAsyncEnumerable<Tuple<JellyfinMovie, int>> GetMovieLibraryItems(
string address, string address,
string apiKey, string apiKey,
@ -102,9 +82,8 @@ public class JellyfinApiClient : IJellyfinApiClient
library, library,
library.MediaSourceId, library.MediaSourceId,
library.ItemId, library.ItemId,
(service, userId, itemId, skip, pageSize) => service.GetMovieLibraryItems( (service, itemId, skip, pageSize) => service.GetMovieLibraryItems(
apiKey, apiKey,
userId,
itemId, itemId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -119,9 +98,8 @@ public class JellyfinApiClient : IJellyfinApiClient
library, library,
library.MediaSourceId, library.MediaSourceId,
library.ItemId, library.ItemId,
(service, userId, itemId, skip, pageSize) => service.GetShowLibraryItems( (service, itemId, skip, pageSize) => service.GetShowLibraryItems(
apiKey, apiKey,
userId,
itemId, itemId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -137,9 +115,8 @@ public class JellyfinApiClient : IJellyfinApiClient
library, library,
library.MediaSourceId, library.MediaSourceId,
showId, showId,
(service, userId, _, skip, pageSize) => service.GetSeasonLibraryItems( (service, _, skip, pageSize) => service.GetSeasonLibraryItems(
apiKey, apiKey,
userId,
showId, showId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -155,9 +132,8 @@ public class JellyfinApiClient : IJellyfinApiClient
library, library,
library.MediaSourceId, library.MediaSourceId,
seasonId, seasonId,
(service, userId, _, skip, pageSize) => service.GetEpisodeLibraryItems( (service, _, skip, pageSize) => service.GetEpisodeLibraryItems(
apiKey, apiKey,
userId,
seasonId, seasonId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -177,9 +153,8 @@ public class JellyfinApiClient : IJellyfinApiClient
None, None,
mediaSourceId, mediaSourceId,
itemId, itemId,
(service, userId, _, skip, pageSize) => service.GetCollectionLibraryItems( (service, _, skip, pageSize) => service.GetCollectionLibraryItems(
apiKey, apiKey,
userId,
itemId, itemId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -199,9 +174,8 @@ public class JellyfinApiClient : IJellyfinApiClient
None, None,
mediaSourceId, mediaSourceId,
collectionId, collectionId,
(service, userId, _, skip, pageSize) => service.GetCollectionItems( (service, _, skip, pageSize) => service.GetCollectionItems(
apiKey, apiKey,
userId,
collectionId, collectionId,
startIndex: skip, startIndex: skip,
limit: pageSize), limit: pageSize),
@ -215,15 +189,10 @@ public class JellyfinApiClient : IJellyfinApiClient
{ {
try try
{ {
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{library.MediaSourceId}", out string userId)) IJellyfinApi service = RestService.For<IJellyfinApi>(address);
{ JellyfinPlaybackInfoResponse playbackInfo = await service.GetPlaybackInfo(apiKey, itemId);
IJellyfinApi service = RestService.For<IJellyfinApi>(address); Option<MediaVersion> maybeVersion = ProjectToMediaVersion(playbackInfo);
JellyfinPlaybackInfoResponse playbackInfo = await service.GetPlaybackInfo(apiKey, userId, itemId); return maybeVersion.ToEither(() => BaseError.New("Unable to locate Jellyfin statistics"));
Option<MediaVersion> maybeVersion = ProjectToMediaVersion(playbackInfo);
return maybeVersion.ToEither(() => BaseError.New("Unable to locate Jellyfin statistics"));
}
return BaseError.New("Jellyfin admin user id is not available");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -232,34 +201,31 @@ public class JellyfinApiClient : IJellyfinApiClient
} }
} }
private async IAsyncEnumerable<Tuple<TItem, int>> GetPagedLibraryItems<TItem>( private static async IAsyncEnumerable<Tuple<TItem, int>> GetPagedLibraryItems<TItem>(
string address, string address,
Option<JellyfinLibrary> maybeLibrary, Option<JellyfinLibrary> maybeLibrary,
int mediaSourceId, int mediaSourceId,
string parentId, string parentId,
Func<IJellyfinApi, string, string, int, int, Task<JellyfinLibraryItemsResponse>> getItems, Func<IJellyfinApi, string, int, int, Task<JellyfinLibraryItemsResponse>> getItems,
Func<Option<JellyfinLibrary>, JellyfinLibraryItemResponse, Option<TItem>> mapper) Func<Option<JellyfinLibrary>, JellyfinLibraryItemResponse, Option<TItem>> mapper)
{ {
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{mediaSourceId}", out string userId)) IJellyfinApi service = RestService.For<IJellyfinApi>(address);
{
IJellyfinApi service = RestService.For<IJellyfinApi>(address);
const int PAGE_SIZE = 10; const int PAGE_SIZE = 10;
int pages = int.MaxValue; int pages = int.MaxValue;
for (var i = 0; i < pages; i++) for (var i = 0; i < pages; i++)
{ {
int skip = i * PAGE_SIZE; int skip = i * PAGE_SIZE;
JellyfinLibraryItemsResponse result = await getItems(service, userId, parentId, skip, PAGE_SIZE); JellyfinLibraryItemsResponse result = await getItems(service, parentId, skip, PAGE_SIZE);
// update page count // update page count
pages = Math.Min(pages, (result.TotalRecordCount - 1) / PAGE_SIZE + 1); pages = Math.Min(pages, (result.TotalRecordCount - 1) / PAGE_SIZE + 1);
foreach (TItem item in result.Items.Map(item => mapper(maybeLibrary, item)).Somes()) foreach (TItem item in result.Items.Map(item => mapper(maybeLibrary, item)).Somes())
{ {
yield return new Tuple<TItem, int>(item, result.TotalRecordCount); yield return new Tuple<TItem, int>(item, result.TotalRecordCount);
}
} }
} }
} }

5
ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserId.cs

@ -1,5 +0,0 @@
using ErsatzTV.Core;
namespace ErsatzTV.Scanner.Application.Jellyfin;
public record SynchronizeJellyfinAdminUserId(int JellyfinMediaSourceId) : IRequest<Either<BaseError, Unit>>;

103
ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinAdminUserIdHandler.cs

@ -1,103 +0,0 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Jellyfin;
using Microsoft.Extensions.Caching.Memory;
namespace ErsatzTV.Scanner.Application.Jellyfin;
public class
SynchronizeJellyfinAdminUserIdHandler : IRequestHandler<SynchronizeJellyfinAdminUserId,
Either<BaseError, Unit>>
{
private readonly IJellyfinApiClient _jellyfinApiClient;
private readonly IJellyfinSecretStore _jellyfinSecretStore;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMemoryCache _memoryCache;
public SynchronizeJellyfinAdminUserIdHandler(
IMemoryCache memoryCache,
IMediaSourceRepository mediaSourceRepository,
IJellyfinSecretStore jellyfinSecretStore,
IJellyfinApiClient jellyfinApiClient)
{
_memoryCache = memoryCache;
_mediaSourceRepository = mediaSourceRepository;
_jellyfinSecretStore = jellyfinSecretStore;
_jellyfinApiClient = jellyfinApiClient;
}
public Task<Either<BaseError, Unit>> Handle(
SynchronizeJellyfinAdminUserId request,
CancellationToken cancellationToken) =>
Validate(request)
.Map(v => v.ToEither<ConnectionParameters>())
.BindT(PerformSync);
private async Task<Either<BaseError, Unit>> PerformSync(ConnectionParameters parameters)
{
if (_memoryCache.TryGetValue($"jellyfin_admin_user_id.{parameters.JellyfinMediaSource.Id}", out string? _))
{
return Unit.Default;
}
Either<BaseError, string> maybeUserId = await _jellyfinApiClient.GetAdminUserId(
parameters.ActiveConnection.Address,
parameters.ApiKey);
return await maybeUserId.Match(
userId =>
{
// _logger.LogDebug("Jellyfin admin user id is {UserId}", userId);
_memoryCache.Set($"jellyfin_admin_user_id.{parameters.JellyfinMediaSource.Id}", userId);
return Task.FromResult<Either<BaseError, Unit>>(Unit.Default);
},
async error =>
{
// clear api key if unable to sync with jellyfin
if (error.Value.Contains("Unauthorized"))
{
await _jellyfinSecretStore.SaveSecrets(
new JellyfinSecrets { Address = parameters.ActiveConnection.Address, ApiKey = null });
}
return Left<BaseError, Unit>(error);
});
}
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizeJellyfinAdminUserId request) =>
MediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, JellyfinMediaSource>> MediaSourceMustExist(
SynchronizeJellyfinAdminUserId request) =>
_mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId)
.Map(o => o.ToValidation<BaseError>("Jellyfin media source does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
JellyfinMediaSource jellyfinMediaSource)
{
Option<JellyfinConnection> maybeConnection = jellyfinMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(jellyfinMediaSource, connection))
.ToValidation<BaseError>("Jellyfin media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
JellyfinSecrets secrets = await _jellyfinSecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Jellyfin media source requires an api key");
}
private record ConnectionParameters(
JellyfinMediaSource JellyfinMediaSource,
JellyfinConnection ActiveConnection)
{
public string? ApiKey { get; init; }
}
}

11
ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs

@ -59,17 +59,6 @@ public class
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(parameters.LibraryRefreshInterval); DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(parameters.LibraryRefreshInterval);
if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now)
{ {
// need the jellyfin admin user id for now
Either<BaseError, Unit> syncAdminResult = await _mediator.Send(
new SynchronizeJellyfinAdminUserId(parameters.Library.MediaSourceId),
cancellationToken);
foreach (BaseError error in syncAdminResult.LeftToSeq())
{
_logger.LogError("Error synchronizing jellyfin admin user id: {Error}", error);
return error;
}
Either<BaseError, Unit> result = parameters.Library.MediaKind switch Either<BaseError, Unit> result = parameters.Library.MediaKind switch
{ {
LibraryMediaKind.Movies => LibraryMediaKind.Movies =>

11
ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs

@ -31,17 +31,6 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner
{ {
try try
{ {
// need the jellyfin admin user id for now
Either<BaseError, Unit> syncAdminResult = await _mediator.Send(
new SynchronizeJellyfinAdminUserId(mediaSourceId),
CancellationToken.None);
foreach (BaseError error in syncAdminResult.LeftToSeq())
{
_logger.LogError("Error synchronizing jellyfin admin user id: {Error}", error);
return error;
}
// need to call get libraries to find library that contains collections (box sets) // need to call get libraries to find library that contains collections (box sets)
await _jellyfinApiClient.GetLibraries(address, apiKey); await _jellyfinApiClient.GetLibraries(address, apiKey);

21
ErsatzTV/Services/ScannerService.cs

@ -57,9 +57,6 @@ public class ScannerService : BackgroundService
case SynchronizePlexCollections synchronizePlexCollections: case SynchronizePlexCollections synchronizePlexCollections:
requestTask = SynchronizePlexCollections(synchronizePlexCollections, stoppingToken); requestTask = SynchronizePlexCollections(synchronizePlexCollections, stoppingToken);
break; break;
case SynchronizeJellyfinAdminUserId synchronizeJellyfinAdminUserId:
requestTask = SynchronizeAdminUserId(synchronizeJellyfinAdminUserId, stoppingToken);
break;
case SynchronizeJellyfinLibraries synchronizeJellyfinLibraries: case SynchronizeJellyfinLibraries synchronizeJellyfinLibraries:
requestTask = SynchronizeLibraries(synchronizeJellyfinLibraries, stoppingToken); requestTask = SynchronizeLibraries(synchronizeJellyfinLibraries, stoppingToken);
break; break;
@ -223,24 +220,6 @@ public class ScannerService : BackgroundService
} }
} }
private async Task SynchronizeAdminUserId(
SynchronizeJellyfinAdminUserId request,
CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
Either<BaseError, Unit> result = await mediator.Send(request, cancellationToken);
result.BiIter(
_ => _logger.LogInformation(
"Successfully synchronized Jellyfin admin user id for source {MediaSourceId}",
request.JellyfinMediaSourceId),
error => _logger.LogWarning(
"Unable to synchronize Jellyfin admin user id for source {MediaSourceId}: {Error}",
request.JellyfinMediaSourceId,
error.Value));
}
private async Task SynchronizeLibraries(SynchronizeJellyfinLibraries request, CancellationToken cancellationToken) private async Task SynchronizeLibraries(SynchronizeJellyfinLibraries request, CancellationToken cancellationToken)
{ {
using IServiceScope scope = _serviceScopeFactory.CreateScope(); using IServiceScope scope = _serviceScopeFactory.CreateScope();

Loading…
Cancel
Save