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.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Plex; using LanguageExt; using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.MediaSources.Commands { public class SynchronizePlexLibrariesHandler : MediatR.IRequestHandler> { private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexSecretStore _plexSecretStore; private readonly IPlexServerApiClient _plexServerApiClient; public SynchronizePlexLibrariesHandler( IMediaSourceRepository mediaSourceRepository, IPlexSecretStore plexSecretStore, IPlexServerApiClient plexServerApiClient, ILogger logger) { _mediaSourceRepository = mediaSourceRepository; _plexSecretStore = plexSecretStore; _plexServerApiClient = plexServerApiClient; _logger = logger; } public Task> Handle( SynchronizePlexLibraries request, CancellationToken cancellationToken) => Validate(request) .MapT(SynchronizeLibraries) .Bind(v => v.ToEitherAsync()); private Task> Validate(SynchronizePlexLibraries request) => MediaSourceMustExist(request) .BindT(MediaSourceMustHaveActiveConnection) .BindT(MediaSourceMustHaveToken); private Task> MediaSourceMustExist(SynchronizePlexLibraries request) => _mediaSourceRepository.GetPlex(request.PlexMediaSourceId) .Map(o => o.ToValidation("Plex media source does not exist.")); private Validation MediaSourceMustHaveActiveConnection( PlexMediaSource plexMediaSource) { Option maybeConnection = plexMediaSource.Connections.SingleOrDefault(c => c.IsActive); return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection)) .ToValidation("Plex media source requires an active connection"); } private async Task> MediaSourceMustHaveToken( ConnectionParameters connectionParameters) { Option maybeToken = await _plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier); return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token }) .ToValidation("Plex media source requires a token"); } private async Task SynchronizeLibraries(ConnectionParameters connectionParameters) { Either> maybeLibraries = await _plexServerApiClient.GetLibraries( connectionParameters.ActiveConnection, connectionParameters.PlexServerAuthToken); await maybeLibraries.Match( libraries => { List existing = connectionParameters.PlexMediaSource.Libraries; var toAdd = libraries.Filter(library => existing.All(l => l.Key != library.Key)).ToList(); var toRemove = existing.Filter(library => libraries.All(l => l.Key != library.Key)).ToList(); existing.AddRange(toAdd); toRemove.ForEach(c => existing.Remove(c)); return _mediaSourceRepository.Update(connectionParameters.PlexMediaSource); }, error => { _logger.LogWarning( "Unable to synchronize libraries from Plex server {PlexServer}: {Error}", connectionParameters.PlexMediaSource.Name, error.Value); return Task.CompletedTask; }); return Unit.Default; } private record ConnectionParameters( PlexMediaSource PlexMediaSource, PlexMediaSourceConnection ActiveConnection) { public PlexServerAuthToken PlexServerAuthToken { get; set; } } } }