diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt index d97138ec9..84bfc2865 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt @@ -10,7 +10,7 @@ const val HIGH_PRIORITY = 6 const val DEFAULT_PRIORITY = 4 const val LOWEST_PRIORITY = 0 -sealed class Task(priority: Int = DEFAULT_PRIORITY) : Serializable { +sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null) : Serializable { abstract fun uniqueId(): String val priority = priority.coerceIn(0, 9) @@ -24,63 +24,63 @@ sealed class Task(priority: Int = DEFAULT_PRIORITY) : Serializable { override fun toString(): String = "EmptyTrash(libraryId='$libraryId', priority='$priority')" } - class AnalyzeBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class AnalyzeBook(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId() = "ANALYZE_BOOK_$bookId" override fun toString(): String = "AnalyzeBook(bookId='$bookId', priority='$priority')" } - class GenerateBookThumbnail(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class GenerateBookThumbnail(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId() = "GENERATE_BOOK_THUMBNAIL_$bookId" override fun toString(): String = "GenerateBookThumbnail(bookId='$bookId', priority='$priority')" } - class RefreshBookMetadata(val bookId: String, val capabilities: Set, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class RefreshBookMetadata(val bookId: String, val capabilities: Set, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId() = "REFRESH_BOOK_METADATA_$bookId" override fun toString(): String = "RefreshBookMetadata(bookId='$bookId', capabilities=$capabilities, priority='$priority')" } - class HashBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class HashBook(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId() = "HASH_BOOK_$bookId" override fun toString(): String = "HashBook(bookId='$bookId', priority='$priority')" } - class HashBookPages(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class HashBookPages(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId() = "HASH_BOOK_PAGES_$bookId" override fun toString(): String = "HashBookPages(bookId='$bookId', priority='$priority')" } - class RefreshSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class RefreshSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId.takeLast(1)) { override fun uniqueId() = "REFRESH_SERIES_METADATA_$seriesId" override fun toString(): String = "RefreshSeriesMetadata(seriesId='$seriesId', priority='$priority')" } - class AggregateSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class AggregateSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId.takeLast(1)) { override fun uniqueId() = "AGGREGATE_SERIES_METADATA_$seriesId" override fun toString(): String = "AggregateSeriesMetadata(seriesId='$seriesId', priority='$priority')" } - class RefreshBookLocalArtwork(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class RefreshBookLocalArtwork(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId(): String = "REFRESH_BOOK_LOCAL_ARTWORK_$bookId" override fun toString(): String = "RefreshBookLocalArtwork(bookId='$bookId', priority='$priority')" } - class RefreshSeriesLocalArtwork(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class RefreshSeriesLocalArtwork(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId.takeLast(1)) { override fun uniqueId(): String = "REFRESH_SERIES_LOCAL_ARTWORK_$seriesId" override fun toString(): String = "RefreshSeriesLocalArtwork(seriesId=$seriesId, priority='$priority')" } - class ImportBook(val sourceFile: String, val seriesId: String, val copyMode: CopyMode, val destinationName: String?, val upgradeBookId: String?, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class ImportBook(val sourceFile: String, val seriesId: String, val copyMode: CopyMode, val destinationName: String?, val upgradeBookId: String?, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId.takeLast(1)) { override fun uniqueId(): String = "IMPORT_BOOK_${seriesId}_$sourceFile" override fun toString(): String = "ImportBook(sourceFile='$sourceFile', seriesId='$seriesId', copyMode=$copyMode, destinationName=$destinationName, upgradeBookId=$upgradeBookId, priority='$priority')" } - class ConvertBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class ConvertBook(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId(): String = "CONVERT_BOOK_$bookId" override fun toString(): String = "ConvertBook(bookId='$bookId', priority='$priority')" } - class RepairExtension(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { + class RepairExtension(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId.takeLast(1)) { override fun uniqueId(): String = "REPAIR_EXTENSION_$bookId" override fun toString(): String = "RepairExtension(bookId='$bookId', priority='$priority')" } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt index 52393d6b4..fd5130dea 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt @@ -40,7 +40,7 @@ class TaskHandler( private val searchIndexLifecycle: SearchIndexLifecycle, ) { - @JmsListener(destination = QUEUE_TASKS, selector = QUEUE_TASKS_SELECTOR, containerFactory = QUEUE_FACTORY) + @JmsListener(destination = QUEUE_TASKS, selector = QUEUE_TASKS_SELECTOR, containerFactory = QUEUE_FACTORY, concurrency = "#{@komgaProperties.taskConsumers}-#{@komgaProperties.taskConsumersMax}") fun handleTask(task: Task) { logger.info { "Executing task: $task" } try { @@ -65,8 +65,8 @@ class TaskHandler( is Task.AnalyzeBook -> bookRepository.findByIdOrNull(task.bookId)?.let { book -> if (bookLifecycle.analyzeAndPersist(book)) { - taskReceiver.generateBookThumbnail(book.id, priority = task.priority + 1) - taskReceiver.refreshBookMetadata(book.id, priority = task.priority + 1) + taskReceiver.generateBookThumbnail(book, priority = task.priority + 1) + taskReceiver.refreshBookMetadata(book, priority = task.priority + 1) } } ?: logger.warn { "Cannot execute task $task: Book does not exist" } @@ -105,7 +105,7 @@ class TaskHandler( is Task.ImportBook -> seriesRepository.findByIdOrNull(task.seriesId)?.let { series -> val importedBook = bookImporter.importBook(Paths.get(task.sourceFile), series, task.copyMode, task.destinationName, task.upgradeBookId) - taskReceiver.analyzeBook(importedBook.id, priority = task.priority + 1) + taskReceiver.analyzeBook(importedBook, priority = task.priority + 1) } ?: logger.warn { "Cannot execute task $task: Series does not exist" } is Task.ConvertBook -> diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt index 4ecc70dc1..005d967bd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt @@ -1,6 +1,7 @@ package org.gotson.komga.application.tasks import mu.KotlinLogging +import org.gotson.komga.domain.model.Book import org.gotson.komga.domain.model.BookMetadataPatchCapability import org.gotson.komga.domain.model.BookSearch import org.gotson.komga.domain.model.CopyMode @@ -16,6 +17,7 @@ import org.gotson.komga.infrastructure.jms.QUEUE_TASKS import org.gotson.komga.infrastructure.jms.QUEUE_TASKS_TYPE import org.gotson.komga.infrastructure.jms.QUEUE_TYPE import org.gotson.komga.infrastructure.jms.QUEUE_UNIQUE_ID +import org.gotson.komga.infrastructure.jooq.UnpagedSorted import org.gotson.komga.infrastructure.search.LuceneEntity import org.springframework.data.domain.Sort import org.springframework.jms.core.JmsTemplate @@ -54,59 +56,59 @@ class TaskReceiver( } fun analyzeUnknownAndOutdatedBooks(library: Library) { - bookRepository.findAllIds( + bookRepository.findAll( BookSearch( libraryIds = listOf(library.id), mediaStatus = listOf(Media.Status.UNKNOWN, Media.Status.OUTDATED), ), - Sort.by(Sort.Order.asc("seriesId"), Sort.Order.asc("number")), + UnpagedSorted(Sort.by(Sort.Order.asc("seriesId"), Sort.Order.asc("number"))) ).forEach { - submitTask(Task.AnalyzeBook(it)) + submitTask(Task.AnalyzeBook(it.id, groupId = it.seriesId)) } } fun hashBooksWithoutHash(library: Library) { if (library.hashFiles) - bookRepository.findAllIdsByLibraryIdAndWithEmptyHash(library.id).forEach { - submitTask(Task.HashBook(it, LOWEST_PRIORITY)) + bookRepository.findAllByLibraryIdAndWithEmptyHash(library.id).forEach { + submitTask(Task.HashBook(it.id, LOWEST_PRIORITY, it.seriesId)) } } fun hashBookPagesWithMissingHash(library: Library) { if (library.hashPages) mediaRepository.findAllBookIdsByLibraryIdAndWithMissingPageHash(library.id, komgaProperties.pageHashing).forEach { - submitTask(Task.HashBookPages(it, LOWEST_PRIORITY)) + submitTask(Task.HashBookPages(it, LOWEST_PRIORITY, bookRepository.getSeriesIdOrNull(it) ?: "")) } } fun convertBooksToCbz(library: Library, priority: Int = DEFAULT_PRIORITY) { if (library.convertToCbz) - bookConverter.getConvertibleBookIds(library).forEach { - submitTask(Task.ConvertBook(it, priority)) + bookConverter.getConvertibleBooks(library).forEach { + submitTask(Task.ConvertBook(it.id, priority, it.seriesId)) } } fun repairExtensions(library: Library, priority: Int = DEFAULT_PRIORITY) { if (library.repairExtensions) - bookConverter.getMismatchedExtensionBookIds(library).forEach { - submitTask(Task.RepairExtension(it, priority)) + bookConverter.getMismatchedExtensionBooks(library).forEach { + submitTask(Task.RepairExtension(it.id, priority, it.seriesId)) } } - fun analyzeBook(bookId: String, priority: Int = DEFAULT_PRIORITY) { - submitTask(Task.AnalyzeBook(bookId, priority)) + fun analyzeBook(book: Book, priority: Int = DEFAULT_PRIORITY) { + submitTask(Task.AnalyzeBook(book.id, priority, book.seriesId)) } - fun generateBookThumbnail(bookId: String, priority: Int = DEFAULT_PRIORITY) { - submitTask(Task.GenerateBookThumbnail(bookId, priority)) + fun generateBookThumbnail(book: Book, priority: Int = DEFAULT_PRIORITY) { + submitTask(Task.GenerateBookThumbnail(book.id, priority, book.seriesId)) } fun refreshBookMetadata( - bookId: String, + book: Book, capabilities: Set = BookMetadataPatchCapability.values().toSet(), priority: Int = DEFAULT_PRIORITY, ) { - submitTask(Task.RefreshBookMetadata(bookId, capabilities, priority)) + submitTask(Task.RefreshBookMetadata(book.id, capabilities, priority, book.seriesId)) } fun refreshSeriesMetadata(seriesId: String, priority: Int = DEFAULT_PRIORITY) { @@ -117,8 +119,8 @@ class TaskReceiver( submitTask(Task.AggregateSeriesMetadata(seriesId, priority)) } - fun refreshBookLocalArtwork(bookId: String, priority: Int = DEFAULT_PRIORITY) { - submitTask(Task.RefreshBookLocalArtwork(bookId, priority)) + fun refreshBookLocalArtwork(book: Book, priority: Int = DEFAULT_PRIORITY) { + submitTask(Task.RefreshBookLocalArtwork(book.id, priority, book.seriesId)) } fun refreshSeriesLocalArtwork(seriesId: String, priority: Int = DEFAULT_PRIORITY) { @@ -148,6 +150,7 @@ class TaskReceiver( setStringProperty(QUEUE_TYPE, QUEUE_TASKS_TYPE) setStringProperty(QUEUE_UNIQUE_ID, task.uniqueId()) setStringProperty(QUEUE_SUB_TYPE, task::class.simpleName) + task.groupId?.let { groupId -> setStringProperty("JMSXGroupID", groupId) } } } } 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 370277ad5..4bd96ca95 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 @@ -18,6 +18,9 @@ interface BookRepository { fun findAll(bookSearch: BookSearch): Collection fun findAll(bookSearch: BookSearch, pageable: Pageable): Page fun findAllDeletedByFileSize(fileSize: Long): Collection + fun findAllByLibraryIdAndWithEmptyHash(libraryId: String): Collection + fun findAllByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection + fun findAllByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection fun getLibraryIdOrNull(bookId: String): String? fun getSeriesIdOrNull(bookId: String): String? @@ -28,9 +31,6 @@ interface BookRepository { fun findAllIdsBySeriesId(seriesId: String): Collection fun findAllIdsBySeriesIds(seriesIds: Collection): Collection fun findAllIdsByLibraryId(libraryId: String): Collection - fun findAllIdsByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection - fun findAllIdsByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection - fun findAllIdsByLibraryIdAndWithEmptyHash(libraryId: String): Collection fun findAllIds(bookSearch: BookSearch, sort: Sort): Collection fun insert(book: Book) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt index 0c9551acc..b8d2163c8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt @@ -53,8 +53,8 @@ class BookConverter( private val failedConversions = mutableListOf() private val skippedRepairs = mutableListOf() - fun getConvertibleBookIds(library: Library): Collection = - bookRepository.findAllIdsByLibraryIdAndMediaTypes(library.id, convertibleTypes) + fun getConvertibleBooks(library: Library): Collection = + bookRepository.findAllByLibraryIdAndMediaTypes(library.id, convertibleTypes) fun convertToCbz(book: Book) { if (!libraryRepository.findById(book.libraryId).convertToCbz) @@ -133,9 +133,9 @@ class BookConverter( } } - fun getMismatchedExtensionBookIds(library: Library): Collection = + fun getMismatchedExtensionBooks(library: Library): Collection = mediaTypeToExtension.flatMap { (mediaType, extension) -> - bookRepository.findAllIdsByLibraryIdAndMismatchedExtension(library.id, mediaType, extension) + bookRepository.findAllByLibraryIdAndMismatchedExtension(library.id, mediaType, extension) } fun repairExtension(book: Book) { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt index 74e923e7c..1c7e592b6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt @@ -195,8 +195,8 @@ class BookImporter( sidecars.forEach { (sourceSidecar, destPath) -> when (sourceSidecar.type) { - Sidecar.Type.ARTWORK -> taskReceiver.refreshBookLocalArtwork(importedBook.id) - Sidecar.Type.METADATA -> taskReceiver.refreshBookMetadata(importedBook.id) + Sidecar.Type.ARTWORK -> taskReceiver.refreshBookLocalArtwork(importedBook) + Sidecar.Type.METADATA -> taskReceiver.refreshBookMetadata(importedBook) } val destSidecar = sourceSidecar.copy( url = destPath.toUri().toURL(), diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt index 2546d4cd2..4fe181896 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt @@ -210,8 +210,8 @@ class LibraryContentLifecycle( bookRepository.findNotDeletedByLibraryIdAndUrlOrNull(library.id, newSidecar.parentUrl)?.let { book -> logger.info { "Sidecar changed on disk (${newSidecar.url}, refresh Book for ${newSidecar.type}: $book" } when (newSidecar.type) { - Sidecar.Type.ARTWORK -> taskReceiver.refreshBookLocalArtwork(book.id) - Sidecar.Type.METADATA -> taskReceiver.refreshBookMetadata(book.id) + Sidecar.Type.ARTWORK -> taskReceiver.refreshBookLocalArtwork(book) + Sidecar.Type.METADATA -> taskReceiver.refreshBookMetadata(book) } } } @@ -349,7 +349,7 @@ class LibraryContentLifecycle( title = if (deleted.titleLock) deleted.title else newlyAdded.title, ), ) - if (!deleted.titleLock) taskReceiver.refreshBookMetadata(bookToAdd.id, setOf(BookMetadataPatchCapability.TITLE)) + if (!deleted.titleLock) taskReceiver.refreshBookMetadata(bookToAdd, setOf(BookMetadataPatchCapability.TITLE)) } // copy read progress diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt index d46077dad..a491aab58 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt @@ -90,20 +90,23 @@ class SeriesLifecycle( sorted.mapIndexed { index, (book, _) -> book.copy(number = index + 1) }, ) - val oldToNew = sorted.mapIndexedNotNull { index, (_, metadata) -> + val oldToNew = sorted.mapIndexedNotNull { index, (book, metadata) -> if (metadata.numberLock && metadata.numberSortLock) null - else metadata to metadata.copy( - number = if (!metadata.numberLock) (index + 1).toString() else metadata.number, - numberSort = if (!metadata.numberSortLock) (index + 1).toFloat() else metadata.numberSort, + else Triple( + book, metadata, + metadata.copy( + number = if (!metadata.numberLock) (index + 1).toString() else metadata.number, + numberSort = if (!metadata.numberSortLock) (index + 1).toFloat() else metadata.numberSort + ) ) } - bookMetadataRepository.update(oldToNew.map { it.second }) + bookMetadataRepository.update(oldToNew.map { it.third }) // refresh metadata to reimport book number, else the series resorting would overwrite it - oldToNew.forEach { (old, new) -> + oldToNew.forEach { (book, old, new) -> if (old.number != new.number || old.numberSort != new.numberSort) { logger.debug { "Metadata numbering has changed, refreshing metadata for book ${new.bookId} " } - taskReceiver.refreshBookMetadata(new.bookId, setOf(BookMetadataPatchCapability.NUMBER, BookMetadataPatchCapability.NUMBER_SORT)) + taskReceiver.refreshBookMetadata(book, setOf(BookMetadataPatchCapability.NUMBER, BookMetadataPatchCapability.NUMBER_SORT)) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt index a3e9193c7..96431fb10 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt @@ -46,6 +46,12 @@ class KomgaProperties { var configDir: String? = null + @Positive + var taskConsumers: Int = 1 + + @Positive + var taskConsumersMax: Int = 1 + class RememberMe { @get:NotBlank var key: String? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt index 6e102c121..66a4b5979 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/BookDao.kt @@ -200,29 +200,31 @@ class BookDao( .fetch(b.ID) } - override fun findAllIdsByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection = - dsl.select(b.ID) + override fun findAllByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection = + dsl.select(*b.fields()) .from(b) .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) .where(b.LIBRARY_ID.eq(libraryId)) .and(m.MEDIA_TYPE.`in`(mediaTypes)) - .fetch(b.ID) + .fetchInto(b) + .map { it.toDomain() } - override fun findAllIdsByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection = - dsl.select(b.ID) + override fun findAllByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection = + dsl.select(*b.fields()) .from(b) .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) .where(b.LIBRARY_ID.eq(libraryId)) .and(m.MEDIA_TYPE.eq(mediaType)) .and(b.URL.notLike("%.$extension")) - .fetch(b.ID) + .fetchInto(b) + .map { it.toDomain() } - override fun findAllIdsByLibraryIdAndWithEmptyHash(libraryId: String): Collection = - dsl.select(b.ID) - .from(b) + override fun findAllByLibraryIdAndWithEmptyHash(libraryId: String): Collection = + dsl.selectFrom(b) .where(b.LIBRARY_ID.eq(libraryId)) .and(b.FILE_HASH.eq("")) - .fetch(b.ID) + .fetchInto(b) + .map { it.toDomain() } @Transactional override fun insert(book: Book) { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt index 7bf43adfc..2b54dedc8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt @@ -564,7 +564,7 @@ class BookController( @ResponseStatus(HttpStatus.ACCEPTED) fun analyze(@PathVariable bookId: String) { bookRepository.findByIdOrNull(bookId)?.let { book -> - taskReceiver.analyzeBook(book.id, HIGH_PRIORITY) + taskReceiver.analyzeBook(book, HIGH_PRIORITY) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } @@ -573,8 +573,8 @@ class BookController( @ResponseStatus(HttpStatus.ACCEPTED) fun refreshMetadata(@PathVariable bookId: String) { bookRepository.findByIdOrNull(bookId)?.let { book -> - taskReceiver.refreshBookMetadata(book.id, priority = HIGH_PRIORITY) - taskReceiver.refreshBookLocalArtwork(book.id, priority = HIGH_PRIORITY) + taskReceiver.refreshBookMetadata(book, priority = HIGH_PRIORITY) + taskReceiver.refreshBookLocalArtwork(book, priority = HIGH_PRIORITY) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt index 941a5afd5..53bfe855a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt @@ -3,6 +3,7 @@ package org.gotson.komga.interfaces.api.rest import org.gotson.komga.application.tasks.HIGHEST_PRIORITY import org.gotson.komga.application.tasks.HIGH_PRIORITY import org.gotson.komga.application.tasks.TaskReceiver +import org.gotson.komga.domain.model.BookSearch import org.gotson.komga.domain.model.DirectoryNotFoundException import org.gotson.komga.domain.model.DuplicateNameException import org.gotson.komga.domain.model.Library @@ -178,7 +179,7 @@ class LibraryController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) fun analyze(@PathVariable libraryId: String) { - bookRepository.findAllIdsByLibraryId(libraryId).forEach { + bookRepository.findAll(BookSearch(libraryIds = listOf(libraryId))).forEach { taskReceiver.analyzeBook(it, HIGH_PRIORITY) } } @@ -187,7 +188,7 @@ class LibraryController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) fun refreshMetadata(@PathVariable libraryId: String) { - bookRepository.findAllIdsByLibraryId(libraryId).forEach { + bookRepository.findAll(BookSearch(libraryIds = listOf(libraryId))).forEach { taskReceiver.refreshBookMetadata(it, priority = HIGH_PRIORITY) taskReceiver.refreshBookLocalArtwork(it, priority = HIGH_PRIORITY) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt index 63b0c70c3..157cca1b4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt @@ -488,7 +488,7 @@ class SeriesController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) fun analyze(@PathVariable seriesId: String) { - bookRepository.findAllIdsBySeriesId(seriesId).forEach { + bookRepository.findAllBySeriesId(seriesId).forEach { taskReceiver.analyzeBook(it, HIGH_PRIORITY) } } @@ -497,7 +497,7 @@ class SeriesController( @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) fun refreshMetadata(@PathVariable seriesId: String) { - bookRepository.findAllIdsBySeriesId(seriesId).forEach { + bookRepository.findAllBySeriesId(seriesId).forEach { taskReceiver.refreshBookMetadata(it, priority = HIGH_PRIORITY) taskReceiver.refreshBookLocalArtwork(it, priority = HIGH_PRIORITY) } diff --git a/komga/src/main/resources/application-dev.yml b/komga/src/main/resources/application-dev.yml index fdc6a898b..d08ec3a47 100644 --- a/komga/src/main/resources/application-dev.yml +++ b/komga/src/main/resources/application-dev.yml @@ -9,6 +9,8 @@ komga: file: ":memory:" cors.allowed-origins: - http://localhost:8081 + task-consumers: 5 + task-consumers-max: 20 # delete-empty-collections: true # delete-empty-read-lists: true oauth2-account-creation: false diff --git a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt index 6d99927f1..abe57dc14 100644 --- a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskHandlerTest.kt @@ -66,8 +66,9 @@ class TaskHandlerTest( every { mockBookLifecycle.analyzeAndPersist(any()) } returns false jmsListenerEndpointRegistry.stop() + val book = makeBook("book") repeat(100) { - taskReceiver.analyzeBook("id") + taskReceiver.analyzeBook(book) } jmsListenerEndpointRegistry.start() @@ -88,7 +89,7 @@ class TaskHandlerTest( jmsListenerEndpointRegistry.stop() (0..9).forEach { - taskReceiver.analyzeBook("$it", it) + taskReceiver.analyzeBook(makeBook("$it", id = "$it"), it) } jmsListenerEndpointRegistry.start() diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt index b27bc3371..ad781f32c 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt @@ -4,7 +4,14 @@ import com.github.f4b6a3.tsid.TsidCreator import java.net.URL import java.time.LocalDateTime -fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now(), libraryId: String = "", seriesId: String = "", url: URL? = null): Book { +fun makeBook( + name: String, + fileLastModified: LocalDateTime = LocalDateTime.now(), + libraryId: String = "", + seriesId: String = "", + url: URL? = null, + id: String = TsidCreator.getTsid256().toString(), +): Book { Thread.sleep(5) return Book( name = name, @@ -12,6 +19,7 @@ fun makeBook(name: String, fileLastModified: LocalDateTime = LocalDateTime.now() fileLastModified = fileLastModified, libraryId = libraryId, seriesId = seriesId, + id = id, ) } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt index 5a116b5a1..496bfc91b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt @@ -817,7 +817,7 @@ class LibraryContentLifecycleTest( // then verify(exactly = 1) { mockHasher.computeHash(any()) } - verify(exactly = 0) { mockTaskReceiver.refreshBookMetadata(bookRenamed.id, setOf(BookMetadataPatchCapability.TITLE)) } + verify(exactly = 0) { mockTaskReceiver.refreshBookMetadata(bookRenamed, setOf(BookMetadataPatchCapability.TITLE)) } val allSeries = seriesRepository.findAll() val allBooks = bookRepository.findAll().sortedBy { it.number } @@ -861,7 +861,7 @@ class LibraryContentLifecycleTest( // then verify(exactly = 1) { mockHasher.computeHash(any()) } - verify(exactly = 1) { mockTaskReceiver.refreshBookMetadata(bookRenamed.id, setOf(BookMetadataPatchCapability.TITLE)) } + verify(exactly = 1) { mockTaskReceiver.refreshBookMetadata(withArg { assertThat(it.id).isEqualTo(bookRenamed.id) }, setOf(BookMetadataPatchCapability.TITLE)) } val allSeries = seriesRepository.findAll() val allBooks = bookRepository.findAll().sortedBy { it.number } @@ -1186,7 +1186,7 @@ class LibraryContentLifecycleTest( libraryContentLifecycle.scanRootFolder(library) // rename // then - verify(exactly = 0) { mockTaskReceiver.refreshBookMetadata(book2Moved.id, setOf(BookMetadataPatchCapability.TITLE)) } + verify(exactly = 0) { mockTaskReceiver.refreshBookMetadata(book2Moved, setOf(BookMetadataPatchCapability.TITLE)) } val allSeries = seriesRepository.findAll() val allBooks = bookRepository.findAll().sortedBy { it.number } @@ -1244,7 +1244,7 @@ class LibraryContentLifecycleTest( libraryContentLifecycle.scanRootFolder(library) // rename // then - verify(exactly = 1) { mockTaskReceiver.refreshBookMetadata(book2Moved.id, setOf(BookMetadataPatchCapability.TITLE)) } + verify(exactly = 1) { mockTaskReceiver.refreshBookMetadata(withArg { assertThat(it.id).isEqualTo(book2Moved.id) }, setOf(BookMetadataPatchCapability.TITLE)) } val allSeries = seriesRepository.findAll() val allBooks = bookRepository.findAll().sortedBy { it.number }