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.

135 lines
5.8 KiB

using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Repositories;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Scanner.Application.Emby;
public class SynchronizeEmbyShowByIdHandler : IRequestHandler<SynchronizeEmbyShowById, Either<BaseError, string>>
{
private readonly IEmbySecretStore _embySecretStore;
private readonly IEmbyTelevisionLibraryScanner _embyTelevisionLibraryScanner;
private readonly ILogger<SynchronizeEmbyShowByIdHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IEmbyTelevisionRepository _embyTelevisionRepository;
public SynchronizeEmbyShowByIdHandler(
IMediaSourceRepository mediaSourceRepository,
IEmbyTelevisionRepository embyTelevisionRepository,
IEmbySecretStore embySecretStore,
IEmbyTelevisionLibraryScanner embyTelevisionLibraryScanner,
ILogger<SynchronizeEmbyShowByIdHandler> logger)
{
_mediaSourceRepository = mediaSourceRepository;
_embyTelevisionRepository = embyTelevisionRepository;
_embySecretStore = embySecretStore;
_embyTelevisionLibraryScanner = embyTelevisionLibraryScanner;
_logger = logger;
}
public async Task<Either<BaseError, string>> Handle(
SynchronizeEmbyShowById request,
CancellationToken cancellationToken)
{
Validation<BaseError, RequestParameters> validation = await Validate(request);
return await validation.Match(
parameters => Synchronize(parameters, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join()));
}
private async Task<Either<BaseError, string>> Synchronize(
RequestParameters parameters,
CancellationToken cancellationToken)
{
if (parameters.Library.MediaKind != LibraryMediaKind.Shows)
{
return BaseError.New($"Library {parameters.Library.Name} is not a TV show library");
}
_logger.LogInformation(
"Starting targeted scan for show '{ShowTitle}' in Emby library {LibraryName}",
parameters.ShowTitle,
parameters.Library.Name);
Either<BaseError, Unit> result = await _embyTelevisionLibraryScanner.ScanSingleShow(
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.ItemId,
parameters.ShowTitle,
parameters.DeepScan,
cancellationToken);
foreach (BaseError error in result.LeftToSeq())
{
_logger.LogError("Error synchronizing Emby show '{ShowTitle}': {Error}", parameters.ShowTitle, error);
}
return result.Map(_ => $"Show '{parameters.ShowTitle}' in {parameters.Library.Name}");
}
private async Task<Validation<BaseError, RequestParameters>> Validate(SynchronizeEmbyShowById request) =>
(await ValidateConnection(request), await EmbyLibraryMustExist(request), await EmbyShowMustExist(request))
.Apply((connectionParameters, embyLibrary, showTitleItemId) =>
new RequestParameters(
connectionParameters,
embyLibrary,
showTitleItemId.ItemId,
showTitleItemId.Title,
request.DeepScan
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
SynchronizeEmbyShowById request) =>
EmbyMediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
SynchronizeEmbyShowById request) =>
_mediaSourceRepository.GetEmbyByLibraryId(request.EmbyLibraryId)
.Map(v => v.ToValidation<BaseError>(
$"Emby media source for library {request.EmbyLibraryId} does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
EmbyMediaSource embyMediaSource)
{
Option<EmbyConnection> maybeConnection = embyMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(connection))
.ToValidation<BaseError>("Emby media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
EmbySecrets secrets = await _embySecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Emby media source requires an api key");
}
private Task<Validation<BaseError, EmbyLibrary>> EmbyLibraryMustExist(
SynchronizeEmbyShowById request) =>
_mediaSourceRepository.GetEmbyLibrary(request.EmbyLibraryId)
.Map(v => v.ToValidation<BaseError>($"Emby library {request.EmbyLibraryId} does not exist."));
private Task<Validation<BaseError, EmbyShowTitleItemIdResult>> EmbyShowMustExist(
SynchronizeEmbyShowById request) =>
_embyTelevisionRepository.GetShowTitleItemId(request.EmbyLibraryId, request.ShowId)
.Map(v => v.ToValidation<BaseError>($"Jellyfin show {request.ShowId} does not exist in library {request.EmbyLibraryId}."));
private record RequestParameters(
ConnectionParameters ConnectionParameters,
EmbyLibrary Library,
string ItemId,
string ShowTitle,
bool DeepScan);
private record ConnectionParameters(EmbyConnection ActiveConnection)
{
public string? ApiKey { get; init; }
}
}