diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs new file mode 100644 index 000000000..79344f004 --- /dev/null +++ b/src/NzbDrone.Core/MediaCover/MediaCoverProxy.cs @@ -0,0 +1,66 @@ +using NzbDrone.Common.Cache; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +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 _cache; + + public MediaCoverProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, ICacheManager cacheManager) + { + _httpClient = httpClient; + _configFileProvider = configFileProvider; + _cache = cacheManager.GetCache(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; + } + } +} diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index b9a6eb914..73911b39f 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaCover IHandleAsync, IMapCoversToLocal { + private readonly IMediaCoverProxy _mediaCoverProxy; private readonly IImageResizer _resizer; private readonly IHttpClient _httpClient; private readonly IDiskProvider _diskProvider; @@ -40,7 +41,8 @@ namespace NzbDrone.Core.MediaCover // 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, IHttpClient httpClient, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, @@ -49,6 +51,7 @@ namespace NzbDrone.Core.MediaCover IEventAggregator eventAggregator, Logger logger) { + _mediaCoverProxy = mediaCoverProxy; _resizer = resizer; _httpClient = httpClient; _diskProvider = diskProvider; @@ -69,16 +72,27 @@ namespace NzbDrone.Core.MediaCover public void ConvertToLocalUrls(int seriesId, IEnumerable covers) { - foreach (var mediaCover in covers) + if (seriesId == 0) { - var filePath = GetCoverPath(seriesId, mediaCover.CoverType); + // Series isn't in Sonarr 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) + { + var filePath = GetCoverPath(seriesId, mediaCover.CoverType); - mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; + mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; - if (_diskProvider.FileExists(filePath)) - { - var lastWrite = _diskProvider.FileGetLastWrite(filePath); - mediaCover.Url += "?lastWrite=" + lastWrite.Ticks; + if (_diskProvider.FileExists(filePath)) + { + var lastWrite = _diskProvider.FileGetLastWrite(filePath); + mediaCover.Url += "?lastWrite=" + lastWrite.Ticks; + } } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index b23b3dcd0..0c9cb03ab 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -100,7 +100,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpResponse = _httpClient.Get>(httpRequest); - return httpResponse.Resource.SelectList(MapSearhResult); + return httpResponse.Resource.SelectList(MapSearchResult); } catch (HttpException) { @@ -113,7 +113,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - private Series MapSearhResult(ShowResource show) + private Series MapSearchResult(ShowResource show) { var series = _seriesService.FindByTvdbId(show.TvdbId); diff --git a/src/Sonarr.Api.V3/Series/SeriesLookupModule.cs b/src/Sonarr.Api.V3/Series/SeriesLookupModule.cs index 33ad52c3c..558169165 100644 --- a/src/Sonarr.Api.V3/Series/SeriesLookupModule.cs +++ b/src/Sonarr.Api.V3/Series/SeriesLookupModule.cs @@ -13,12 +13,14 @@ namespace Sonarr.Api.V3.Series { private readonly ISearchForNewSeries _searchProxy; private readonly IBuildFileNames _fileNameBuilder; + private readonly IMapCoversToLocal _coverMapper; - public SeriesLookupModule(ISearchForNewSeries searchProxy, IBuildFileNames fileNameBuilder) + public SeriesLookupModule(ISearchForNewSeries searchProxy, IBuildFileNames fileNameBuilder, IMapCoversToLocal coverMapper) : base("/series/lookup") { _searchProxy = searchProxy; _fileNameBuilder = fileNameBuilder; + _coverMapper = coverMapper; Get("/", x => Search()); } @@ -33,6 +35,9 @@ namespace Sonarr.Api.V3.Series foreach (var currentSeries in series) { var resource = currentSeries.ToResource(); + + _coverMapper.ConvertToLocalUrls(resource.Id, resource.Images); + var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); if (poster != null) diff --git a/src/Sonarr.Http/Frontend/Mappers/MediaCoverMapper.cs b/src/Sonarr.Http/Frontend/Mappers/MediaCoverMapper.cs index 78d36f5fc..afabdcf00 100644 --- a/src/Sonarr.Http/Frontend/Mappers/MediaCoverMapper.cs +++ b/src/Sonarr.Http/Frontend/Mappers/MediaCoverMapper.cs @@ -43,7 +43,7 @@ namespace Sonarr.Http.Frontend.Mappers public override bool CanHandle(string resourceUrl) { - return resourceUrl.StartsWith("/MediaCover", StringComparison.InvariantCultureIgnoreCase); + return resourceUrl.StartsWith("/MediaCover/", StringComparison.InvariantCultureIgnoreCase); } } } diff --git a/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs b/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs new file mode 100644 index 000000000..32be17548 --- /dev/null +++ b/src/Sonarr.Http/Frontend/Mappers/MediaCoverProxyMapper.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Text.RegularExpressions; +using Nancy; +using Nancy.Responses; +using NzbDrone.Core.MediaCover; + +namespace Sonarr.Http.Frontend.Mappers +{ + public class MediaCoverProxyMapper : IMapHttpRequestsToDisk + { + private readonly Regex _regex = new Regex(@"/MediaCoverProxy/(?\w+)/(?(.+)\.(jpg|png|gif))"); + + private readonly IMediaCoverProxy _mediaCoverProxy; + + public MediaCoverProxyMapper(IMediaCoverProxy mediaCoverProxy) + { + _mediaCoverProxy = mediaCoverProxy; + } + + public string Map(string resourceUrl) + { + return null; + } + + public bool CanHandle(string resourceUrl) + { + return resourceUrl.StartsWith("/MediaCoverProxy/", StringComparison.InvariantCultureIgnoreCase); + } + + public Response GetResponse(string resourceUrl) + { + var match = _regex.Match(resourceUrl); + + if (!match.Success) + { + return new NotFoundResponse(); + } + + var hash = match.Groups["hash"].Value; + var filename = match.Groups["filename"].Value; + + var imageData = _mediaCoverProxy.GetImage(hash); + + return new StreamResponse(() => new MemoryStream(imageData), MimeTypes.GetMimeType(filename)); + } + } +}