From 2f8ac793ff2aa628e7bc198b3e4d40d8d8c07e29 Mon Sep 17 00:00:00 2001 From: ta264 Date: Thu, 3 Sep 2020 21:35:49 +0100 Subject: [PATCH] New: Refresh button on book page that bypasses cache --- frontend/src/Book/Details/BookDetails.js | 12 +++++ .../src/Book/Details/BookDetailsConnector.js | 15 ++++++ frontend/src/Commands/commandNames.js | 1 + src/NzbDrone.Core.Test/Framework/CoreTest.cs | 4 +- .../MusicTests/AddAlbumFixture.cs | 4 +- .../MusicTests/AddArtistFixture.cs | 4 +- .../Books/Services/RefreshBookService.cs | 53 ++++++++++++++++++- .../Http/CachedHttpResponseService.cs | 6 +-- .../Goodreads/GoodreadsProxy.cs | 12 ++--- .../MetadataSource/IProvideAuthorInfo.cs | 2 +- .../MetadataSource/IProvideBookInfo.cs | 2 +- 11 files changed, 97 insertions(+), 18 deletions(-) diff --git a/frontend/src/Book/Details/BookDetails.js b/frontend/src/Book/Details/BookDetails.js index 681864e73..e5146d4b5 100644 --- a/frontend/src/Book/Details/BookDetails.js +++ b/frontend/src/Book/Details/BookDetails.js @@ -117,6 +117,7 @@ class BookDetails extends Component { images, links, isSaving, + isRefreshing, isFetching, isPopulated, bookFilesError, @@ -127,6 +128,7 @@ class BookDetails extends Component { nextBook, isSearching, onMonitorTogglePress, + onRefreshPress, onSearchPress } = this.props; @@ -142,6 +144,15 @@ class BookDetails extends Component { + + -1 ); + const isRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_BOOK }); + const isRefreshing = ( + isCommandExecuting(isRefreshingCommand) && + isRefreshingCommand.body.bookId === book.id + ); + const isFetching = isBookFilesFetching; const isPopulated = isBookFilesPopulated; @@ -77,6 +83,7 @@ function createMapStateToProps() { ...book, shortDateFormat: uiSettings.shortDateFormat, author, + isRefreshing, isSearching, isFetching, isPopulated, @@ -147,6 +154,13 @@ class BookDetailsConnector extends Component { }); } + onRefreshPress = () => { + this.props.executeCommand({ + name: commandNames.REFRESH_BOOK, + bookId: this.props.id + }); + } + onSearchPress = () => { this.props.executeCommand({ name: commandNames.BOOK_SEARCH, @@ -162,6 +176,7 @@ class BookDetailsConnector extends Component { ); diff --git a/frontend/src/Commands/commandNames.js b/frontend/src/Commands/commandNames.js index ddbdcbc62..b6eacc28d 100644 --- a/frontend/src/Commands/commandNames.js +++ b/frontend/src/Commands/commandNames.js @@ -12,6 +12,7 @@ export const INTERACTIVE_IMPORT = 'ManualImport'; export const MISSING_BOOK_SEARCH = 'MissingBookSearch'; export const MOVE_AUTHOR = 'MoveAuthor'; export const REFRESH_AUTHOR = 'RefreshAuthor'; +export const REFRESH_BOOK = 'RefreshBook'; export const RENAME_FILES = 'RenameFiles'; export const RENAME_AUTHOR = 'RenameAuthor'; export const RESCAN_FOLDERS = 'RescanFolders'; diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index bfd934e7b..b3967c9b8 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -32,8 +32,8 @@ protected void UseRealHttp() var httpClient = Mocker.Resolve(); Mocker.GetMock() - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns((HttpRequest request, TimeSpan ttl) => httpClient.Get(request)); + .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((HttpRequest request, bool useCavhe, TimeSpan ttl) => httpClient.Get(request)); } } diff --git a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs index a6d4d2d30..2cbd6b848 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs @@ -43,7 +43,7 @@ private void GivenValidAlbum(string readarrId) .Build(); Mocker.GetMock() - .Setup(s => s.GetBookInfo(readarrId)) + .Setup(s => s.GetBookInfo(readarrId, true)) .Returns(Tuple.Create(_fakeArtist.Metadata.Value.ForeignAuthorId, _fakeAlbum, new List { _fakeArtist.Metadata.Value })); @@ -99,7 +99,7 @@ public void should_throw_if_album_cannot_be_found() var newAlbum = AlbumToAdd("edition", "book", "author"); Mocker.GetMock() - .Setup(s => s.GetBookInfo("edition")) + .Setup(s => s.GetBookInfo("edition", true)) .Throws(new BookNotFoundException("edition")); Assert.Throws(() => Subject.AddBook(newAlbum)); diff --git a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs index 42f353d4b..bdbceb712 100644 --- a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs @@ -33,7 +33,7 @@ public void Setup() private void GivenValidArtist(string readarrId) { Mocker.GetMock() - .Setup(s => s.GetAuthorInfo(readarrId)) + .Setup(s => s.GetAuthorInfo(readarrId, true)) .Returns(_fakeArtist); } @@ -113,7 +113,7 @@ public void should_throw_if_artist_cannot_be_found() }; Mocker.GetMock() - .Setup(s => s.GetAuthorInfo(newArtist.ForeignAuthorId)) + .Setup(s => s.GetAuthorInfo(newArtist.ForeignAuthorId, true)) .Throws(new AuthorNotFoundException(newArtist.ForeignAuthorId)); Mocker.GetMock() diff --git a/src/NzbDrone.Core/Books/Services/RefreshBookService.cs b/src/NzbDrone.Core/Books/Services/RefreshBookService.cs index 3a79b1dc8..f4c2d48f7 100644 --- a/src/NzbDrone.Core/Books/Services/RefreshBookService.cs +++ b/src/NzbDrone.Core/Books/Services/RefreshBookService.cs @@ -5,10 +5,13 @@ using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Books.Commands; using NzbDrone.Core.Books.Events; +using NzbDrone.Core.Exceptions; using NzbDrone.Core.History; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MetadataSource; @@ -20,12 +23,15 @@ public interface IRefreshBookService bool RefreshBookInfo(List books, List remoteBooks, Author remoteData, bool forceBookRefresh, bool forceUpdateFileTags, DateTime? lastUpdate); } - public class RefreshBookService : RefreshEntityServiceBase, IRefreshBookService + public class RefreshBookService : RefreshEntityServiceBase, + IRefreshBookService, + IExecute { private readonly IBookService _bookService; private readonly IAuthorService _authorService; private readonly IAddAuthorService _addAuthorService; private readonly IEditionService _editionService; + private readonly IProvideAuthorInfo _authorInfo; private readonly IProvideBookInfo _bookInfo; private readonly IRefreshEditionService _refreshEditionService; private readonly IMediaFileService _mediaFileService; @@ -40,6 +46,7 @@ public RefreshBookService(IBookService bookService, IAddAuthorService addAuthorService, IEditionService editionService, IAuthorMetadataService authorMetadataService, + IProvideAuthorInfo authorInfo, IProvideBookInfo bookInfo, IRefreshEditionService refreshEditionService, IMediaFileService mediaFileService, @@ -54,6 +61,7 @@ public RefreshBookService(IBookService bookService, _authorService = authorService; _addAuthorService = addAuthorService; _editionService = editionService; + _authorInfo = authorInfo; _bookInfo = bookInfo; _refreshEditionService = refreshEditionService; _mediaFileService = mediaFileService; @@ -64,6 +72,32 @@ public RefreshBookService(IBookService bookService, _logger = logger; } + private Author GetSkyhookData(Book book) + { + var foreignId = book.Editions.Value.First().ForeignEditionId; + + try + { + var tuple = _bookInfo.GetBookInfo(foreignId, false); + var author = _authorInfo.GetAuthorInfo(tuple.Item1, false); + var newbook = tuple.Item2; + + newbook.Author = author; + newbook.AuthorMetadata = author.Metadata.Value; + newbook.AuthorMetadataId = book.AuthorMetadataId; + newbook.AuthorMetadata.Value.Id = book.AuthorMetadataId; + + author.Books = new List { newbook }; + return author; + } + catch (BookNotFoundException) + { + _logger.Error($"Could not find book with id {foreignId}"); + } + + return null; + } + protected override RemoteData GetRemoteData(Book local, List remote, Author data) { var result = new RemoteData(); @@ -326,5 +360,22 @@ public bool RefreshBookInfo(Book book, List remoteBooks, Author remoteData { return RefreshEntityInfo(book, remoteBooks, remoteData, true, forceUpdateFileTags, null); } + + public bool RefreshBookInfo(Book book) + { + var data = GetSkyhookData(book); + + return RefreshBookInfo(book, data.Books, data, false); + } + + public void Execute(RefreshBookCommand message) + { + if (message.BookId.HasValue) + { + var book = _bookService.GetBook(message.BookId.Value); + + RefreshBookInfo(book); + } + } } } diff --git a/src/NzbDrone.Core/Http/CachedHttpResponseService.cs b/src/NzbDrone.Core/Http/CachedHttpResponseService.cs index 74a53a086..6672fd80c 100644 --- a/src/NzbDrone.Core/Http/CachedHttpResponseService.cs +++ b/src/NzbDrone.Core/Http/CachedHttpResponseService.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Http { public interface ICachedHttpResponseService { - HttpResponse Get(HttpRequest request, TimeSpan ttl); + HttpResponse Get(HttpRequest request, bool useCache, TimeSpan ttl); } public class CachedHttpResponseService : ICachedHttpResponseService @@ -21,11 +21,11 @@ public CachedHttpResponseService(ICachedHttpResponseRepository httpResponseRepos _httpClient = httpClient; } - public HttpResponse Get(HttpRequest request, TimeSpan ttl) + public HttpResponse Get(HttpRequest request, bool useCache, TimeSpan ttl) { var cached = _repo.FindByUrl(request.Url.ToString()); - if (cached != null && cached.Expiry > DateTime.UtcNow) + if (useCache && cached != null && cached.Expiry > DateTime.UtcNow) { return new HttpResponse(request, new HttpHeader(), cached.Value, (HttpStatusCode)cached.StatusCode); } diff --git a/src/NzbDrone.Core/MetadataSource/Goodreads/GoodreadsProxy.cs b/src/NzbDrone.Core/MetadataSource/Goodreads/GoodreadsProxy.cs index 3f6ab57b4..eb9cc728b 100644 --- a/src/NzbDrone.Core/MetadataSource/Goodreads/GoodreadsProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/Goodreads/GoodreadsProxy.cs @@ -73,7 +73,7 @@ public HashSet GetChangedArtists(DateTime startTime) return null; } - public Author GetAuthorInfo(string foreignAuthorId) + public Author GetAuthorInfo(string foreignAuthorId, bool useCache = true) { _logger.Debug("Getting Author details GoodreadsId of {0}", foreignAuthorId); @@ -85,7 +85,7 @@ public Author GetAuthorInfo(string foreignAuthorId) httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _cachedHttpClient.Get(httpRequest, TimeSpan.FromDays(30)); + var httpResponse = _cachedHttpClient.Get(httpRequest, useCache, TimeSpan.FromDays(30)); if (httpResponse.HasHttpError) { @@ -217,7 +217,7 @@ private AuthorBookListResource GetAuthorBooksPageResource(string foreignAuthorId httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _cachedHttpClient.Get(httpRequest, TimeSpan.FromDays(7)); + var httpResponse = _cachedHttpClient.Get(httpRequest, true, TimeSpan.FromDays(7)); if (httpResponse.HasHttpError) { @@ -249,7 +249,7 @@ private List GetAuthorSeries(string foreignAuthorId, List books) httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _cachedHttpClient.Get(httpRequest, TimeSpan.FromDays(90)); + var httpResponse = _cachedHttpClient.Get(httpRequest, true, TimeSpan.FromDays(90)); if (httpResponse.HasHttpError) { @@ -311,7 +311,7 @@ private HashSet GetChangedBooksUncached(DateTime startTime) return null; } - public Tuple> GetBookInfo(string foreignEditionId) + public Tuple> GetBookInfo(string foreignEditionId, bool useCache = true) { _logger.Debug("Getting Book with GoodreadsId of {0}", foreignEditionId); @@ -323,7 +323,7 @@ public Tuple> GetBookInfo(string foreignEditi httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _cachedHttpClient.Get(httpRequest, TimeSpan.FromDays(90)); + var httpResponse = _cachedHttpClient.Get(httpRequest, useCache, TimeSpan.FromDays(90)); if (httpResponse.HasHttpError) { diff --git a/src/NzbDrone.Core/MetadataSource/IProvideAuthorInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideAuthorInfo.cs index 6dc6b2888..76a241327 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideAuthorInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideAuthorInfo.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.MetadataSource { public interface IProvideAuthorInfo { - Author GetAuthorInfo(string readarrId); + Author GetAuthorInfo(string readarrId, bool useCache = true); Author GetAuthorAndBooks(string readarrId, double minPopularity = 0); HashSet GetChangedArtists(DateTime startTime); } diff --git a/src/NzbDrone.Core/MetadataSource/IProvideBookInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideBookInfo.cs index b1efebe32..f97806e57 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideBookInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideBookInfo.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.MetadataSource { public interface IProvideBookInfo { - Tuple> GetBookInfo(string id); + Tuple> GetBookInfo(string id, bool useCache = true); HashSet GetChangedBooks(DateTime startTime); } }