diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt index 1e93e11b5..dd2499fb3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt @@ -6,8 +6,11 @@ import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookMetadata import org.gotson.komga.domain.model.Status import org.gotson.komga.domain.persistence.BookRepository +import org.springframework.scheduling.annotation.Async +import org.springframework.scheduling.annotation.AsyncResult import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.util.concurrent.Future import kotlin.system.measureTimeMillis private val logger = KotlinLogging.logger {} @@ -19,9 +22,10 @@ class BookManager( ) { @Transactional - fun parseAndPersist(book: Book) { + @Async("parseBookTaskExecutor") + fun parseAndPersist(book: Book): Future { logger.info { "Parse and persist book: ${book.url}" } - measureTimeMillis { + return AsyncResult(measureTimeMillis { try { book.metadata = bookParser.parse(book) } catch (ex: UnsupportedMediaTypeException) { @@ -32,7 +36,7 @@ class BookManager( book.metadata = BookMetadata(status = Status.ERROR) } bookRepository.save(book) - }.also { logger.info { "Parsing finished in ${DurationFormatUtils.formatDurationHMS(it)}" } } + }.also { logger.info { "Parsing finished in ${DurationFormatUtils.formatDurationHMS(it)}" } }) } } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt index 40e404344..3c76d121a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt @@ -80,10 +80,17 @@ class LibraryManager( } fun parseUnparsedBooks() { - logger.info { "Parsing all books in status: unkown" } + logger.info { "Parsing all books in status: unknown" } val booksToParse = bookRepository.findAllByMetadataStatus(Status.UNKNOWN) + + var sumOfTasksTime = 0L measureTimeMillis { - booksToParse.forEach { bookManager.parseAndPersist(it) } - }.also { logger.info { "Parsed ${booksToParse.size} books in ${DurationFormatUtils.formatDurationHMS(it)}" } } + sumOfTasksTime = booksToParse + .map { bookManager.parseAndPersist(it) } + .map { it.get() } + .sum() + }.also { + logger.info { "Parsed ${booksToParse.size} books in ${DurationFormatUtils.formatDurationHMS(it)} (virtual: ${DurationFormatUtils.formatDurationHMS(sumOfTasksTime)})" } + } } } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/async/AsyncConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/async/AsyncConfiguration.kt new file mode 100644 index 000000000..7f936e2f9 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/async/AsyncConfiguration.kt @@ -0,0 +1,18 @@ +package org.gotson.komga.infrastructure.async + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.EnableAsync +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor +import java.util.concurrent.Executor + +@Configuration +@EnableAsync +class AsyncConfiguration { + + @Bean + fun parseBookTaskExecutor(): Executor = + ThreadPoolTaskExecutor().apply { + corePoolSize = 5 + } +} \ No newline at end of file diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt index 35747af23..4fcdb7220 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryManagerTest.kt @@ -25,7 +25,6 @@ import javax.persistence.EntityManager @ExtendWith(SpringExtension::class) @SpringBootTest @AutoConfigureTestDatabase -@Transactional class LibraryManagerTest( @Autowired private val serieRepository: SerieRepository, @Autowired private val bookRepository: BookRepository, @@ -48,6 +47,7 @@ class LibraryManagerTest( } @Test + @Transactional fun `given existing Serie when adding files and scanning then only updated Books are persisted`() { //given val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))) @@ -73,6 +73,7 @@ class LibraryManagerTest( } @Test + @Transactional fun `given existing Serie when removing files and scanning then only updated Books are persisted`() { //given val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"), makeBook("book2"))) @@ -100,6 +101,7 @@ class LibraryManagerTest( } @Test + @Transactional fun `given existing Serie when updating files and scanning then Books are updated`() { //given val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))) @@ -161,7 +163,7 @@ class LibraryManagerTest( libraryManager.scanRootFolder(library) every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg"))) - bookRepository.findAll().forEach { bookManager.parseAndPersist(it) } + bookRepository.findAll().map { bookManager.parseAndPersist(it) }.map { it.get() } // when libraryManager.scanRootFolder(library)