mirror of
https://github.com/Readarr/Readarr
synced 2025-12-16 05:12:42 +01: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:
parent
52c3a95e63
commit
bb5ad605fd
11 changed files with 184 additions and 34 deletions
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 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ public interface IMapHttpRequestsToDisk
|
|||
{
|
||||
string Map(string resourceUrl);
|
||||
bool CanHandle(string resourceUrl);
|
||||
FileStreamResult GetResponse(string resourceUrl);
|
||||
IActionResult GetResponse(string resourceUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue