Stream custom live channels using your own media
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

112 lines
4.9 KiB

using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Jellyfin;
using LanguageExt;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Jellyfin.Commands
{
public class
SynchronizeJellyfinAdminUserIdHandler : MediatR.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)
.Filter(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; set; }
}
}
}