* wip * start to add jellyfin tables to db * code cleanup * finish adding jellyfin media source * sync jellyfin libraries * display list of jellyfin libraries * toggle jellyfin library sync * edit jellyfin path replacements * noop jellyfin scanners * get jellyfin admin user id on startup * implement jellyfin disconnect * add jellyfin libraries to list; start to query jellyfin library items * code cleanup * start to project jellyfin movies * save new jellyfin movies to db * basic jellyfin movie update * load jellyfin actor artwork * load jellyfin movie poster and fan art * more jellyfin artwork fixes, sync audio streams * jellyfin playback sort of works * skip jellyfin movies that are inaccessible * use ffprobe for jellyfin movie statistics * code cleanup * store jellyfin operating system * more jellyfin movie updates * update jellyfin movie poster and fan art * add jellyfin tv types * sync jellyfin shows * sync jellyfin seasons * sync jellyfin episodes * remove missing jellyfin television items * delete empty jellyfin seasons and shows * fix jellyfin updates * fix indexing jellyfin movie and show languages
112 lines
4.8 KiB
C#
112 lines
4.8 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
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.Logging;
|
|
using static LanguageExt.Prelude;
|
|
|
|
namespace ErsatzTV.Application.Jellyfin.Commands
|
|
{
|
|
public class
|
|
SynchronizeJellyfinLibrariesHandler : MediatR.IRequestHandler<SynchronizeJellyfinLibraries,
|
|
Either<BaseError, Unit>>
|
|
|
|
{
|
|
private readonly IJellyfinApiClient _jellyfinApiClient;
|
|
private readonly IJellyfinSecretStore _jellyfinSecretStore;
|
|
private readonly ILogger<SynchronizeJellyfinLibrariesHandler> _logger;
|
|
private readonly IMediaSourceRepository _mediaSourceRepository;
|
|
|
|
public SynchronizeJellyfinLibrariesHandler(
|
|
IMediaSourceRepository mediaSourceRepository,
|
|
IJellyfinSecretStore jellyfinSecretStore,
|
|
IJellyfinApiClient jellyfinApiClient,
|
|
ILogger<SynchronizeJellyfinLibrariesHandler> logger)
|
|
{
|
|
_mediaSourceRepository = mediaSourceRepository;
|
|
_jellyfinSecretStore = jellyfinSecretStore;
|
|
_jellyfinApiClient = jellyfinApiClient;
|
|
_logger = logger;
|
|
}
|
|
|
|
public Task<Either<BaseError, Unit>> Handle(
|
|
SynchronizeJellyfinLibraries request,
|
|
CancellationToken cancellationToken) =>
|
|
Validate(request)
|
|
.MapT(SynchronizeLibraries)
|
|
.Bind(v => v.ToEitherAsync());
|
|
|
|
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizeJellyfinLibraries request) =>
|
|
MediaSourceMustExist(request)
|
|
.BindT(MediaSourceMustHaveActiveConnection)
|
|
.BindT(MediaSourceMustHaveApiKey);
|
|
|
|
private Task<Validation<BaseError, JellyfinMediaSource>> MediaSourceMustExist(
|
|
SynchronizeJellyfinLibraries 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 async Task<Unit> SynchronizeLibraries(ConnectionParameters connectionParameters)
|
|
{
|
|
Either<BaseError, List<JellyfinLibrary>> maybeLibraries = await _jellyfinApiClient.GetLibraries(
|
|
connectionParameters.ActiveConnection.Address,
|
|
connectionParameters.ApiKey);
|
|
|
|
await maybeLibraries.Match(
|
|
libraries =>
|
|
{
|
|
var existing = connectionParameters.JellyfinMediaSource.Libraries.OfType<JellyfinLibrary>()
|
|
.ToList();
|
|
var toAdd = libraries.Filter(library => existing.All(l => l.ItemId != library.ItemId)).ToList();
|
|
var toRemove = existing.Filter(library => libraries.All(l => l.ItemId != library.ItemId)).ToList();
|
|
return _mediaSourceRepository.UpdateLibraries(
|
|
connectionParameters.JellyfinMediaSource.Id,
|
|
toAdd,
|
|
toRemove);
|
|
},
|
|
error =>
|
|
{
|
|
_logger.LogWarning(
|
|
"Unable to synchronize libraries from jellyfin server {JellyfinServer}: {Error}",
|
|
connectionParameters.JellyfinMediaSource.ServerName,
|
|
error.Value);
|
|
|
|
return Task.CompletedTask;
|
|
});
|
|
|
|
return Unit.Default;
|
|
}
|
|
|
|
private record ConnectionParameters(
|
|
JellyfinMediaSource JellyfinMediaSource,
|
|
JellyfinConnection ActiveConnection)
|
|
{
|
|
public string ApiKey { get; set; }
|
|
}
|
|
}
|
|
}
|