diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt index 8ab61f224..a712d3b76 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt @@ -1,7 +1,10 @@ package org.gotson.komga.domain.model import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator +import org.hibernate.annotations.Cache +import org.hibernate.annotations.CacheConcurrencyStrategy import java.util.* +import javax.persistence.Cacheable import javax.persistence.CollectionTable import javax.persistence.Column import javax.persistence.ElementCollection @@ -21,6 +24,8 @@ private val natSortComparator: Comparator = CaseInsensitiveSimpleNatural @Entity @Table(name = "book_metadata") +@Cacheable +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "cache.bookmetadata") class BookMetadata( @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) @@ -43,9 +48,10 @@ class BookMetadata( @OneToOne(optional = false, fetch = FetchType.LAZY, mappedBy = "metadata") lateinit var book: Book - @ElementCollection(fetch = FetchType.EAGER) + @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")]) @OrderColumn(name = "number") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "cache.bookmetadata.collection.pages") private var _pages: MutableList = mutableListOf() var pages: List diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt index cb1c24da3..1cd8aa606 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt @@ -51,6 +51,7 @@ class Series( lateinit var library: Library @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "series") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "cache.series.collection.books") private var _books: MutableList = mutableListOf() var books: List diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt index 2472abfb3..bddbbf34c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt @@ -3,7 +3,6 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookMetadata import org.gotson.komga.domain.model.Library -import org.gotson.komga.domain.model.Series import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository @@ -20,6 +19,4 @@ interface BookRepository : JpaRepository, JpaSpecificationExecutor fun findAllByMetadataThumbnailIsNull(): List fun findBySeriesLibraryIn(seriesLibrary: Collection, pageable: Pageable): Page - - fun countBySeries(series: Series): Int } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt index 249b24431..f4b169ddb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt @@ -19,4 +19,5 @@ interface SeriesRepository : JpaRepository, JpaSpecificationExecut fun findByLibraryIdAndUrlNotIn(libraryId: Long, urls: Collection): List fun findByLibraryIdAndUrl(libraryId: Long, url: URL): Series? fun deleteByLibraryId(libraryId: Long) + fun findByLibraryIdIn(libraryIDs: Collection): List } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/BookController.kt index 6e978e85e..7ef63af4a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/BookController.kt @@ -62,19 +62,25 @@ class BookController( ) return mutableListOf>().let { specs -> - if (!principal.user.sharedAllLibraries) { - specs.add(Book::series.`in`(seriesRepository.findByLibraryIn(principal.user.sharedLibraries))) + when { + !principal.user.sharedAllLibraries && !libraryIds.isNullOrEmpty() -> { + val authorizedLibraryIDs = libraryIds.intersect(principal.user.sharedLibraries.map { it.id }) + if (authorizedLibraryIDs.isEmpty()) return@let Page.empty(pageRequest) + else specs.add(Book::series.`in`(seriesRepository.findByLibraryIdIn(authorizedLibraryIDs))) + } + + !principal.user.sharedAllLibraries -> specs.add(Book::series.`in`(seriesRepository.findByLibraryIn(principal.user.sharedLibraries))) + + !libraryIds.isNullOrEmpty() -> { + val libraries = libraryRepository.findAllById(libraryIds) + specs.add(Book::series.`in`(seriesRepository.findByLibraryIn(libraries))) + } } if (!searchTerm.isNullOrEmpty()) { specs.add(Book::name.likeLower("%$searchTerm%")) } - if (!libraryIds.isNullOrEmpty()) { - val libraries = libraryRepository.findAllById(libraryIds) - specs.add(Book::series.`in`(seriesRepository.findByLibraryIn(libraries))) - } - if (specs.isNotEmpty()) { bookRepository.findAll(specs.reduce { acc, spec -> acc.and(spec)!! }, pageRequest) } else { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/Dto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/Dto.kt index a21cd6311..d0d25fc92 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/Dto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/Dto.kt @@ -17,13 +17,13 @@ data class SeriesDto( val booksCount: Int ) -fun Series.toDto(booksCount: Int) = SeriesDto( +fun Series.toDto() = SeriesDto( id = id, libraryId = library.id, name = name, url = url.toString(), lastModified = lastModifiedDate?.toUTC(), - booksCount = booksCount + booksCount = books.size ) data class BookDto( diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/SeriesController.kt index f3575c81d..63db79587 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/rest/SeriesController.kt @@ -63,8 +63,8 @@ class SeriesController( !principal.user.sharedAllLibraries -> specs.add(Series::library.`in`(principal.user.sharedLibraries)) !libraryIds.isNullOrEmpty() -> { - val values = libraryRepository.findAllById(libraryIds) - specs.add(Series::library.`in`(values)) + val libraries = libraryRepository.findAllById(libraryIds) + specs.add(Series::library.`in`(libraries)) } } @@ -77,7 +77,7 @@ class SeriesController( } else { seriesRepository.findAll(pageRequest) } - }.map { it.toDto(bookRepository.countBySeries(it)) } + }.map { it.toDto() } } @GetMapping("/latest") @@ -95,7 +95,7 @@ class SeriesController( seriesRepository.findAll(pageRequest) } else { seriesRepository.findByLibraryIn(principal.user.sharedLibraries, pageRequest) - }.map { it.toDto(bookRepository.countBySeries(it)) } + }.map { it.toDto() } } @GetMapping("{seriesId}") @@ -105,7 +105,7 @@ class SeriesController( ): SeriesDto = seriesRepository.findByIdOrNull(id)?.let { if (!principal.user.canAccessSeries(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) - it.toDto(bookRepository.countBySeries(it)) + it.toDto() } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @GetMapping(value = ["{seriesId}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE]) @@ -116,7 +116,7 @@ class SeriesController( seriesRepository.findByIdOrNull(id)?.let { series -> if (!principal.user.canAccessSeries(series)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) - val thumbnail = series.books.firstOrNull()?.metadata?.thumbnail + val thumbnail = series.books.minBy { it.number }?.metadata?.thumbnail if (thumbnail != null) { ResponseEntity.ok() .cacheControl(CacheControl diff --git a/komga/src/main/resources/application.conf b/komga/src/main/resources/application.conf index c932543f8..9568d847c 100644 --- a/komga/src/main/resources/application.conf +++ b/komga/src/main/resources/application.conf @@ -21,6 +21,17 @@ caffeine.jcache { } } + cache.series.collection.books { + monitoring { + statistics = true + } + policy { + maximum { + size = 500 + } + } + } + cache.book { monitoring { statistics = true @@ -31,4 +42,26 @@ caffeine.jcache { } } } + + cache.bookmetadata { + monitoring { + statistics = true + } + policy { + maximum { + size = 500 + } + } + } + + cache.bookmetadata.collection.pages { + monitoring { + statistics = true + } + policy { + maximum { + size = 500 + } + } + } } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt index acfe8072c..f66ce3425 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryScannerTest.kt @@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.annotation.Transactional +import org.springframework.transaction.support.TransactionTemplate import java.nio.file.Paths @ExtendWith(SpringExtension::class) @@ -30,7 +32,8 @@ class LibraryScannerTest( @Autowired private val libraryRepository: LibraryRepository, @Autowired private val bookRepository: BookRepository, @Autowired private val libraryScanner: LibraryScanner, - @Autowired private val bookLifecycle: BookLifecycle + @Autowired private val bookLifecycle: BookLifecycle, + @Autowired private val transactionManager: PlatformTransactionManager ) { @MockkBean @@ -201,12 +204,14 @@ class LibraryScannerTest( verify(exactly = 2) { mockScanner.scanRootFolder(any()) } verify(exactly = 1) { mockParser.parse(any()) } - val book = bookRepository.findAll().first() - assertThat(book.metadata.status).isEqualTo(BookMetadata.Status.READY) - assertThat(book.metadata.mediaType).isEqualTo("application/zip") - assertThat(book.metadata.pages).hasSize(2) - assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg") - assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate) + TransactionTemplate(transactionManager).execute { + val book = bookRepository.findAll().first() + assertThat(book.metadata.status).isEqualTo(BookMetadata.Status.READY) + assertThat(book.metadata.mediaType).isEqualTo("application/zip") + assertThat(book.metadata.pages).hasSize(2) + assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg") + assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate) + } } @Test