feat(api): new endpoint to regenerate thumbnails conditionally

This commit is contained in:
Gauthier Roebroeck 2023-10-11 10:57:26 +08:00
parent da184c8fb1
commit 796745a27f
8 changed files with 51 additions and 1 deletions

View file

@ -131,4 +131,9 @@ sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null)
override fun uniqueId() = "FIX_THUMBNAILS_WITHOUT_METADATA"
override fun toString(): String = "FixThumbnailsWithoutMetadata(priority='$priority')"
}
class FindBookThumbnailsToRegenerate(val forBiggerResultOnly: Boolean, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
override fun uniqueId() = "FIND_BOOK_THUMBNAILS_TO_REGENERATE"
override fun toString(): String = "FindBookThumbnailsToRegenerate(forBiggerResultOnly='$forBiggerResultOnly', priority='$priority')"
}
}

View file

@ -108,6 +108,10 @@ class TaskEmitter(
submitTask(Task.GenerateBookThumbnail(book.id, priority, book.seriesId))
}
fun generateBookThumbnail(bookId: String, priority: Int = DEFAULT_PRIORITY) {
submitTask(Task.GenerateBookThumbnail(bookId, priority, bookId))
}
fun refreshBookMetadata(
book: Book,
capabilities: Set<BookMetadataPatchCapability> = BookMetadataPatchCapability.values().toSet(),
@ -156,6 +160,10 @@ class TaskEmitter(
submitTask(Task.FixThumbnailsWithoutMetadata(priority))
}
fun findBookThumbnailsToRegenerate(forBiggerResultOnly: Boolean, priority: Int = DEFAULT_PRIORITY) {
submitTask(Task.FindBookThumbnailsToRegenerate(forBiggerResultOnly, priority))
}
private fun submitTask(task: Task) {
logger.info { "Sending task: $task" }
jmsTemplates[task.priority]!!.convertAndSend(QUEUE_TASKS, task) {

View file

@ -187,6 +187,13 @@ class TaskHandler(
if (thumbnailLifecycle.fixThumbnailsMetadata())
taskEmitter.fixThumbnailsWithoutMetadata(LOWEST_PRIORITY)
}
is Task.FindBookThumbnailsToRegenerate -> {
val bookIds = bookLifecycle.findBookThumbnailsToRegenerate(task.forBiggerResultOnly)
bookIds.forEach {
taskEmitter.generateBookThumbnail(it, task.priority)
}
}
}
}.also {
logger.info { "Task $task executed in $it" }

View file

@ -10,6 +10,7 @@ interface ThumbnailBookRepository {
fun findAllByBookId(bookId: String): Collection<ThumbnailBook>
fun findAllByBookIdAndType(bookId: String, type: ThumbnailBook.Type): Collection<ThumbnailBook>
fun findAllWithoutMetadata(pageable: Pageable): Page<ThumbnailBook>
fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan(type: ThumbnailBook.Type, size: Int): Collection<String>
fun insert(thumbnail: ThumbnailBook)
fun update(thumbnail: ThumbnailBook)

View file

@ -5,6 +5,7 @@ import org.gotson.komga.application.events.EventPublisher
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookAction
import org.gotson.komga.domain.model.BookPageContent
import org.gotson.komga.domain.model.BookSearch
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.DomainEvent
import org.gotson.komga.domain.model.HistoricalEvent
@ -23,9 +24,11 @@ import org.gotson.komga.domain.persistence.MediaRepository
import org.gotson.komga.domain.persistence.ReadListRepository
import org.gotson.komga.domain.persistence.ReadProgressRepository
import org.gotson.komga.domain.persistence.ThumbnailBookRepository
import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.image.ImageConverter
import org.gotson.komga.infrastructure.image.ImageType
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import org.springframework.transaction.support.TransactionTemplate
import java.io.File
@ -54,6 +57,7 @@ class BookLifecycle(
private val transactionTemplate: TransactionTemplate,
private val hasher: Hasher,
private val historicalEventRepository: HistoricalEventRepository,
private val komgaSettingsProvider: KomgaSettingsProvider,
) {
private val resizeTargetFormat = ImageType.JPEG
@ -232,6 +236,14 @@ class BookLifecycle(
}
}
fun findBookThumbnailsToRegenerate(forBiggerResultOnly: Boolean): Collection<String> {
return if (forBiggerResultOnly) {
thumbnailBookRepository.findAllBookIdsByThumbnailTypeAndDimensionSmallerThan(ThumbnailBook.Type.GENERATED, komgaSettingsProvider.thumbnailSize.maxEdge)
} else {
bookRepository.findAllIds(BookSearch(deleted = false), Sort.unsorted())
}
}
@Throws(
ImageConversionException::class,
MediaNotReadyException::class,

View file

@ -30,7 +30,6 @@ class BookDao(
private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA
private val r = Tables.READ_PROGRESS
private val l = Tables.LIBRARY
private val sorts = mapOf(
"createdDate" to b.CREATED_DATE,

View file

@ -65,6 +65,14 @@ class ThumbnailBookDao(
return PageImpl(items, pageable, count.toLong())
}
override fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan(type: ThumbnailBook.Type, size: Int): Collection<String> =
dsl.select(tb.BOOK_ID)
.from(tb)
.where(tb.TYPE.eq(type.toString()))
.and(tb.WIDTH.lt(size))
.and(tb.HEIGHT.lt(size))
.fetch(tb.BOOK_ID)
override fun insert(thumbnail: ThumbnailBook) {
dsl.insertInto(tb)
.set(tb.ID, thumbnail.id)

View file

@ -11,6 +11,7 @@ import org.apache.commons.io.IOUtils
import org.gotson.komga.application.events.EventPublisher
import org.gotson.komga.application.tasks.HIGHEST_PRIORITY
import org.gotson.komga.application.tasks.HIGH_PRIORITY
import org.gotson.komga.application.tasks.LOWEST_PRIORITY
import org.gotson.komga.application.tasks.TaskEmitter
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookSearchWithReadProgress
@ -836,6 +837,15 @@ class BookController(
)
}
@PutMapping("api/v1/books/thumbnails")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun regenerateThumbnails(
@RequestParam(name = "for_bigger_result_only", required = false) forBiggerResultOnly: Boolean = false,
) {
taskEmitter.findBookThumbnailsToRegenerate(forBiggerResultOnly, LOWEST_PRIORITY)
}
private fun ResponseEntity.BodyBuilder.setNotModified(media: Media) =
this.setCachePrivate().lastModified(getBookLastModified(media))