diff --git a/ErsatzTV.Application/Plex/PlexConnectionParametersViewModel.cs b/ErsatzTV.Application/Plex/PlexConnectionParametersViewModel.cs new file mode 100644 index 000000000..d01b17e50 --- /dev/null +++ b/ErsatzTV.Application/Plex/PlexConnectionParametersViewModel.cs @@ -0,0 +1,6 @@ +using System; + +namespace ErsatzTV.Application.Plex +{ + public record PlexConnectionParametersViewModel(Uri Uri, string AuthToken); +} diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParameters.cs b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParameters.cs new file mode 100644 index 000000000..3607ee8db --- /dev/null +++ b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParameters.cs @@ -0,0 +1,9 @@ +using ErsatzTV.Core; +using LanguageExt; +using MediatR; + +namespace ErsatzTV.Application.Plex.Queries +{ + public record GetPlexConnectionParameters + (int PlexMediaSourceId) : IRequest>; +} diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexImageHandler.cs b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs similarity index 57% rename from ErsatzTV.Application/Plex/Queries/GetPlexImageHandler.cs rename to ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs index d162ef1e2..0ea0e867a 100644 --- a/ErsatzTV.Application/Plex/Queries/GetPlexImageHandler.cs +++ b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs @@ -1,9 +1,7 @@ using System; using System.Linq; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using ErsatzTV.Application.Images; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Plex; @@ -12,17 +10,17 @@ using ErsatzTV.Core.Plex; using LanguageExt; using MediatR; using Microsoft.Extensions.Caching.Memory; -using static LanguageExt.Prelude; namespace ErsatzTV.Application.Plex.Queries { - public class GetPlexImageHandler : IRequestHandler> + public class GetPlexConnectionParametersHandler : IRequestHandler> { private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMemoryCache _memoryCache; private readonly IPlexSecretStore _plexSecretStore; - public GetPlexImageHandler( + public GetPlexConnectionParametersHandler( IMemoryCache memoryCache, IMediaSourceRepository mediaSourceRepository, IPlexSecretStore plexSecretStore) @@ -32,50 +30,39 @@ namespace ErsatzTV.Application.Plex.Queries _plexSecretStore = plexSecretStore; } - public async Task> - Handle(GetPlexImage request, CancellationToken cancellationToken) + public async Task> Handle( + GetPlexConnectionParameters request, + CancellationToken cancellationToken) { - if (!_memoryCache.TryGetValue(request, out ConnectionParameters parameters)) + if (_memoryCache.TryGetValue(request, out PlexConnectionParametersViewModel parameters)) { - Either maybeParameters = - await Validate(request).Map(v => v.ToEither()); - return await maybeParameters.Match( - p => - { - _memoryCache.Set(request, p, TimeSpan.FromHours(1)); - return RequestImageFromPlex(request, p); - }, - error => Left(error).AsTask()); + return parameters; } - return await RequestImageFromPlex(request, parameters); - } - - private async Task> RequestImageFromPlex( - GetPlexImage request, - ConnectionParameters connectionParameters) - { - var url = $"{connectionParameters.ActiveConnection.Uri}/{request.Path}"; - - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("X-Plex-Token", connectionParameters.PlexServerAuthToken.AuthToken); - HttpResponseMessage response = await client.GetAsync(url); - if (response.IsSuccessStatusCode) - { - return new ImageViewModel( - await response.Content.ReadAsByteArrayAsync(), - response.Content.Headers.ContentType?.MediaType ?? "image/jpeg"); - } + Either maybeParameters = + await Validate(request) + .MapT( + cp => new PlexConnectionParametersViewModel( + new Uri(cp.ActiveConnection.Uri), + cp.PlexServerAuthToken.AuthToken)) + .Map(v => v.ToEither()); - return BaseError.New($"Unsuccessful response from plex image request: {response.StatusCode}"); + return maybeParameters.Match( + p => + { + _memoryCache.Set(request, p, TimeSpan.FromHours(1)); + return maybeParameters; + }, + error => error); } - private Task> Validate(GetPlexImage request) => + private Task> Validate(GetPlexConnectionParameters request) => PlexMediaSourceMustExist(request) .BindT(MediaSourceMustHaveActiveConnection) .BindT(MediaSourceMustHaveToken); - private Task> PlexMediaSourceMustExist(GetPlexImage request) => + private Task> PlexMediaSourceMustExist( + GetPlexConnectionParameters request) => _mediaSourceRepository.GetPlex(request.PlexMediaSourceId) .Map( v => v.ToValidation( diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexImage.cs b/ErsatzTV.Application/Plex/Queries/GetPlexImage.cs deleted file mode 100644 index 26ad8f14a..000000000 --- a/ErsatzTV.Application/Plex/Queries/GetPlexImage.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ErsatzTV.Application.Images; -using ErsatzTV.Core; -using LanguageExt; -using MediatR; - -namespace ErsatzTV.Application.Plex.Queries -{ - public record GetPlexImage(int PlexMediaSourceId, string Path) : IRequest>; -} diff --git a/ErsatzTV/Controllers/ArtworkController.cs b/ErsatzTV/Controllers/ArtworkController.cs index 5b482812f..5170da035 100644 --- a/ErsatzTV/Controllers/ArtworkController.cs +++ b/ErsatzTV/Controllers/ArtworkController.cs @@ -1,6 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; using ErsatzTV.Application.Images; using ErsatzTV.Application.Images.Queries; +using ErsatzTV.Application.Plex; using ErsatzTV.Application.Plex.Queries; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -14,9 +18,14 @@ namespace ErsatzTV.Controllers [ApiExplorerSettings(IgnoreApi = true)] public class PostersController : ControllerBase { + private readonly IHttpClientFactory _httpClientFactory; private readonly IMediator _mediator; - public PostersController(IMediator mediator) => _mediator = mediator; + public PostersController(IMediator mediator, IHttpClientFactory httpClientFactory) + { + _mediator = mediator; + _httpClientFactory = httpClientFactory; + } [HttpGet("/artwork/posters/{fileName}")] public async Task GetPoster(string fileName) @@ -31,11 +40,27 @@ namespace ErsatzTV.Controllers [HttpGet("/artwork/posters/plex/{plexMediaSourceId}/{*path}")] public async Task GetPlexPoster(int plexMediaSourceId, string path) { - Either imageContents = - await _mediator.Send(new GetPlexImage(plexMediaSourceId, path)); - return imageContents.Match( - Left: _ => new NotFoundResult(), - Right: r => new FileContentResult(r.Contents, r.MimeType)); + Either connectionParameters = + await _mediator.Send(new GetPlexConnectionParameters(plexMediaSourceId)); + + return await connectionParameters.Match>( + Left: _ => new NotFoundResult().AsTask(), + Right: async r => + { + HttpClient client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Add("X-Plex-Token", r.AuthToken); + + var transcodePath = $"photo/:/transcode?url=/{path}&height=440&width=304&minSize=1&upscale=0"; + var fullPath = new Uri(r.Uri, transcodePath); + HttpResponseMessage response = await client.GetAsync( + fullPath, + HttpCompletionOption.ResponseHeadersRead); + Stream stream = await response.Content.ReadAsStreamAsync(); + + return new FileStreamResult( + stream, + response.Content.Headers.ContentType?.MediaType ?? "image/jpeg"); + }); } [HttpGet("/artwork/thumbnails/{fileName}")]