mirror of
https://github.com/gotson/komga.git
synced 2026-05-06 03:27:08 +02:00
perf: hash and delete pages in a single scan
This commit is contained in:
parent
b724f205cd
commit
b436e90a8c
4 changed files with 49 additions and 16 deletions
|
|
@ -9,6 +9,7 @@ import java.io.Serializable
|
||||||
const val HIGHEST_PRIORITY = 8
|
const val HIGHEST_PRIORITY = 8
|
||||||
const val HIGH_PRIORITY = 6
|
const val HIGH_PRIORITY = 6
|
||||||
const val DEFAULT_PRIORITY = 4
|
const val DEFAULT_PRIORITY = 4
|
||||||
|
const val LOW_PRIORITY = 2
|
||||||
const val LOWEST_PRIORITY = 0
|
const val LOWEST_PRIORITY = 0
|
||||||
|
|
||||||
sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null) : Serializable {
|
sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null) : Serializable {
|
||||||
|
|
@ -25,6 +26,16 @@ sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null)
|
||||||
override fun toString(): String = "FindBooksToConvert(libraryId='$libraryId', priority='$priority')"
|
override fun toString(): String = "FindBooksToConvert(libraryId='$libraryId', priority='$priority')"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FindBooksWithMissingPageHash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
||||||
|
override fun uniqueId() = "FIND_BOOKS_WITH_MISSING_PAGE_HASH_$libraryId"
|
||||||
|
override fun toString(): String = "FindBooksWithMissingPageHash(libraryId='$libraryId', priority='$priority')"
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindDuplicatePagesToDelete(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
||||||
|
override fun uniqueId() = "FIND_DUPLICATE_PAGES_TO_DELETE_$libraryId"
|
||||||
|
override fun toString(): String = "FindDuplicatePagesToDelete(libraryId='$libraryId', priority='$priority')"
|
||||||
|
}
|
||||||
|
|
||||||
class EmptyTrash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
class EmptyTrash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
|
||||||
override fun uniqueId() = "EMPTY_TRASH_$libraryId"
|
override fun uniqueId() = "EMPTY_TRASH_$libraryId"
|
||||||
override fun toString(): String = "EmptyTrash(libraryId='$libraryId', priority='$priority')"
|
override fun toString(): String = "EmptyTrash(libraryId='$libraryId', priority='$priority')"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import org.gotson.komga.domain.service.BookMetadataLifecycle
|
||||||
import org.gotson.komga.domain.service.BookPageEditor
|
import org.gotson.komga.domain.service.BookPageEditor
|
||||||
import org.gotson.komga.domain.service.LibraryContentLifecycle
|
import org.gotson.komga.domain.service.LibraryContentLifecycle
|
||||||
import org.gotson.komga.domain.service.LocalArtworkLifecycle
|
import org.gotson.komga.domain.service.LocalArtworkLifecycle
|
||||||
|
import org.gotson.komga.domain.service.PageHashLifecycle
|
||||||
import org.gotson.komga.domain.service.SeriesLifecycle
|
import org.gotson.komga.domain.service.SeriesLifecycle
|
||||||
import org.gotson.komga.domain.service.SeriesMetadataLifecycle
|
import org.gotson.komga.domain.service.SeriesMetadataLifecycle
|
||||||
import org.gotson.komga.infrastructure.jms.QUEUE_FACTORY
|
import org.gotson.komga.infrastructure.jms.QUEUE_FACTORY
|
||||||
|
|
@ -43,6 +44,7 @@ class TaskHandler(
|
||||||
private val bookConverter: BookConverter,
|
private val bookConverter: BookConverter,
|
||||||
private val bookPageEditor: BookPageEditor,
|
private val bookPageEditor: BookPageEditor,
|
||||||
private val searchIndexLifecycle: SearchIndexLifecycle,
|
private val searchIndexLifecycle: SearchIndexLifecycle,
|
||||||
|
private val pageHashLifecycle: PageHashLifecycle,
|
||||||
private val meterRegistry: MeterRegistry,
|
private val meterRegistry: MeterRegistry,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -57,11 +59,11 @@ class TaskHandler(
|
||||||
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
||||||
libraryContentLifecycle.scanRootFolder(library)
|
libraryContentLifecycle.scanRootFolder(library)
|
||||||
taskReceiver.analyzeUnknownAndOutdatedBooks(library)
|
taskReceiver.analyzeUnknownAndOutdatedBooks(library)
|
||||||
taskReceiver.hashBooksWithoutHash(library)
|
taskReceiver.repairExtensions(library, LOW_PRIORITY)
|
||||||
taskReceiver.hashBookPagesWithMissingHash(library)
|
|
||||||
taskReceiver.repairExtensions(library, LOWEST_PRIORITY)
|
|
||||||
taskReceiver.findBooksToConvert(library, LOWEST_PRIORITY)
|
taskReceiver.findBooksToConvert(library, LOWEST_PRIORITY)
|
||||||
taskReceiver.removeDuplicatePages(library, LOWEST_PRIORITY)
|
taskReceiver.findBooksWithMissingPageHash(library, LOWEST_PRIORITY)
|
||||||
|
taskReceiver.findDuplicatePagesToDelete(library, LOWEST_PRIORITY)
|
||||||
|
taskReceiver.hashBooksWithoutHash(library)
|
||||||
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
is Task.FindBooksToConvert ->
|
is Task.FindBooksToConvert ->
|
||||||
|
|
@ -71,6 +73,20 @@ class TaskHandler(
|
||||||
}
|
}
|
||||||
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
|
is Task.FindBooksWithMissingPageHash ->
|
||||||
|
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
||||||
|
pageHashLifecycle.getBookAndSeriesIdsWithMissingPageHash(library).forEach {
|
||||||
|
taskReceiver.hashBookPages(it.first, it.second, task.priority + 1)
|
||||||
|
}
|
||||||
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
|
is Task.FindDuplicatePagesToDelete ->
|
||||||
|
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
||||||
|
pageHashLifecycle.getBookPagesToDeleteAutomatically(library).forEach { (bookId, pages) ->
|
||||||
|
taskReceiver.removeDuplicatePages(bookId, pages, task.priority + 1)
|
||||||
|
}
|
||||||
|
} ?: logger.warn { "Cannot execute task $task: Library does not exist" }
|
||||||
|
|
||||||
is Task.EmptyTrash ->
|
is Task.EmptyTrash ->
|
||||||
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
|
||||||
libraryContentLifecycle.emptyTrash(library)
|
libraryContentLifecycle.emptyTrash(library)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import org.gotson.komga.domain.model.Media
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||||
import org.gotson.komga.domain.service.BookConverter
|
import org.gotson.komga.domain.service.BookConverter
|
||||||
import org.gotson.komga.domain.service.PageHashLifecycle
|
|
||||||
import org.gotson.komga.infrastructure.jms.JMS_PROPERTY_TYPE
|
import org.gotson.komga.infrastructure.jms.JMS_PROPERTY_TYPE
|
||||||
import org.gotson.komga.infrastructure.jms.QUEUE_TASKS
|
import org.gotson.komga.infrastructure.jms.QUEUE_TASKS
|
||||||
import org.gotson.komga.infrastructure.jms.QUEUE_UNIQUE_ID
|
import org.gotson.komga.infrastructure.jms.QUEUE_UNIQUE_ID
|
||||||
|
|
@ -30,7 +29,6 @@ class TaskReceiver(
|
||||||
private val libraryRepository: LibraryRepository,
|
private val libraryRepository: LibraryRepository,
|
||||||
private val bookRepository: BookRepository,
|
private val bookRepository: BookRepository,
|
||||||
private val bookConverter: BookConverter,
|
private val bookConverter: BookConverter,
|
||||||
private val pageHashLifecycle: PageHashLifecycle,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val jmsTemplates = (0..9).associateWith {
|
private val jmsTemplates = (0..9).associateWith {
|
||||||
|
|
@ -71,11 +69,12 @@ class TaskReceiver(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hashBookPagesWithMissingHash(library: Library) {
|
fun findBooksWithMissingPageHash(library: Library, priority: Int = DEFAULT_PRIORITY) {
|
||||||
if (library.hashPages)
|
submitTask(Task.FindBooksWithMissingPageHash(library.id, priority))
|
||||||
pageHashLifecycle.getBookAndSeriesIdsWithMissingPageHash(library).forEach {
|
}
|
||||||
submitTask(Task.HashBookPages(it.first, LOWEST_PRIORITY, it.second))
|
|
||||||
}
|
fun hashBookPages(bookId: String, seriesId: String, priority: Int = DEFAULT_PRIORITY) {
|
||||||
|
submitTask(Task.HashBookPages(bookId, priority, seriesId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findBooksToConvert(library: Library, priority: Int = DEFAULT_PRIORITY) {
|
fun findBooksToConvert(library: Library, priority: Int = DEFAULT_PRIORITY) {
|
||||||
|
|
@ -93,10 +92,8 @@ class TaskReceiver(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeDuplicatePages(library: Library, priority: Int = DEFAULT_PRIORITY) {
|
fun findDuplicatePagesToDelete(library: Library, priority: Int = DEFAULT_PRIORITY) {
|
||||||
pageHashLifecycle.getBookPagesToDeleteAutomatically(library).forEach { (bookId, pages) ->
|
submitTask(Task.FindDuplicatePagesToDelete(library.id, priority))
|
||||||
removeDuplicatePages(bookId, pages, priority)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeDuplicatePages(bookId: String, pages: Collection<BookPageNumbered>, priority: Int = DEFAULT_PRIORITY) {
|
fun removeDuplicatePages(bookId: String, pages: Collection<BookPageNumbered>, priority: Int = DEFAULT_PRIORITY) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package org.gotson.komga.domain.service
|
package org.gotson.komga.domain.service
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
import org.gotson.komga.domain.model.BookPageContent
|
import org.gotson.komga.domain.model.BookPageContent
|
||||||
import org.gotson.komga.domain.model.BookPageNumbered
|
import org.gotson.komga.domain.model.BookPageNumbered
|
||||||
import org.gotson.komga.domain.model.Library
|
import org.gotson.komga.domain.model.Library
|
||||||
|
|
@ -13,6 +14,8 @@ import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class PageHashLifecycle(
|
class PageHashLifecycle(
|
||||||
private val pageHashRepository: PageHashRepository,
|
private val pageHashRepository: PageHashRepository,
|
||||||
|
|
@ -28,7 +31,13 @@ class PageHashLifecycle(
|
||||||
* @return a Collection of Pair of BookId/SeriesId
|
* @return a Collection of Pair of BookId/SeriesId
|
||||||
*/
|
*/
|
||||||
fun getBookAndSeriesIdsWithMissingPageHash(library: Library): Collection<Pair<String, String>> =
|
fun getBookAndSeriesIdsWithMissingPageHash(library: Library): Collection<Pair<String, String>> =
|
||||||
mediaRepository.findAllBookAndSeriesIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(library.id, hashableMediaTypes, komgaProperties.pageHashing)
|
if (library.hashPages)
|
||||||
|
mediaRepository.findAllBookAndSeriesIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(library.id, hashableMediaTypes, komgaProperties.pageHashing)
|
||||||
|
.also { logger.info { "Found ${it.size} books with missing page hash" } }
|
||||||
|
else {
|
||||||
|
logger.info { "Page hashing is not enabled, skipping" }
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
fun getPage(pageHash: PageHash, resizeTo: Int? = null): BookPageContent? {
|
fun getPage(pageHash: PageHash, resizeTo: Int? = null): BookPageContent? {
|
||||||
val match = pageHashRepository.findMatchesByHash(pageHash, null, Pageable.ofSize(1)).firstOrNull() ?: return null
|
val match = pageHashRepository.findMatchesByHash(pageHash, null, Pageable.ofSize(1)).firstOrNull() ?: return null
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue