diff --git a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs index cc96c58b7..b8130548a 100644 --- a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs +++ b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs @@ -88,6 +88,19 @@ private void WithBookId() _importListReports.First().EditionGoodreadsId = "1234"; } + private void WithSecondBook() + { + var importListItem2 = new ImportListItemInfo + { + Author = "Linkin Park", + AuthorGoodreadsId = "f59c5520-5f46-4d2c-b2c4-822eabf53419", + Book = "Meteora 2", + EditionGoodreadsId = "5678", + BookGoodreadsId = "8765" + }; + _importListReports.Add(importListItem2); + } + private void WithExistingAuthor() { Mocker.GetMock() @@ -290,5 +303,26 @@ public void should_not_add_book_if_excluded_author() Mocker.GetMock() .Verify(v => v.AddBooks(It.Is>(t => t.Count == 0), false)); } + + [TestCase(ImportListMonitorType.None, 0, false)] + [TestCase(ImportListMonitorType.SpecificBook, 2, true)] + [TestCase(ImportListMonitorType.EntireAuthor, 0, true)] + public void should_add_two_books(ImportListMonitorType monitor, int expectedBooksMonitored, bool expectedAuthorMonitored) + { + WithBook(); + WithBookId(); + WithSecondBook(); + WithAuthorId(); + WithMonitorType(monitor); + + Subject.Execute(new ImportListSyncCommand()); + + Mocker.GetMock() + .Verify(v => v.AddBooks(It.Is>(t => t.Count == 2), false)); + Mocker.GetMock() + .Verify(v => v.AddAuthors(It.Is>(t => t.Count == 1 && + t.First().AddOptions.BooksToMonitor.Count == expectedBooksMonitored && + t.First().Monitored == expectedAuthorMonitored), false)); + } } } diff --git a/src/NzbDrone.Core/Books/Services/BookMonitoredService.cs b/src/NzbDrone.Core/Books/Services/BookMonitoredService.cs index 30d793d44..ff33a26ee 100644 --- a/src/NzbDrone.Core/Books/Services/BookMonitoredService.cs +++ b/src/NzbDrone.Core/Books/Services/BookMonitoredService.cs @@ -41,9 +41,9 @@ public void SetBookMonitoredStatus(Author author, MonitoringOptions monitoringOp if (monitoredBooks.Any()) { ToggleBooksMonitoredState( - books.Where(s => monitoredBooks.Any(t => t == s.ForeignBookId)), true); + books.Where(s => monitoredBooks.Contains(s.ForeignBookId)), true); ToggleBooksMonitoredState( - books.Where(s => monitoredBooks.Any(t => t != s.ForeignBookId)), false); + books.Where(s => !monitoredBooks.Contains(s.ForeignBookId)), false); } else { diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index 975c3bb83..dcb9dc4d9 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -99,12 +99,12 @@ private List ProcessReports(List reports) if (report.Book.IsNotNullOrWhiteSpace() || report.EditionGoodreadsId.IsNotNullOrWhiteSpace()) { - if (report.EditionGoodreadsId.IsNullOrWhiteSpace() || report.AuthorGoodreadsId.IsNullOrWhiteSpace()) + if (report.EditionGoodreadsId.IsNullOrWhiteSpace() || report.AuthorGoodreadsId.IsNullOrWhiteSpace() || report.BookGoodreadsId.IsNullOrWhiteSpace()) { MapBookReport(report); } - ProcessBookReport(importList, report, listExclusions, booksToAdd); + ProcessBookReport(importList, report, listExclusions, booksToAdd, authorsToAdd); } else if (report.Author.IsNotNullOrWhiteSpace() || report.AuthorGoodreadsId.IsNotNullOrWhiteSpace()) { @@ -147,7 +147,7 @@ private void MapBookReport(ImportListItemInfo report) mappedBook = _bookSearchService.SearchForNewBook(report.Book, report.Author).FirstOrDefault(); } - // Break if we are looking for an book and cant find it. This will avoid us from adding the author and possibly getting it wrong. + // Break if we are looking for a book and cant find it. This will avoid us from adding the author and possibly getting it wrong. if (mappedBook == null) { _logger.Trace($"Nothing found for {report.EditionGoodreadsId}"); @@ -159,11 +159,11 @@ private void MapBookReport(ImportListItemInfo report) report.BookGoodreadsId = mappedBook.ForeignBookId; report.Book = mappedBook.Title; - report.Author = mappedBook.AuthorMetadata?.Value?.Name; - report.AuthorGoodreadsId = mappedBook.AuthorMetadata?.Value?.ForeignAuthorId; + report.Author ??= mappedBook.AuthorMetadata?.Value?.Name; + report.AuthorGoodreadsId ??= mappedBook.AuthorMetadata?.Value?.ForeignAuthorId; } - private void ProcessBookReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List booksToAdd) + private void ProcessBookReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List booksToAdd, List authorsToAdd) { if (report.EditionGoodreadsId == null) { @@ -181,13 +181,13 @@ private void ProcessBookReport(ImportListDefinition importList, ImportListItemIn if (excludedBook != null) { - _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.EditionGoodreadsId, report.Book); + _logger.Debug("{0} [{1}] Rejected due to list exclusion", report.EditionGoodreadsId, report.Book); return; } if (excludedAuthor != null) { - _logger.Debug("{0} [{1}] Rejected due to list exlcusion for parent author", report.EditionGoodreadsId, report.Book); + _logger.Debug("{0} [{1}] Rejected due to list exclusion for parent author", report.EditionGoodreadsId, report.Book); return; } @@ -223,6 +223,26 @@ private void ProcessBookReport(ImportListDefinition importList, ImportListItemIn { var monitored = importList.ShouldMonitor != ImportListMonitorType.None; + var toAddAuthor = new Author + { + Monitored = monitored, + RootFolderPath = importList.RootFolderPath, + QualityProfileId = importList.ProfileId, + MetadataProfileId = importList.MetadataProfileId, + Tags = importList.Tags, + AddOptions = new AddAuthorOptions + { + SearchForMissingBooks = importList.ShouldSearch, + Monitored = monitored, + Monitor = monitored ? MonitorTypes.All : MonitorTypes.None + } + }; + + if (report.AuthorGoodreadsId != null && report.Author != null) + { + toAddAuthor = ProcessAuthorReport(importList, report, listExclusions, authorsToAdd); + } + var toAdd = new Book { ForeignBookId = report.BookGoodreadsId, @@ -235,29 +255,16 @@ private void ProcessBookReport(ImportListDefinition importList, ImportListItemIn Monitored = true } }, - Author = new Author - { - Monitored = monitored, - RootFolderPath = importList.RootFolderPath, - QualityProfileId = importList.ProfileId, - MetadataProfileId = importList.MetadataProfileId, - Tags = importList.Tags, - AddOptions = new AddAuthorOptions - { - SearchForMissingBooks = importList.ShouldSearch, - Monitored = monitored, - Monitor = monitored ? MonitorTypes.All : MonitorTypes.None - } - }, + Author = toAddAuthor, AddOptions = new AddBookOptions { - SearchForNewBook = monitored + SearchForNewBook = importList.ShouldSearch } }; if (importList.ShouldMonitor == ImportListMonitorType.SpecificBook) { - toAdd.Author.Value.AddOptions.BooksToMonitor.Add(toAdd.ForeignBookId); + toAddAuthor.AddOptions.BooksToMonitor.Add(toAdd.ForeignBookId); } booksToAdd.Add(toAdd); @@ -272,11 +279,11 @@ private void MapAuthorReport(ImportListItemInfo report) report.Author = mappedAuthor?.Metadata.Value?.Name; } - private void ProcessAuthorReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List authorsToAdd) + private Author ProcessAuthorReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List authorsToAdd) { if (report.AuthorGoodreadsId == null) { - return; + return null; } // Check to see if author in DB @@ -285,10 +292,13 @@ private void ProcessAuthorReport(ImportListDefinition importList, ImportListItem // Check to see if author excluded var excludedAuthor = listExclusions.Where(s => s.ForeignId == report.AuthorGoodreadsId).SingleOrDefault(); + // Check to see if author in import + var existingImportAuthor = authorsToAdd.Find(i => i.ForeignAuthorId == report.AuthorGoodreadsId); + if (excludedAuthor != null) { - _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.AuthorGoodreadsId, report.Author); - return; + _logger.Debug("{0} [{1}] Rejected due to list exclusion", report.AuthorGoodreadsId, report.Author); + return null; } if (existingAuthor != null) @@ -301,34 +311,41 @@ private void ProcessAuthorReport(ImportListDefinition importList, ImportListItem _authorService.UpdateAuthor(existingAuthor); } - return; + return existingAuthor; } - // Append Author if not already in DB or already on add list - if (authorsToAdd.All(s => s.Metadata.Value.ForeignAuthorId != report.AuthorGoodreadsId)) + if (existingImportAuthor != null) { - var monitored = importList.ShouldMonitor != ImportListMonitorType.None; + _logger.Debug("{0} [{1}] Rejected, Author Exists in Import.", report.AuthorGoodreadsId, report.Author); - authorsToAdd.Add(new Author - { - Metadata = new AuthorMetadata - { - ForeignAuthorId = report.AuthorGoodreadsId, - Name = report.Author - }, - Monitored = monitored, - RootFolderPath = importList.RootFolderPath, - QualityProfileId = importList.ProfileId, - MetadataProfileId = importList.MetadataProfileId, - Tags = importList.Tags, - AddOptions = new AddAuthorOptions - { - SearchForMissingBooks = importList.ShouldSearch, - Monitored = monitored, - Monitor = monitored ? MonitorTypes.All : MonitorTypes.None - } - }); + return existingImportAuthor; } + + var monitored = importList.ShouldMonitor != ImportListMonitorType.None; + + var toAdd = new Author + { + Metadata = new AuthorMetadata + { + ForeignAuthorId = report.AuthorGoodreadsId, + Name = report.Author + }, + Monitored = monitored, + RootFolderPath = importList.RootFolderPath, + QualityProfileId = importList.ProfileId, + MetadataProfileId = importList.MetadataProfileId, + Tags = importList.Tags, + AddOptions = new AddAuthorOptions + { + SearchForMissingBooks = importList.ShouldSearch, + Monitored = monitored, + Monitor = monitored ? MonitorTypes.All : MonitorTypes.None + } + }; + + authorsToAdd.Add(toAdd); + + return toAdd; } public void Execute(ImportListSyncCommand message) diff --git a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrAPIResource.cs b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrAPIResource.cs index 7de0c94d5..d3c040ba6 100644 --- a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrAPIResource.cs +++ b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrAPIResource.cs @@ -13,6 +13,26 @@ public class ReadarrAuthor public HashSet Tags { get; set; } } + public class ReadarrEdition + { + public string Title { get; set; } + public string ForeignEditionId { get; set; } + public string Overview { get; set; } + public List Images { get; set; } + public bool Monitored { get; set; } + } + + public class ReadarrBook + { + public string Title { get; set; } + public string ForeignBookId { get; set; } + public string Overview { get; set; } + public List Images { get; set; } + public bool Monitored { get; set; } + public ReadarrAuthor Author { get; set; } + public List Editions { get; set; } + } + public class ReadarrProfile { public string Name { get; set; } diff --git a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs index f7aaa4ebf..bbf7c520a 100644 --- a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs +++ b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs @@ -30,21 +30,25 @@ public ReadarrImport(IReadarrV1Proxy readarrV1Proxy, public override IList Fetch() { - var authors = new List(); + var authorsAndBooks = new List(); try { - var remoteAuthors = _readarrV1Proxy.GetAuthors(Settings); + var remoteBooks = _readarrV1Proxy.GetBooks(Settings); - foreach (var remoteAuthor in remoteAuthors) + foreach (var remoteBook in remoteBooks) { - if ((!Settings.ProfileIds.Any() || Settings.ProfileIds.Contains(remoteAuthor.QualityProfileId)) && - (!Settings.TagIds.Any() || Settings.TagIds.Any(x => remoteAuthor.Tags.Any(y => y == x)))) + if ((!Settings.ProfileIds.Any() || Settings.ProfileIds.Contains(remoteBook.Author.QualityProfileId)) && + (!Settings.TagIds.Any() || Settings.TagIds.Any(x => remoteBook.Author.Tags.Any(y => y == x))) && + remoteBook.Monitored && remoteBook.Author.Monitored) { - authors.Add(new ImportListItemInfo + authorsAndBooks.Add(new ImportListItemInfo { - AuthorGoodreadsId = remoteAuthor.ForeignAuthorId, - Author = remoteAuthor.AuthorName + BookGoodreadsId = remoteBook.ForeignBookId, + Book = remoteBook.Title, + EditionGoodreadsId = remoteBook.Editions.Single(x => x.Monitored).ForeignEditionId, + Author = remoteBook.Author.AuthorName, + AuthorGoodreadsId = remoteBook.Author.ForeignAuthorId }); } } @@ -56,7 +60,7 @@ public override IList Fetch() _importListStatusService.RecordFailure(Definition.Id); } - return CleanupListItems(authors); + return CleanupListItems(authorsAndBooks); } public override object RequestAction(string action, IDictionary query) diff --git a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrV1Proxy.cs b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrV1Proxy.cs index 49e299f2c..c2535c2dc 100644 --- a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrV1Proxy.cs +++ b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrV1Proxy.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.ImportLists.Readarr public interface IReadarrV1Proxy { List GetAuthors(ReadarrSettings settings); + List GetBooks(ReadarrSettings settings); List GetProfiles(ReadarrSettings settings); List GetTags(ReadarrSettings settings); ValidationFailure Test(ReadarrSettings settings); @@ -33,6 +34,11 @@ public List GetAuthors(ReadarrSettings settings) return Execute("/api/v1/author", settings); } + public List GetBooks(ReadarrSettings settings) + { + return Execute("/api/v1/book", settings); + } + public List GetProfiles(ReadarrSettings settings) { return Execute("/api/v1/qualityprofile", settings);