diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index a539b3b3e..be1b53a65 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -202,8 +202,6 @@ namespace NzbDrone.Common.Http.Dispatchers case "If-Modified-Since": webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value); break; - case "Range": - throw new NotImplementedException(); case "Referer": webRequest.Referer = header.Value; break; diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs index b4641be10..40d951367 100644 --- a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs @@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests .Build(); _httpResponse = new HttpResponse(null, new HttpHeader(), ""); - Mocker.GetMock<IHttpClient>().Setup(c => c.Head(It.IsAny<HttpRequest>())).Returns(_httpResponse); + Mocker.GetMock<IHttpClient>().Setup(c => c.Get(It.IsAny<HttpRequest>())).Returns(_httpResponse); } [TestCase(".png")] diff --git a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs index ce310ed5f..8054aa8ec 100644 --- a/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs +++ b/src/NzbDrone.Core/MediaCover/CoverAlreadyExistsSpecification.cs @@ -38,7 +38,7 @@ namespace NzbDrone.Core.MediaCover return lastModifiedLocal.Value.ToUniversalTime() == serverModifiedDate.Value.ToUniversalTime(); } - return true; + return false; } } } diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs index 30a1d909c..c8dcb59ce 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -29,8 +29,7 @@ namespace NzbDrone.Core.MediaCover IHandleAsync<BookDeletedEvent>, IMapCoversToLocal { - private const double HTTP_RATE_LIMIT = 0; - private const string USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 10; Mi A2 Build/QKQ1.190910.002)"; + private const string USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 10; SM-G975U Build/QP1A.190711.020)"; private readonly IImageResizer _resizer; private readonly IBookService _bookService; @@ -127,11 +126,13 @@ namespace NzbDrone.Core.MediaCover try { - alreadyExists = _coverExistsSpecification.AlreadyExists(null, null, fileName); + var serverFileHeaders = GetServerHeaders(cover.Url); + + alreadyExists = _coverExistsSpecification.AlreadyExists(serverFileHeaders.LastModified, GetContentLength(serverFileHeaders), fileName); if (!alreadyExists) { - DownloadCover(author, cover, DateTime.Now); + DownloadCover(author, cover, serverFileHeaders.LastModified ?? DateTime.Now); } } catch (WebException e) @@ -169,11 +170,13 @@ namespace NzbDrone.Core.MediaCover var alreadyExists = false; try { - alreadyExists = _coverExistsSpecification.AlreadyExists(null, null, fileName); + var serverFileHeaders = GetServerHeaders(cover.Url); + + alreadyExists = _coverExistsSpecification.AlreadyExists(serverFileHeaders.LastModified, GetContentLength(serverFileHeaders), fileName); if (!alreadyExists) { - DownloadAlbumCover(book, cover, DateTime.Now); + DownloadAlbumCover(book, cover, serverFileHeaders.LastModified ?? DateTime.Now); } } catch (WebException e) @@ -269,6 +272,38 @@ namespace NzbDrone.Core.MediaCover } } + private HttpHeader GetServerHeaders(string url) + { + // Goodreads doesn't allow a HEAD, so request a zero byte range instead + var request = new HttpRequest(url) + { + AllowAutoRedirect = true, + }; + + request.Headers.Add("Range", "bytes=0-0"); + request.Headers.Add("User-Agent", USER_AGENT); + + return _httpClient.Get(request).Headers; + } + + private long? GetContentLength(HttpHeader headers) + { + var range = headers.Get("content-range"); + + if (range == null) + { + return null; + } + + var split = range.Split('/'); + if (split.Length == 2 && long.TryParse(split[1], out long length)) + { + return length; + } + + return null; + } + public void HandleAsync(AuthorRefreshCompleteEvent message) { EnsureArtistCovers(message.Author);