From 90d3a1dbc316f41917f14fd96b4800eecb07ee56 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Thu, 26 Mar 2026 17:27:17 +0800 Subject: [PATCH] fix: epub extension could get lost during book restoration --- .../domain/persistence/MediaRepository.kt | 8 +++++ .../domain/service/LibraryContentLifecycle.kt | 4 +-- .../infrastructure/jooq/main/MediaDao.kt | 34 ++++++++++++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt index b781d001..5511e4eb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt @@ -24,6 +24,14 @@ interface MediaRepository { fun update(media: Media) + /** + * Performs a full copy of a Media, including MediaExtension, pages, and files. + */ + fun copy( + fromBookId: String, + toBookId: String, + ) + fun delete(bookId: String) fun delete(bookIds: Collection) 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 9315653c..3bab7cf7 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 @@ -381,9 +381,7 @@ class LibraryContentLifecycle( logger.info { "Match found, restore $match into $bookToAdd" } transactionTemplate.executeWithoutResult { // copy media - mediaRepository.findById(match.id).let { deleted -> - mediaRepository.update(deleted.copy(bookId = bookToAdd.id)) - } + mediaRepository.copy(match.id, bookToAdd.id) // copy generated and user uploaded thumbnails thumbnailBookRepository.findAllByBookIdAndType(match.id, setOf(ThumbnailBook.Type.GENERATED, ThumbnailBook.Type.USER_UPLOADED)).forEach { deleted -> diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt index 69339dad..e5f7375c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt @@ -1,6 +1,7 @@ package org.gotson.komga.infrastructure.jooq.main import com.fasterxml.jackson.databind.ObjectMapper +import io.github.oshai.kotlinlogging.KotlinLogging import org.gotson.komga.domain.model.BookPage import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Media @@ -26,6 +27,8 @@ import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime import java.time.ZoneId +private val logger = KotlinLogging.logger {} + @Component class MediaDao( dslRW: DSLContext, @@ -158,7 +161,14 @@ class MediaDao( media.pageCount, media.epubDivinaCompatible, media.epubIsKepub, - media.extension?.let { if (it is ProxyExtension) null else it::class.qualifiedName }, + media.extension?.let { + if (it is ProxyExtension) { + logger.error { "Found ProxyExtension while trying to insert Media: this is not expected and should be reported." } + null + } else { + it::class.qualifiedName + } + }, media.extension?.let { if (it is ProxyExtension) null else mapper.serializeJsonGz(it) }, ) } @@ -249,9 +259,13 @@ class MediaDao( .set(m.EPUB_DIVINA_COMPATIBLE, media.epubDivinaCompatible) .set(m.EPUB_IS_KEPUB, media.epubIsKepub) .apply { - if (media.extension != null && media.extension !is ProxyExtension) { - set(m.EXTENSION_CLASS, media.extension::class.qualifiedName) - set(m.EXTENSION_VALUE_BLOB, mapper.serializeJsonGz(media.extension)) + if (media.extension != null) { + if (media.extension !is ProxyExtension) { + set(m.EXTENSION_CLASS, media.extension::class.qualifiedName) + set(m.EXTENSION_VALUE_BLOB, mapper.serializeJsonGz(media.extension)) + } else { + logger.error { "Found ProxyExtension while trying to update Media: this is not expected and should be reported." } + } } }.set(m.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(m.BOOK_ID.eq(media.bookId)) @@ -271,6 +285,18 @@ class MediaDao( dslRW.insertFiles(listOf(media)) } + @Transactional + override fun copy( + fromBookId: String, + toBookId: String, + ) { + val source = findById(fromBookId) + val sourceExtension = findExtensionByIdOrNull(fromBookId) + + val copy = source.copy(bookId = toBookId, extension = sourceExtension) + update(copy) + } + @Transactional override fun delete(bookId: String) { dslRW.deleteFrom(p).where(p.BOOK_ID.eq(bookId)).execute()