mirror of
https://github.com/fergalmoran/Readarr.git
synced 2025-12-25 19:09:00 +00:00
Fixed: Posters not always showing when searching for new authors
(cherry picked from commit 10dc884fa87a8337e9f0622c269adede0b262029) Co-authored-by: optimous012 Closes #145
This commit is contained in:
64
src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs
Normal file
64
src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaCover
|
||||||
|
{
|
||||||
|
public interface IMediaCoverProxy
|
||||||
|
{
|
||||||
|
string RegisterUrl(string url);
|
||||||
|
|
||||||
|
string GetUrl(string hash);
|
||||||
|
byte[] GetImage(string hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MediaCoverProxy : IMediaCoverProxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly ICached<string> _cache;
|
||||||
|
|
||||||
|
public MediaCoverProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, ICacheManager cacheManager)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RegisterUrl(string url)
|
||||||
|
{
|
||||||
|
var hash = url.SHA256Hash();
|
||||||
|
|
||||||
|
_cache.Set(hash, url, TimeSpan.FromHours(24));
|
||||||
|
|
||||||
|
_cache.ClearExpired();
|
||||||
|
|
||||||
|
var fileName = Path.GetFileName(url);
|
||||||
|
return _configFileProvider.UrlBase + @"/MediaCoverProxy/" + hash + "/" + fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUrl(string hash)
|
||||||
|
{
|
||||||
|
var result = _cache.Find(hash);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException("Url no longer in cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetImage(string hash)
|
||||||
|
{
|
||||||
|
var url = GetUrl(hash);
|
||||||
|
|
||||||
|
var request = new HttpRequest(url);
|
||||||
|
|
||||||
|
return _httpClient.Get(request).ResponseData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
{
|
{
|
||||||
private const string USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 10; SM-G975U Build/QP1A.190711.020)";
|
private const string USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 10; SM-G975U Build/QP1A.190711.020)";
|
||||||
|
|
||||||
|
private readonly IMediaCoverProxy _mediaCoverProxy;
|
||||||
private readonly IImageResizer _resizer;
|
private readonly IImageResizer _resizer;
|
||||||
private readonly IBookService _bookService;
|
private readonly IBookService _bookService;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
@@ -46,7 +47,8 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
// So limit the number of concurrent resizing tasks
|
// So limit the number of concurrent resizing tasks
|
||||||
private static SemaphoreSlim _semaphore = new SemaphoreSlim((int)Math.Ceiling(Environment.ProcessorCount / 2.0));
|
private static SemaphoreSlim _semaphore = new SemaphoreSlim((int)Math.Ceiling(Environment.ProcessorCount / 2.0));
|
||||||
|
|
||||||
public MediaCoverService(IImageResizer resizer,
|
public MediaCoverService(IMediaCoverProxy mediaCoverProxy,
|
||||||
|
IImageResizer resizer,
|
||||||
IBookService bookService,
|
IBookService bookService,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
@@ -56,6 +58,7 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
|
_mediaCoverProxy = mediaCoverProxy;
|
||||||
_resizer = resizer;
|
_resizer = resizer;
|
||||||
_bookService = bookService;
|
_bookService = bookService;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
@@ -81,6 +84,16 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable<MediaCover> covers)
|
public void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable<MediaCover> covers)
|
||||||
|
{
|
||||||
|
if (entityId == 0)
|
||||||
|
{
|
||||||
|
// Author isn't in Readarr yet, map via a proxy to circument referrer issues
|
||||||
|
foreach (var mediaCover in covers)
|
||||||
|
{
|
||||||
|
mediaCover.Url = _mediaCoverProxy.RegisterUrl(mediaCover.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
foreach (var mediaCover in covers)
|
foreach (var mediaCover in covers)
|
||||||
{
|
{
|
||||||
@@ -107,6 +120,7 @@ namespace NzbDrone.Core.MediaCover
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetAuthorCoverPath(int authorId)
|
private string GetAuthorCoverPath(int authorId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ namespace Readarr.Api.V1.Author
|
|||||||
public class AuthorLookupController : Controller
|
public class AuthorLookupController : Controller
|
||||||
{
|
{
|
||||||
private readonly ISearchForNewAuthor _searchProxy;
|
private readonly ISearchForNewAuthor _searchProxy;
|
||||||
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
|
|
||||||
public AuthorLookupController(ISearchForNewAuthor searchProxy)
|
public AuthorLookupController(ISearchForNewAuthor searchProxy, IMapCoversToLocal coverMapper)
|
||||||
{
|
{
|
||||||
_searchProxy = searchProxy;
|
_searchProxy = searchProxy;
|
||||||
|
_coverMapper = coverMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -24,12 +26,16 @@ namespace Readarr.Api.V1.Author
|
|||||||
return MapToResource(searchResults).ToList();
|
return MapToResource(searchResults).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<AuthorResource> MapToResource(IEnumerable<NzbDrone.Core.Books.Author> author)
|
private IEnumerable<AuthorResource> MapToResource(IEnumerable<NzbDrone.Core.Books.Author> author)
|
||||||
{
|
{
|
||||||
foreach (var currentAuthor in author)
|
foreach (var currentAuthor in author)
|
||||||
{
|
{
|
||||||
var resource = currentAuthor.ToResource();
|
var resource = currentAuthor.ToResource();
|
||||||
|
|
||||||
|
_coverMapper.ConvertToLocalUrls(resource.Id, MediaCoverEntity.Author, resource.Images);
|
||||||
|
|
||||||
var poster = currentAuthor.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
var poster = currentAuthor.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||||
|
|
||||||
if (poster != null)
|
if (poster != null)
|
||||||
{
|
{
|
||||||
resource.RemotePoster = poster.Url;
|
resource.RemotePoster = poster.Url;
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ namespace Readarr.Api.V1.Books
|
|||||||
public class BookLookupController : Controller
|
public class BookLookupController : Controller
|
||||||
{
|
{
|
||||||
private readonly ISearchForNewBook _searchProxy;
|
private readonly ISearchForNewBook _searchProxy;
|
||||||
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
|
|
||||||
public BookLookupController(ISearchForNewBook searchProxy)
|
public BookLookupController(ISearchForNewBook searchProxy, IMapCoversToLocal coverMapper)
|
||||||
{
|
{
|
||||||
_searchProxy = searchProxy;
|
_searchProxy = searchProxy;
|
||||||
|
_coverMapper = coverMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -24,12 +26,16 @@ namespace Readarr.Api.V1.Books
|
|||||||
return MapToResource(searchResults).ToList();
|
return MapToResource(searchResults).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<BookResource> MapToResource(IEnumerable<NzbDrone.Core.Books.Book> books)
|
private IEnumerable<BookResource> MapToResource(IEnumerable<NzbDrone.Core.Books.Book> books)
|
||||||
{
|
{
|
||||||
foreach (var currentBook in books)
|
foreach (var currentBook in books)
|
||||||
{
|
{
|
||||||
var resource = currentBook.ToResource();
|
var resource = currentBook.ToResource();
|
||||||
|
|
||||||
|
_coverMapper.ConvertToLocalUrls(resource.Id, MediaCoverEntity.Book, resource.Images);
|
||||||
|
|
||||||
var cover = currentBook.Editions.Value.Single(x => x.Monitored).Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover);
|
var cover = currentBook.Editions.Value.Single(x => x.Monitored).Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover);
|
||||||
|
|
||||||
if (cover != null)
|
if (cover != null)
|
||||||
{
|
{
|
||||||
resource.RemoteCover = cover.Url;
|
resource.RemoteCover = cover.Url;
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ namespace Readarr.Api.V1.Search
|
|||||||
public class SearchController : Controller
|
public class SearchController : Controller
|
||||||
{
|
{
|
||||||
private readonly ISearchForNewEntity _searchProxy;
|
private readonly ISearchForNewEntity _searchProxy;
|
||||||
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
|
|
||||||
public SearchController(ISearchForNewEntity searchProxy)
|
public SearchController(ISearchForNewEntity searchProxy, IMapCoversToLocal coverMapper)
|
||||||
{
|
{
|
||||||
_searchProxy = searchProxy;
|
_searchProxy = searchProxy;
|
||||||
|
_coverMapper = coverMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -27,7 +29,7 @@ namespace Readarr.Api.V1.Search
|
|||||||
return MapToResource(searchResults).ToList();
|
return MapToResource(searchResults).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<SearchResource> MapToResource(IEnumerable<object> results)
|
private IEnumerable<SearchResource> MapToResource(IEnumerable<object> results)
|
||||||
{
|
{
|
||||||
var id = 1;
|
var id = 1;
|
||||||
foreach (var result in results)
|
foreach (var result in results)
|
||||||
@@ -35,28 +37,32 @@ namespace Readarr.Api.V1.Search
|
|||||||
var resource = new SearchResource();
|
var resource = new SearchResource();
|
||||||
resource.Id = id++;
|
resource.Id = id++;
|
||||||
|
|
||||||
if (result is NzbDrone.Core.Books.Author)
|
if (result is NzbDrone.Core.Books.Author author)
|
||||||
{
|
{
|
||||||
var author = (NzbDrone.Core.Books.Author)result;
|
|
||||||
resource.Author = author.ToResource();
|
resource.Author = author.ToResource();
|
||||||
resource.ForeignId = author.ForeignAuthorId;
|
resource.ForeignId = author.ForeignAuthorId;
|
||||||
|
|
||||||
|
_coverMapper.ConvertToLocalUrls(resource.Author.Id, MediaCoverEntity.Author, resource.Author.Images);
|
||||||
|
|
||||||
var poster = author.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
var poster = author.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||||
|
|
||||||
if (poster != null)
|
if (poster != null)
|
||||||
{
|
{
|
||||||
resource.Author.RemotePoster = poster.Url;
|
resource.Author.RemotePoster = poster.Url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (result is NzbDrone.Core.Books.Book)
|
else if (result is NzbDrone.Core.Books.Book book)
|
||||||
{
|
{
|
||||||
var book = (NzbDrone.Core.Books.Book)result;
|
|
||||||
resource.Book = book.ToResource();
|
resource.Book = book.ToResource();
|
||||||
resource.Book.Overview = book.Editions.Value.Single(x => x.Monitored).Overview;
|
resource.Book.Overview = book.Editions.Value.Single(x => x.Monitored).Overview;
|
||||||
resource.Book.Author = book.Author.Value.ToResource();
|
resource.Book.Author = book.Author.Value.ToResource();
|
||||||
resource.Book.Editions = book.Editions.Value.ToResource();
|
resource.Book.Editions = book.Editions.Value.ToResource();
|
||||||
resource.ForeignId = book.ForeignBookId;
|
resource.ForeignId = book.ForeignBookId;
|
||||||
|
|
||||||
|
_coverMapper.ConvertToLocalUrls(resource.Book.Id, MediaCoverEntity.Book, resource.Book.Images);
|
||||||
|
|
||||||
var cover = book.Editions.Value.Single(x => x.Monitored).Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover);
|
var cover = book.Editions.Value.Single(x => x.Monitored).Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover);
|
||||||
|
|
||||||
if (cover != null)
|
if (cover != null)
|
||||||
{
|
{
|
||||||
resource.Book.RemoteCover = cover.Url;
|
resource.Book.RemoteCover = cover.Url;
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ using Readarr.Http.REST;
|
|||||||
|
|
||||||
namespace Readarr.Api.V1.Search
|
namespace Readarr.Api.V1.Search
|
||||||
{
|
{
|
||||||
public class
|
public class SearchResource : RestResource
|
||||||
SearchResource : RestResource
|
|
||||||
{
|
{
|
||||||
public string ForeignId { get; set; }
|
public string ForeignId { get; set; }
|
||||||
public AuthorResource Author { get; set; }
|
public AuthorResource Author { get; set; }
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ namespace Readarr.Http.Frontend.Mappers
|
|||||||
{
|
{
|
||||||
string Map(string resourceUrl);
|
string Map(string resourceUrl);
|
||||||
bool CanHandle(string resourceUrl);
|
bool CanHandle(string resourceUrl);
|
||||||
FileStreamResult GetResponse(string resourceUrl);
|
IActionResult GetResponse(string resourceUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace Readarr.Http.Frontend.Mappers
|
|||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
{
|
{
|
||||||
return resourceUrl.StartsWith("/MediaCover", StringComparison.InvariantCultureIgnoreCase);
|
return resourceUrl.StartsWith("/MediaCover/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
src/Readarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs
Normal file
55
src/Readarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
|
||||||
|
namespace Readarr.Http.Frontend.Mappers
|
||||||
|
{
|
||||||
|
public class MediaCoverProxyMapper : IMapHttpRequestsToDisk
|
||||||
|
{
|
||||||
|
private readonly Regex _regex = new Regex(@"/MediaCoverProxy/(?<hash>\w+)/(?<filename>(.+)\.(jpg|png|gif))");
|
||||||
|
|
||||||
|
private readonly IMediaCoverProxy _mediaCoverProxy;
|
||||||
|
private readonly IContentTypeProvider _mimeTypeProvider;
|
||||||
|
|
||||||
|
public MediaCoverProxyMapper(IMediaCoverProxy mediaCoverProxy)
|
||||||
|
{
|
||||||
|
_mediaCoverProxy = mediaCoverProxy;
|
||||||
|
_mimeTypeProvider = new FileExtensionContentTypeProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Map(string resourceUrl)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHandle(string resourceUrl)
|
||||||
|
{
|
||||||
|
return resourceUrl.StartsWith("/MediaCoverProxy/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult GetResponse(string resourceUrl)
|
||||||
|
{
|
||||||
|
var match = _regex.Match(resourceUrl);
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return new StatusCodeResult((int)HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = match.Groups["hash"].Value;
|
||||||
|
var filename = match.Groups["filename"].Value;
|
||||||
|
|
||||||
|
var imageData = _mediaCoverProxy.GetImage(hash);
|
||||||
|
|
||||||
|
if (!_mimeTypeProvider.TryGetContentType(filename, out var contentType))
|
||||||
|
{
|
||||||
|
contentType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileContentResult(imageData, contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ namespace Readarr.Http.Frontend.Mappers
|
|||||||
|
|
||||||
public abstract bool CanHandle(string resourceUrl);
|
public abstract bool CanHandle(string resourceUrl);
|
||||||
|
|
||||||
public FileStreamResult GetResponse(string resourceUrl)
|
public IActionResult GetResponse(string resourceUrl)
|
||||||
{
|
{
|
||||||
var filePath = Map(resourceUrl);
|
var filePath = Map(resourceUrl);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace Readarr.Http.Frontend
|
|||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
if (result.ContentType == "text/html")
|
if ((result as FileResult)?.ContentType == "text/html")
|
||||||
{
|
{
|
||||||
Response.Headers.DisableCache();
|
Response.Headers.DisableCache();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user