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:
Taloth Saldono 2020-01-12 22:26:29 +01:00 committed by Bogdan
parent 52c3a95e63
commit bb5ad605fd
11 changed files with 184 additions and 34 deletions

View 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;
}
}
}

View file

@ -31,6 +31,7 @@ public class MediaCoverService :
{
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 IBookService _bookService;
private readonly IHttpClient _httpClient;
@ -46,7 +47,8 @@ public class MediaCoverService :
// So limit the number of concurrent resizing tasks
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,
IHttpClient httpClient,
IDiskProvider diskProvider,
@ -56,6 +58,7 @@ public MediaCoverService(IImageResizer resizer,
IEventAggregator eventAggregator,
Logger logger)
{
_mediaCoverProxy = mediaCoverProxy;
_resizer = resizer;
_bookService = bookService;
_httpClient = httpClient;
@ -82,28 +85,39 @@ public string GetCoverPath(int entityId, MediaCoverEntity coverEntity, MediaCove
public void ConvertToLocalUrls(int entityId, MediaCoverEntity coverEntity, IEnumerable<MediaCover> covers)
{
foreach (var mediaCover in covers)
if (entityId == 0)
{
if (mediaCover.CoverType == MediaCoverTypes.Unknown)
// Author isn't in Readarr yet, map via a proxy to circument referrer issues
foreach (var mediaCover in covers)
{
continue;
mediaCover.Url = _mediaCoverProxy.RegisterUrl(mediaCover.Url);
}
var filePath = GetCoverPath(entityId, coverEntity, mediaCover.CoverType, mediaCover.Extension, null);
if (coverEntity == MediaCoverEntity.Book)
}
else
{
foreach (var mediaCover in covers)
{
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Books/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + GetExtension(mediaCover.CoverType, mediaCover.Extension);
}
else
{
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + GetExtension(mediaCover.CoverType, mediaCover.Extension);
}
if (mediaCover.CoverType == MediaCoverTypes.Unknown)
{
continue;
}
if (_diskProvider.FileExists(filePath))
{
var lastWrite = _diskProvider.FileGetLastWrite(filePath);
mediaCover.Url += "?lastWrite=" + lastWrite.Ticks;
var filePath = GetCoverPath(entityId, coverEntity, mediaCover.CoverType, mediaCover.Extension, null);
if (coverEntity == MediaCoverEntity.Book)
{
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Books/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + GetExtension(mediaCover.CoverType, mediaCover.Extension);
}
else
{
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + GetExtension(mediaCover.CoverType, mediaCover.Extension);
}
if (_diskProvider.FileExists(filePath))
{
var lastWrite = _diskProvider.FileGetLastWrite(filePath);
mediaCover.Url += "?lastWrite=" + lastWrite.Ticks;
}
}
}
}

View file

@ -11,10 +11,12 @@ namespace Readarr.Api.V1.Author
public class AuthorLookupController : Controller
{
private readonly ISearchForNewAuthor _searchProxy;
private readonly IMapCoversToLocal _coverMapper;
public AuthorLookupController(ISearchForNewAuthor searchProxy)
public AuthorLookupController(ISearchForNewAuthor searchProxy, IMapCoversToLocal coverMapper)
{
_searchProxy = searchProxy;
_coverMapper = coverMapper;
}
[HttpGet]
@ -24,12 +26,16 @@ public object Search([FromQuery] string term)
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)
{
var resource = currentAuthor.ToResource();
_coverMapper.ConvertToLocalUrls(resource.Id, MediaCoverEntity.Author, resource.Images);
var poster = currentAuthor.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;

View file

@ -11,10 +11,12 @@ namespace Readarr.Api.V1.Books
public class BookLookupController : Controller
{
private readonly ISearchForNewBook _searchProxy;
private readonly IMapCoversToLocal _coverMapper;
public BookLookupController(ISearchForNewBook searchProxy)
public BookLookupController(ISearchForNewBook searchProxy, IMapCoversToLocal coverMapper)
{
_searchProxy = searchProxy;
_coverMapper = coverMapper;
}
[HttpGet]
@ -24,12 +26,16 @@ public object Search(string term)
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)
{
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);
if (cover != null)
{
resource.RemoteCover = cover.Url;

View file

@ -14,10 +14,12 @@ namespace Readarr.Api.V1.Search
public class SearchController : Controller
{
private readonly ISearchForNewEntity _searchProxy;
private readonly IMapCoversToLocal _coverMapper;
public SearchController(ISearchForNewEntity searchProxy)
public SearchController(ISearchForNewEntity searchProxy, IMapCoversToLocal coverMapper)
{
_searchProxy = searchProxy;
_coverMapper = coverMapper;
}
[HttpGet]
@ -27,7 +29,7 @@ public object Search([FromQuery] string term)
return MapToResource(searchResults).ToList();
}
private static IEnumerable<SearchResource> MapToResource(IEnumerable<object> results)
private IEnumerable<SearchResource> MapToResource(IEnumerable<object> results)
{
var id = 1;
foreach (var result in results)
@ -35,28 +37,32 @@ private static IEnumerable<SearchResource> MapToResource(IEnumerable<object> res
var resource = new SearchResource();
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.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);
if (poster != null)
{
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.Overview = book.Editions.Value.Single(x => x.Monitored).Overview;
resource.Book.Author = book.Author.Value.ToResource();
resource.Book.Editions = book.Editions.Value.ToResource();
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);
if (cover != null)
{
resource.Book.RemoteCover = cover.Url;

View file

@ -4,8 +4,7 @@
namespace Readarr.Api.V1.Search
{
public class
SearchResource : RestResource
public class SearchResource : RestResource
{
public string ForeignId { get; set; }
public AuthorResource Author { get; set; }

View file

@ -6,6 +6,6 @@ public interface IMapHttpRequestsToDisk
{
string Map(string resourceUrl);
bool CanHandle(string resourceUrl);
FileStreamResult GetResponse(string resourceUrl);
IActionResult GetResponse(string resourceUrl);
}
}

View file

@ -43,7 +43,7 @@ public override string Map(string resourceUrl)
public override bool CanHandle(string resourceUrl)
{
return resourceUrl.StartsWith("/MediaCover", StringComparison.InvariantCultureIgnoreCase);
return resourceUrl.StartsWith("/MediaCover/", StringComparison.InvariantCultureIgnoreCase);
}
}
}

View 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);
}
}
}

View file

@ -30,7 +30,7 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
public abstract bool CanHandle(string resourceUrl);
public FileStreamResult GetResponse(string resourceUrl)
public IActionResult GetResponse(string resourceUrl)
{
var filePath = Map(resourceUrl);

View file

@ -57,7 +57,7 @@ private IActionResult MapResource(string path)
if (result != null)
{
if (result.ContentType == "text/html")
if ((result as FileResult)?.ContentType == "text/html")
{
Response.Headers.DisableCache();
}