diff --git a/src/Chaptarr.Core.Test/Api/CalendarFeedControllerFixture.cs b/src/Chaptarr.Core.Test/Api/CalendarFeedControllerFixture.cs index 0be38edb..0e086d76 100644 --- a/src/Chaptarr.Core.Test/Api/CalendarFeedControllerFixture.cs +++ b/src/Chaptarr.Core.Test/Api/CalendarFeedControllerFixture.cs @@ -203,7 +203,7 @@ private sealed class StubEditionService : IEditionService public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/AddBookServiceAutoEditionSelectionFixture.cs b/src/Chaptarr.Core.Test/Books/AddBookServiceAutoEditionSelectionFixture.cs index b919e77e..56e5bcb1 100644 --- a/src/Chaptarr.Core.Test/Books/AddBookServiceAutoEditionSelectionFixture.cs +++ b/src/Chaptarr.Core.Test/Books/AddBookServiceAutoEditionSelectionFixture.cs @@ -275,7 +275,7 @@ public List SetMonitored(Edition edition, bool isManualSelection = fals public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceMonitoredEditionSelectionFixture.cs b/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceMonitoredEditionSelectionFixture.cs index b3d3f44c..c0bf5e81 100644 --- a/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceMonitoredEditionSelectionFixture.cs +++ b/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceMonitoredEditionSelectionFixture.cs @@ -103,7 +103,7 @@ public void UpdateMany(List editions) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void DeleteMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceSpecificBookMonitoringFixture.cs b/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceSpecificBookMonitoringFixture.cs index 3fa690de..7f476725 100644 --- a/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceSpecificBookMonitoringFixture.cs +++ b/src/Chaptarr.Core.Test/Books/AuthorLibraryServiceSpecificBookMonitoringFixture.cs @@ -215,7 +215,7 @@ public void UpdateMany(List editions) public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void DeleteMany(List editions) => throw new NotImplementedException(); public List GetEditionsForRefresh(int bookId) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/BookListQueriesFixture.cs b/src/Chaptarr.Core.Test/Books/BookListQueriesFixture.cs new file mode 100644 index 00000000..005f99ea --- /dev/null +++ b/src/Chaptarr.Core.Test/Books/BookListQueriesFixture.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; +using System.Linq; +using Dapper; +using Microsoft.Data.Sqlite; +using NUnit.Framework; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Books; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace Chaptarr.Core.Test.Books +{ + [TestFixture] + public class BookListQueriesFixture + { + private sealed class StubEventAggregator : IEventAggregator + { + public void PublishEvent(TEvent @event) + where TEvent : class, IEvent + { + } + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + if (TableMapping.Mapper.TableMap.Count == 0) + { + TableMapping.Map(); + } + } + + [TestCase(BookMediaType.Audiobook, new[] { 1 })] + [TestCase(BookMediaType.Ebook, new[] { 2 })] + public void book_list_editions_should_be_the_monitored_editions_of_the_requested_media_type(BookMediaType mediaType, int[] expectedEditionIds) + { + WithDatabase(database => + { + var repository = new EditionRepository(database, new StubEventAggregator()); + + var editions = repository.GetMonitoredEditionsForBookList(mediaType); + + Assert.That(editions.Select(e => e.Id).OrderBy(id => id), Is.EqualTo(expectedEditionIds)); + }); + } + + [Test] + public void book_list_editions_without_media_type_should_include_both_media_types() + { + WithDatabase(database => + { + var repository = new EditionRepository(database, new StubEventAggregator()); + + var editions = repository.GetMonitoredEditionsForBookList(null); + + Assert.That(editions.Select(e => e.Id).OrderBy(id => id), Is.EqualTo(new[] { 1, 2 })); + }); + } + + [Test] + public void book_list_editions_should_not_load_chapters() + { + WithDatabase(database => + { + var repository = new EditionRepository(database, new StubEventAggregator()); + + var edition = repository.GetMonitoredEditionsForBookList(BookMediaType.Audiobook).Single(); + + Assert.That(edition.Title, Is.EqualTo("Audiobook Edition")); + Assert.That(edition.BookId, Is.EqualTo(1)); + Assert.That(edition.Monitored, Is.True); + Assert.That(edition.Chapters, Is.Null.Or.Empty); + }); + } + + [TestCase(BookMediaType.Audiobook, new[] { 1 })] + [TestCase(BookMediaType.Ebook, new[] { 2 })] + public void books_by_media_type_should_return_only_that_media_type(BookMediaType mediaType, int[] expectedBookIds) + { + WithDatabase(database => + { + var repository = new BookRepository(database, new StubEventAggregator()); + + var books = repository.GetBooksByMediaType(mediaType); + + Assert.That(books.Select(b => b.Id), Is.EqualTo(expectedBookIds)); + }); + } + + private static void WithDatabase(Action action) + { + var databasePath = Path.Combine(TestContext.CurrentContext.WorkDirectory, $"book_list_queries_{Guid.NewGuid():N}.db"); + var connectionString = new SqliteConnectionStringBuilder + { + DataSource = databasePath, + Mode = SqliteOpenMode.ReadWriteCreate + }.ToString(); + + try + { + using (var connection = new SqliteConnection(connectionString)) + { + connection.Open(); + CreateSchema(connection); + CreateSchema(connection); + CreateSchema(connection); + + connection.Execute("INSERT INTO \"Authors\" (\"Id\") VALUES (1);"); + connection.Execute("INSERT INTO \"Books\" (\"Id\", \"AuthorId\", \"Title\", \"MediaType\") VALUES " + + "(1, 1, 'Foundation', 0), " + + "(2, 1, 'Foundation', 1);"); + connection.Execute("INSERT INTO \"Editions\" (\"Id\", \"BookId\", \"Title\", \"Monitored\", \"Chapters\") VALUES " + + "(1, 1, 'Audiobook Edition', 1, '[{\"title\":\"Chapter 1\"}]'), " + + "(2, 2, 'Ebook Edition', 1, '[{\"title\":\"Chapter 1\"}]'), " + + "(3, 2, 'Unmonitored Ebook Edition', 0, NULL);"); + } + + var database = new Database("main", () => + { + var conn = new SqliteConnection(connectionString); + conn.Open(); + return conn; + }); + + action(new MainDatabase(database)); + } + finally + { + try + { + if (File.Exists(databasePath)) + { + File.Delete(databasePath); + } + } + catch + { + } + } + } + + private static void CreateSchema(SqliteConnection connection) + where T : ModelBase + { + var excluded = TableMapping.Mapper.ExcludeProperties(typeof(T)) + .Select(property => property.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + var tableName = TableMapping.Mapper.TableNameMapping(typeof(T)); + var columns = typeof(T) + .GetProperties() + .Where(property => property.Name == nameof(ModelBase.Id) || (property.IsMappableProperty() && !excluded.Contains(property.Name))) + .GroupBy(property => property.Name, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .Select(property => property.Name == nameof(ModelBase.Id) + ? "\"Id\" INTEGER PRIMARY KEY AUTOINCREMENT" + : $"\"{property.Name}\" {GetSqliteColumnType(property)} NULL") + .ToList(); + + connection.Execute($"CREATE TABLE \"{tableName}\" ({string.Join(", ", columns)});"); + } + + private static string GetSqliteColumnType(System.Reflection.PropertyInfo property) + { + var type = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; + + if (type.IsEnum || type == typeof(int) || type == typeof(long) || type == typeof(bool)) + { + return "INTEGER"; + } + + return "TEXT"; + } + } +} diff --git a/src/Chaptarr.Core.Test/Books/BookServiceActiveEditionSelectionFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceActiveEditionSelectionFixture.cs index 0aa23ccd..d5f999f8 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceActiveEditionSelectionFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceActiveEditionSelectionFixture.cs @@ -122,7 +122,7 @@ public List SetMonitored(Edition edition, bool isManualSelection = fals public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) { LoadedAllMonitoredEditions = true; return _editions.Where(e => e.Monitored).ToList(); diff --git a/src/Chaptarr.Core.Test/Books/BookServiceFormatMonitoringSyncFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceFormatMonitoringSyncFixture.cs index 6eaff1b3..e7087afd 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceFormatMonitoringSyncFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceFormatMonitoringSyncFixture.cs @@ -163,7 +163,7 @@ public List GetEditionsByBook(IEnumerable bookIds) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/BookServiceGetBooksLoadsEditionsFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceGetBooksLoadsEditionsFixture.cs index 39833b68..335b6e2b 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceGetBooksLoadsEditionsFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceGetBooksLoadsEditionsFixture.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; +using NzbDrone.Core.RootFolders; namespace Chaptarr.Core.Test.Books { @@ -100,6 +101,11 @@ public IEnumerable FindExisting(IEnumerable ids) public void UpdateMonitoringByAuthorAndMediaType(int authorId, BookMediaType mediaType, bool monitored, IEnumerable exceptBookIds = null) => throw new NotImplementedException(); public BookBucketResource GetBookBuckets(string sortKey, string sortDirection, bool includeUnmonitored = false, string mediaType = null, bool? downloaded = null) => throw new NotImplementedException(); public PagedBookResource GetBooksPaged(int offset, int pageSize, string sortKey, string sortDirection, bool includeUnmonitored = false, string mediaType = null, bool? downloaded = null) => throw new NotImplementedException(); + + public List GetBooksByMediaType(BookMediaType mediaType) + { + return _booksById.Values.Where(b => b.MediaType == mediaType).ToList(); + } } private sealed class StubEditionService : IEditionService @@ -126,7 +132,13 @@ public List GetEditionsByBook(IEnumerable bookIds) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public BookMediaType? BookListMediaType { get; private set; } + + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) + { + BookListMediaType = mediaType; + return _editions.Where(e => e.Monitored).ToList(); + } public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); @@ -140,6 +152,28 @@ public List GetEditionsByBook(IEnumerable bookIds) public List SetMonitored(Edition edition, bool isManualSelection = false) => throw new NotImplementedException(); } + private sealed class StubRootFolderService : IRootFolderService + { + private readonly List _rootFolders; + + public StubRootFolderService(params RootFolder[] rootFolders) + { + _rootFolders = rootFolders.ToList(); + } + + public List All() => _rootFolders; + public List AllWithSpaceStats() => throw new NotImplementedException(); + public RootFolder Add(RootFolder rootFolder) => throw new NotImplementedException(); + public RootFolder Update(RootFolder rootFolder) => throw new NotImplementedException(); + public void Remove(int id) => throw new NotImplementedException(); + public RootFolder Get(int id) => throw new NotImplementedException(); + public List AllForTag(int tagId) => throw new NotImplementedException(); + public RootFolder GetBestRootFolder(string path) => throw new NotImplementedException(); + public RootFolder GetBestRootFolder(string path, List allRootFolders) => throw new NotImplementedException(); + public string GetBestRootFolderPath(string path) => throw new NotImplementedException(); + public string GetBestRootFolderPath(string path, List allRootFolders) => throw new NotImplementedException(); + } + private sealed class StubAuthorService : IAuthorService { private readonly Dictionary _authors; @@ -350,5 +384,42 @@ public void should_batch_load_files_for_book_in_edition_iteration_order() Assert.That(result.Editions[1].BookFiles.Select(file => file.Id), Is.EqualTo(new[] { 300 })); Assert.That(result.Editions[2].BookFiles, Is.Null); } + + [TestCase("audiobook", BookMediaType.Audiobook, 1, 10)] + [TestCase("ebook", BookMediaType.Ebook, 2, 20)] + public void general_display_list_should_load_only_the_requested_media_type(string mediaType, BookMediaType expectedMediaType, int expectedBookId, int expectedEditionId) + { + var author = new Author { Id = 10, Name = "Isaac Asimov" }; + var books = new[] + { + new Book { Id = 1, AuthorId = 10, Title = "Foundation", MediaType = BookMediaType.Audiobook }, + new Book { Id = 2, AuthorId = 10, Title = "Foundation", MediaType = BookMediaType.Ebook } + }; + var editions = new[] + { + new Edition { Id = 10, BookId = 1, Title = "Foundation", Monitored = true }, + new Edition { Id = 20, BookId = 2, Title = "Foundation", Monitored = true }, + new Edition { Id = 21, BookId = 2, Title = "Foundation", Monitored = false } + }; + var editionService = new StubEditionService(editions); + + // The stub repository throws from All(), so loading the whole library fails the test. + var service = new BookService( + new StubBookRepository(books), + editionService, + eventAggregator: null, + authorService: new StubAuthorService(new[] { author }), + mediaFileService: null, + rootFolderService: new StubRootFolderService(new RootFolder { FolderType = FolderType.Mixed }), + new StubSeriesBookLinkRepository(), + multiCopySeriesService: null, + logger: LogManager.GetCurrentClassLogger()); + + var results = service.GetBooksForDisplay(authorId: null, mediaType: mediaType); + + Assert.That(results.Select(book => book.Id), Is.EqualTo(new[] { expectedBookId })); + Assert.That(results[0].Editions.Select(edition => edition.Id), Is.EqualTo(new[] { expectedEditionId })); + Assert.That(editionService.BookListMediaType, Is.EqualTo(expectedMediaType)); + } } } diff --git a/src/Chaptarr.Core.Test/Books/BookServiceLookupHydrationFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceLookupHydrationFixture.cs index 2280afdc..1af51081 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceLookupHydrationFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceLookupHydrationFixture.cs @@ -127,7 +127,7 @@ private sealed class StubEditionService : IEditionService public Edition GetEditionByGoodreadsEditionId(long goodreadsEditionId) => throw new NotImplementedException(); public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/BookServiceProviderIdLookupFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceProviderIdLookupFixture.cs index 9bba989f..46121cae 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceProviderIdLookupFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceProviderIdLookupFixture.cs @@ -121,7 +121,7 @@ public void AddProviderMatch(string provider, string providerId, params Edition[ public Edition GetEditionByGoodreadsEditionId(long goodreadsEditionId) => throw new NotImplementedException(); public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/BookServiceUpdateBookRelinksPinnedEditionFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceUpdateBookRelinksPinnedEditionFixture.cs index 60c89888..c1442908 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceUpdateBookRelinksPinnedEditionFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceUpdateBookRelinksPinnedEditionFixture.cs @@ -163,7 +163,7 @@ public List GetEditionsByBook(IEnumerable bookIds) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => UpdatedEditions.AddRange(editions); diff --git a/src/Chaptarr.Core.Test/Books/BookServiceWantedNarratorSeriesLinksFixture.cs b/src/Chaptarr.Core.Test/Books/BookServiceWantedNarratorSeriesLinksFixture.cs index 8ffa50e7..fe2e6db4 100644 --- a/src/Chaptarr.Core.Test/Books/BookServiceWantedNarratorSeriesLinksFixture.cs +++ b/src/Chaptarr.Core.Test/Books/BookServiceWantedNarratorSeriesLinksFixture.cs @@ -148,7 +148,7 @@ public List SetMonitored(Edition edition, bool isManualSelection = fals public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); public void DeleteMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/EditionLegacySingularLookupLoggingFixture.cs b/src/Chaptarr.Core.Test/Books/EditionLegacySingularLookupLoggingFixture.cs index 1df791a1..073d5bda 100644 --- a/src/Chaptarr.Core.Test/Books/EditionLegacySingularLookupLoggingFixture.cs +++ b/src/Chaptarr.Core.Test/Books/EditionLegacySingularLookupLoggingFixture.cs @@ -68,7 +68,7 @@ private sealed class StubEditionRepository : IEditionRepository public Edition Single() => throw new NotImplementedException(); public Edition SingleOrDefault() => throw new NotImplementedException(); public PagingSpec GetPaged(PagingSpec pagingSpec) => throw new NotImplementedException(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public Edition FindByForeignEditionId(string foreignEditionId) => throw new NotImplementedException(); public List FindAllByForeignEditionId(string foreignEditionId) => throw new NotImplementedException(); public Edition FindByGoodreadsEditionId(long goodreadsEditionId) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceActiveEditionSelectionFixture.cs b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceActiveEditionSelectionFixture.cs index 01945b90..1f97697d 100644 --- a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceActiveEditionSelectionFixture.cs +++ b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceActiveEditionSelectionFixture.cs @@ -90,7 +90,7 @@ public List SetMonitored(Edition edition, bool isManualSelection = fals public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) { diff --git a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceAddChildrenFixture.cs b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceAddChildrenFixture.cs index e8ab86ec..7028442b 100644 --- a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceAddChildrenFixture.cs +++ b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceAddChildrenFixture.cs @@ -109,7 +109,7 @@ public List SetMonitored(Edition edition, bool isManualSelection) public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void DeleteMany(List editions) => throw new NotImplementedException(); public List GetEditionsForRefresh(int bookId) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceUnmergeSplitFixture.cs b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceUnmergeSplitFixture.cs index 19b0bca3..8deed233 100644 --- a/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceUnmergeSplitFixture.cs +++ b/src/Chaptarr.Core.Test/Books/RefreshAuthorServiceUnmergeSplitFixture.cs @@ -142,7 +142,7 @@ public void UpdateMany(List editions) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void DeleteMany(List editions) diff --git a/src/Chaptarr.Core.Test/Books/UpdateEditionSelectionOnMetadataProfileChangeServiceFixture.cs b/src/Chaptarr.Core.Test/Books/UpdateEditionSelectionOnMetadataProfileChangeServiceFixture.cs index 909fcf8a..b0567c93 100644 --- a/src/Chaptarr.Core.Test/Books/UpdateEditionSelectionOnMetadataProfileChangeServiceFixture.cs +++ b/src/Chaptarr.Core.Test/Books/UpdateEditionSelectionOnMetadataProfileChangeServiceFixture.cs @@ -140,7 +140,7 @@ public void UpdateMany(List editions) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void DeleteMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/ImportLists/ImportListSyncServiceMappingFixture.cs b/src/Chaptarr.Core.Test/ImportLists/ImportListSyncServiceMappingFixture.cs index e95a1d49..6df52430 100644 --- a/src/Chaptarr.Core.Test/ImportLists/ImportListSyncServiceMappingFixture.cs +++ b/src/Chaptarr.Core.Test/ImportLists/ImportListSyncServiceMappingFixture.cs @@ -67,7 +67,7 @@ private sealed class StubEditionService : IEditionService public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => null; public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => null; public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); @@ -103,7 +103,7 @@ public Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => null; public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => null; public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/MediaFiles/BookImport/FormatScopedUnitCloneFixture.cs b/src/Chaptarr.Core.Test/MediaFiles/BookImport/FormatScopedUnitCloneFixture.cs index ad39f25c..7c6754dc 100644 --- a/src/Chaptarr.Core.Test/MediaFiles/BookImport/FormatScopedUnitCloneFixture.cs +++ b/src/Chaptarr.Core.Test/MediaFiles/BookImport/FormatScopedUnitCloneFixture.cs @@ -218,7 +218,7 @@ public void InsertMany(List editions) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) { InsertMany(editions); diff --git a/src/Chaptarr.Core.Test/MediaFiles/BookImport/HolyGrailSmokeTestFixture.cs b/src/Chaptarr.Core.Test/MediaFiles/BookImport/HolyGrailSmokeTestFixture.cs index d3cfec33..adbb7f67 100644 --- a/src/Chaptarr.Core.Test/MediaFiles/BookImport/HolyGrailSmokeTestFixture.cs +++ b/src/Chaptarr.Core.Test/MediaFiles/BookImport/HolyGrailSmokeTestFixture.cs @@ -408,7 +408,7 @@ public List FindAllByAsin(string asin, BookMediaType? mediaType) public Edition Single() => throw new NotImplementedException(); public Edition SingleOrDefault() => throw new NotImplementedException(); public PagingSpec GetPaged(PagingSpec pagingSpec) => throw new NotImplementedException(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public Edition FindByForeignEditionId(string foreignEditionId) => throw new NotImplementedException(); public List FindAllByForeignEditionId(string foreignEditionId) => throw new NotImplementedException(); public Edition FindByHardcoverEditionId(string hardcoverEditionId) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/MediaFiles/BookImport/LogicalWorkGroupingFixture.cs b/src/Chaptarr.Core.Test/MediaFiles/BookImport/LogicalWorkGroupingFixture.cs index 0ac2347c..8d7de071 100644 --- a/src/Chaptarr.Core.Test/MediaFiles/BookImport/LogicalWorkGroupingFixture.cs +++ b/src/Chaptarr.Core.Test/MediaFiles/BookImport/LogicalWorkGroupingFixture.cs @@ -153,7 +153,7 @@ public List GetEditions(IEnumerable ids) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => _editionsById.Values.FirstOrDefault(e => string.Equals(e.OpenLibraryEditionId, openLibraryEditionId, StringComparison.OrdinalIgnoreCase)); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => _editionsById.Values.Where(e => e.Monitored).ToList(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => _editionsById.Values.Where(e => e.Monitored).ToList(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/MediaFiles/BookImport/MatchingProvenancePopulationFixture.cs b/src/Chaptarr.Core.Test/MediaFiles/BookImport/MatchingProvenancePopulationFixture.cs index ee5cad26..7f047b84 100644 --- a/src/Chaptarr.Core.Test/MediaFiles/BookImport/MatchingProvenancePopulationFixture.cs +++ b/src/Chaptarr.Core.Test/MediaFiles/BookImport/MatchingProvenancePopulationFixture.cs @@ -216,7 +216,7 @@ public List GetEditions(IEnumerable ids) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/Chaptarr.Core.Test/Profiles/Metadata/MetadataProfileServiceMediaTypeFilteringFixture.cs b/src/Chaptarr.Core.Test/Profiles/Metadata/MetadataProfileServiceMediaTypeFilteringFixture.cs index c0b7471d..164883c8 100644 --- a/src/Chaptarr.Core.Test/Profiles/Metadata/MetadataProfileServiceMediaTypeFilteringFixture.cs +++ b/src/Chaptarr.Core.Test/Profiles/Metadata/MetadataProfileServiceMediaTypeFilteringFixture.cs @@ -182,7 +182,7 @@ public StubEditionService(List editions) public Edition GetEditionByOpenLibraryEditionId(string openLibraryEditionId) => throw new NotImplementedException(); public Edition GetEditionByProviderAndId(string providerPrefix, string providerId) => throw new NotImplementedException(); public System.Collections.Generic.List GetEditionsByProviderAndId(string providerPrefix, string providerId) => new System.Collections.Generic.List(); - public List GetAllMonitoredEditions() => throw new NotImplementedException(); + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) => throw new NotImplementedException(); public void InsertMany(List editions) => throw new NotImplementedException(); public void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction) => throw new NotImplementedException(); public void UpdateMany(List editions) => throw new NotImplementedException(); diff --git a/src/NzbDrone.Core/Books/Repositories/BookRepository.cs b/src/NzbDrone.Core/Books/Repositories/BookRepository.cs index 50726452..03be179c 100644 --- a/src/NzbDrone.Core/Books/Repositories/BookRepository.cs +++ b/src/NzbDrone.Core/Books/Repositories/BookRepository.cs @@ -26,6 +26,7 @@ public interface IBookRepository : IBasicRepository List GetLastBooks(IEnumerable authorIds); List GetNextBooks(IEnumerable authorIds); List GetBooksByAuthorId(int authorId); + List GetBooksByMediaType(BookMediaType mediaType) => throw new NotImplementedException(); List GetBooksForRefresh(int authorId, IEnumerable providerIds); List GetBooksByFileIds(IEnumerable fileIds); Book FindByTitle(int authorId, string title); @@ -198,6 +199,11 @@ public List GetBooksByAuthorId(int authorId) return Query(s => s.AuthorId == authorId); } + public List GetBooksByMediaType(BookMediaType mediaType) + { + return Query(s => s.MediaType == mediaType); + } + public List GetBooksForRefresh(int authorId, IEnumerable providerIds) { // Refresh must be author-scoped. Provider-ID lookups are handled downstream by the diff --git a/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs b/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs index 2636b619..0b92779c 100644 --- a/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs +++ b/src/NzbDrone.Core/Books/Repositories/EditionRepository.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Books { public interface IEditionRepository : IBasicRepository { - List GetAllMonitoredEditions(); + List GetMonitoredEditionsForBookList(BookMediaType? mediaType); Edition FindByForeignEditionId(string foreignEditionId); List FindAllByForeignEditionId(string foreignEditionId); Edition FindByHardcoverEditionId(string hardcoverEditionId); @@ -41,18 +41,47 @@ public interface IEditionRepository : IBasicRepository public class EditionRepository : BasicRepository, IEditionRepository { private readonly Logger _logger; + private readonly string _bookListColumns; public EditionRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { _logger = LogManager.GetCurrentClassLogger(); + _bookListColumns = BuildBookListColumns(); } - public List GetAllMonitoredEditions() + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) { // WhereBuilderSqlite requires a concrete value; avoid boolean-expression simplification. var monitored = true; - return Query(x => x.Monitored == monitored); + var builder = Builder() + .Select(_bookListColumns) + .Where(e => e.Monitored == monitored); + + if (mediaType.HasValue) + { + var bookMediaType = mediaType.Value; + builder = builder.Join((e, b) => e.BookId == b.Id) + .Where(b => b.MediaType == bookMediaType); + } + + var sql = builder.AddSelectTemplate(typeof(Edition)); + return _database.Query(sql.RawSql, sql.Parameters).ToList(); + } + + // Book lists never read Chapters, and on large libraries it is most of the Editions table. + private string BuildBookListColumns() + { + var excluded = TableMapping.Mapper.ExcludeProperties(typeof(Edition)) + .Select(x => x.Name) + .ToHashSet(); + excluded.Add(nameof(Edition.Chapters)); + + var columns = typeof(Edition).GetProperties() + .Where(x => x.IsMappableProperty() && !excluded.Contains(x.Name)) + .Select(x => $"\"{_table}\".\"{x.Name}\""); + + return string.Join(", ", columns); } public int CountMissingMatchingTitles() diff --git a/src/NzbDrone.Core/Books/Services/BookService.cs b/src/NzbDrone.Core/Books/Services/BookService.cs index 39f75f8e..8defd269 100644 --- a/src/NzbDrone.Core/Books/Services/BookService.cs +++ b/src/NzbDrone.Core/Books/Services/BookService.cs @@ -2222,25 +2222,11 @@ public List GetBooksForDisplay(int? authorId = null, string mediaType = nu { _logger.Debug("[DISPLAY-DEBUG] GetBooksForDisplay called with authorId={0}, mediaType={1}", authorId, mediaType); - List allBooks; + BookMediaType? targetMediaType = null; - if (authorId.HasValue) - { - allBooks = GetBooksByAuthor(authorId.Value); - _logger.Debug("[DISPLAY-DEBUG] GetBooksByAuthor returned {0} books for author {1}", allBooks.Count, authorId.Value); - } - else - { - allBooks = GetAllBooks(); - _logger.Debug("[DISPLAY-DEBUG] GetAllBooks returned {0} books", allBooks.Count); - } - - // NEW DUAL-INSTANCE LOGIC: Filter by MediaType enum if specified if (!string.IsNullOrEmpty(mediaType)) { - var targetMediaType = mediaType.ToLower() == "ebook" ? BookMediaType.Ebook : BookMediaType.Audiobook; - allBooks = allBooks.Where(b => b.MediaType == targetMediaType).ToList(); - _logger.Debug("[DISPLAY-DEBUG] Filtered to {0} books with MediaType={1}", allBooks.Count, targetMediaType); + targetMediaType = mediaType.ToLower() == "ebook" ? BookMediaType.Ebook : BookMediaType.Audiobook; // CRITICAL: Check if root folder exists for this media type // If no root folder, return empty list (GUI shows nothing) @@ -2256,6 +2242,32 @@ public List GetBooksForDisplay(int? authorId = null, string mediaType = nu } } + List allBooks; + + if (authorId.HasValue) + { + allBooks = GetBooksByAuthor(authorId.Value); + _logger.Debug("[DISPLAY-DEBUG] GetBooksByAuthor returned {0} books for author {1}", allBooks.Count, authorId.Value); + + if (targetMediaType.HasValue) + { + allBooks = allBooks.Where(b => b.MediaType == targetMediaType.Value).ToList(); + _logger.Debug("[DISPLAY-DEBUG] Filtered to {0} books with MediaType={1}", allBooks.Count, targetMediaType.Value); + } + } + else if (targetMediaType.HasValue) + { + // The general list can be the whole library, so filter in SQL. Series links are + // left to the API mapper, which loads them for the books it returns. + allBooks = _bookRepository.GetBooksByMediaType(targetMediaType.Value); + _logger.Debug("[DISPLAY-DEBUG] GetBooksByMediaType returned {0} books with MediaType={1}", allBooks.Count, targetMediaType.Value); + } + else + { + allBooks = _bookRepository.All().ToList(); + _logger.Debug("[DISPLAY-DEBUG] Loaded all {0} books", allBooks.Count); + } + // Bulk API/index sync path is intentionally lean: one monitored edition per book, no files. // Author-scoped detail paths stay rich because the UI needs all editions/files there. if (allBooks.Any()) @@ -2264,7 +2276,7 @@ public List GetBooksForDisplay(int? authorId = null, string mediaType = nu var bookIdSet = bookIds.ToHashSet(); var allEditions = authorId.HasValue ? _editionService.GetEditionsByBook(bookIds) - : _editionService.GetAllMonitoredEditions().Where(e => e != null && bookIdSet.Contains(e.BookId)).ToList(); + : _editionService.GetMonitoredEditionsForBookList(targetMediaType).Where(e => e != null && bookIdSet.Contains(e.BookId)).ToList(); var editionsByBook = allEditions.GroupBy(e => e.BookId).ToDictionary(g => g.Key, g => g.ToList()); var filesByEdition = new Dictionary>(); diff --git a/src/NzbDrone.Core/Books/Services/EditionService.cs b/src/NzbDrone.Core/Books/Services/EditionService.cs index f71a396b..6d8e9417 100644 --- a/src/NzbDrone.Core/Books/Services/EditionService.cs +++ b/src/NzbDrone.Core/Books/Services/EditionService.cs @@ -24,7 +24,7 @@ public interface IEditionService Edition GetEditionByGoogleBooksEditionId(string googleBooksEditionId); Edition GetEditionByProviderAndId(string providerPrefix, string providerId); List GetEditionsByProviderAndId(string providerPrefix, string providerId); - List GetAllMonitoredEditions(); + List GetMonitoredEditionsForBookList(BookMediaType? mediaType); void InsertMany(List editions); void InsertMany(List editions, IDbConnection connection, IDbTransaction transaction); void UpdateMany(List editions); @@ -220,9 +220,9 @@ private static List DistinctEditions(params IEnumerable[] grou .ToList(); } - public List GetAllMonitoredEditions() + public List GetMonitoredEditionsForBookList(BookMediaType? mediaType) { - return _editionRepository.GetAllMonitoredEditions(); + return _editionRepository.GetMonitoredEditionsForBookList(mediaType); } public void InsertMany(List editions)