using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Plex; namespace ErsatzTV.Scanner.Application.Plex; public class SynchronizePlexNetworksHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexSecretStore _plexSecretStore; private readonly IPlexTelevisionRepository _plexTelevisionRepository; private readonly IPlexNetworkScanner _scanner; public SynchronizePlexNetworksHandler( IMediaSourceRepository mediaSourceRepository, IPlexSecretStore plexSecretStore, IPlexNetworkScanner scanner, IConfigElementRepository configElementRepository, IPlexTelevisionRepository plexTelevisionRepository) { _mediaSourceRepository = mediaSourceRepository; _plexSecretStore = plexSecretStore; _scanner = scanner; _configElementRepository = configElementRepository; _plexTelevisionRepository = plexTelevisionRepository; } public async Task> Handle( SynchronizePlexNetworks request, CancellationToken cancellationToken) { Validation validation = await Validate(request); return await validation.Match( p => SynchronizeNetworks(p, cancellationToken), error => Task.FromResult>(error.Join())); } private async Task> Validate(SynchronizePlexNetworks request) { Task> mediaSource = MediaSourceMustExist(request) .BindT(MediaSourceMustHaveActiveConnection) .BindT(MediaSourceMustHaveToken); return (await mediaSource, await PlexLibraryMustExist(request), await ValidateLibraryRefreshInterval()) .Apply((connectionParameters, plexLibrary, libraryRefreshInterval) => new RequestParameters( connectionParameters, plexLibrary, request.ForceScan, libraryRefreshInterval)); } private Task> PlexLibraryMustExist( SynchronizePlexNetworks request) => _mediaSourceRepository.GetPlexLibrary(request.PlexLibraryId) .Map(v => v.ToValidation($"Plex library {request.PlexLibraryId} does not exist.")); private Task> ValidateLibraryRefreshInterval() => _configElementRepository.GetValue(ConfigElementKey.LibraryRefreshInterval) .FilterT(lri => lri is >= 0 and < 1_000_000) .Map(lri => lri.ToValidation("Library refresh interval is invalid")); private Task> MediaSourceMustExist( SynchronizePlexNetworks request) => _mediaSourceRepository.GetPlexByLibraryId(request.PlexLibraryId) .Map(o => o.ToValidation( $"Plex media source for library {request.PlexLibraryId} does not exist.")); private static 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> SynchronizeNetworks( RequestParameters parameters, CancellationToken cancellationToken) { var lastScan = new DateTimeOffset( parameters.Library.LastNetworksScan ?? SystemTime.MinValueUtc, TimeSpan.Zero); DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(parameters.LibraryRefreshInterval); if (parameters.ForceScan || parameters.LibraryRefreshInterval > 0 && nextScan < DateTimeOffset.Now) { Either result = await _scanner.ScanNetworks( parameters.Library, parameters.ConnectionParameters.ActiveConnection, parameters.ConnectionParameters.PlexServerAuthToken, cancellationToken); if (result.IsRight) { parameters.Library.LastNetworksScan = DateTime.UtcNow; await _plexTelevisionRepository.UpdateLastNetworksScan(parameters.Library); } return result; } return Unit.Default; } private record RequestParameters( ConnectionParameters ConnectionParameters, PlexLibrary Library, bool ForceScan, int LibraryRefreshInterval); private record ConnectionParameters(PlexMediaSource PlexMediaSource, PlexConnection ActiveConnection) { public PlexServerAuthToken? PlexServerAuthToken { get; set; } } }