refactor: book with media

This commit is contained in:
Gauthier Roebroeck 2021-04-19 17:12:34 +08:00
parent f3cc6f6e91
commit 25d6272e0f
11 changed files with 79 additions and 81 deletions

View file

@ -0,0 +1,6 @@
package org.gotson.komga.domain.model
data class BookWithMedia(
val book: Book,
val media: Media,
)

View file

@ -3,11 +3,11 @@ package org.gotson.komga.domain.service
import mu.KotlinLogging
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookPage
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.MediaNotReadyException
import org.gotson.komga.domain.model.MediaUnsupportedException
import org.gotson.komga.domain.model.ThumbnailBook
import org.gotson.komga.domain.persistence.MediaRepository
import org.gotson.komga.infrastructure.image.ImageConverter
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
import org.gotson.komga.infrastructure.mediacontainer.MediaContainerExtractor
@ -19,8 +19,7 @@ private val logger = KotlinLogging.logger {}
class BookAnalyzer(
private val contentDetector: ContentDetector,
extractors: List<MediaContainerExtractor>,
private val imageConverter: ImageConverter,
private val mediaRepository: MediaRepository
private val imageConverter: ImageConverter
) {
val supportedMediaTypes = extractors
@ -47,21 +46,21 @@ class BookAnalyzer(
return Media(mediaType = mediaType, status = Media.Status.ERROR, comment = "ERR_1008")
}
val (pages, others) = entries
.partition { entry ->
entry.mediaType?.let { contentDetector.isImage(it) } ?: false
}.let { (images, others) ->
Pair(
images.map { BookPage(it.name, it.mediaType!!, it.dimension) },
others
)
}
val (pages, others) = entries
.partition { entry ->
entry.mediaType?.let { contentDetector.isImage(it) } ?: false
}.let { (images, others) ->
Pair(
images.map { BookPage(it.name, it.mediaType!!, it.dimension) },
others
)
}
val entriesErrorSummary = others
.filter { it.mediaType.isNullOrBlank() }
.map { it.name }
.ifEmpty { null }
?.joinToString(prefix = "ERR_1007 [", postfix = "]") { it }
val entriesErrorSummary = others
.filter { it.mediaType.isNullOrBlank() }
.map { it.name }
.ifEmpty { null }
?.joinToString(prefix = "ERR_1007 [", postfix = "]") { it }
if (pages.isEmpty()) {
logger.warn { "Book $book does not contain any pages" }
@ -69,24 +68,22 @@ class BookAnalyzer(
}
logger.info { "Book has ${pages.size} pages" }
val files = others.map { it.name }
val files = others.map { it.name }
return Media(mediaType = mediaType, status = Media.Status.READY, pages = pages, files = files, comment = entriesErrorSummary)
}
@Throws(MediaNotReadyException::class)
fun generateThumbnail(book: Book): ThumbnailBook {
fun generateThumbnail(book: BookWithMedia): ThumbnailBook {
logger.info { "Generate thumbnail for book: $book" }
val media = mediaRepository.findById(book.id)
if (media.status != Media.Status.READY) {
if (book.media.status != Media.Status.READY) {
logger.warn { "Book media is not ready, cannot generate thumbnail. Book: $book" }
throw MediaNotReadyException()
}
val thumbnail = try {
supportedMediaTypes.getValue(media.mediaType!!).getEntryStream(book.path(), media.pages.first().fileName).let { cover ->
supportedMediaTypes.getValue(book.media.mediaType!!).getEntryStream(book.book.path(), book.media.pages.first().fileName).let { cover ->
imageConverter.resizeImage(cover, thumbnailFormat, thumbnailSize)
}
} catch (ex: Exception) {
@ -97,7 +94,7 @@ class BookAnalyzer(
return ThumbnailBook(
thumbnail = thumbnail,
type = ThumbnailBook.Type.GENERATED,
bookId = book.id
bookId = book.book.id
)
}
@ -105,37 +102,33 @@ class BookAnalyzer(
MediaNotReadyException::class,
IndexOutOfBoundsException::class
)
fun getPageContent(book: Book, number: Int): ByteArray {
fun getPageContent(book: BookWithMedia, number: Int): ByteArray {
logger.info { "Get page #$number for book: $book" }
val media = mediaRepository.findById(book.id)
if (media.status != Media.Status.READY) {
if (book.media.status != Media.Status.READY) {
logger.warn { "Book media is not ready, cannot get pages" }
throw MediaNotReadyException()
}
if (number > media.pages.size || number <= 0) {
logger.error { "Page number #$number is out of bounds. Book has ${media.pages.size} pages" }
if (number > book.media.pages.size || number <= 0) {
logger.error { "Page number #$number is out of bounds. Book has ${book.media.pages.size} pages" }
throw IndexOutOfBoundsException("Page $number does not exist")
}
return supportedMediaTypes.getValue(media.mediaType!!).getEntryStream(book.path(), media.pages[number - 1].fileName)
return supportedMediaTypes.getValue(book.media.mediaType!!).getEntryStream(book.book.path(), book.media.pages[number - 1].fileName)
}
@Throws(
MediaNotReadyException::class
)
fun getFileContent(book: Book, fileName: String): ByteArray {
fun getFileContent(book: BookWithMedia, fileName: String): ByteArray {
logger.info { "Get file $fileName for book: $book" }
val media = mediaRepository.findById(book.id)
if (media.status != Media.Status.READY) {
if (book.media.status != Media.Status.READY) {
logger.warn { "Book media is not ready, cannot get files" }
throw MediaNotReadyException()
}
return supportedMediaTypes.getValue(media.mediaType!!).getEntryStream(book.path(), fileName)
return supportedMediaTypes.getValue(book.media.mediaType!!).getEntryStream(book.book.path(), fileName)
}
}

View file

@ -3,6 +3,7 @@ package org.gotson.komga.domain.service
import mu.KotlinLogging
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookPageContent
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.ImageConversionException
import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.Media
@ -63,7 +64,7 @@ class BookLifecycle(
fun generateThumbnailAndPersist(book: Book) {
logger.info { "Generate thumbnail and persist for book: $book" }
try {
addThumbnailForBook(bookAnalyzer.generateThumbnail(book))
addThumbnailForBook(bookAnalyzer.generateThumbnail(BookWithMedia(book, mediaRepository.findById(book.id))))
} catch (ex: Exception) {
logger.error(ex) { "Error while creating thumbnail" }
}
@ -155,7 +156,7 @@ class BookLifecycle(
)
fun getBookPage(book: Book, number: Int, convertTo: ImageType? = null, resizeTo: Int? = null): BookPageContent {
val media = mediaRepository.findById(book.id)
val pageContent = bookAnalyzer.getPageContent(book, number)
val pageContent = bookAnalyzer.getPageContent(BookWithMedia(book, mediaRepository.findById(book.id)), number)
val pageMediaType = media.pages[number - 1].mediaType
if (resizeTo != null) {

View file

@ -4,6 +4,7 @@ import mu.KotlinLogging
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookMetadataPatch
import org.gotson.komga.domain.model.BookMetadataPatchCapability
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.ReadList
import org.gotson.komga.domain.model.Series
import org.gotson.komga.domain.model.SeriesCollection
@ -65,7 +66,7 @@ class MetadataLifecycle(
logger.info { "Library is not set to import book metadata from Barcode ISBN, skipping" }
else -> {
logger.debug { "Provider: $provider" }
val patch = provider.getBookMetadataFromBook(book, media)
val patch = provider.getBookMetadataFromBook(BookWithMedia(book, media))
if (
(provider is ComicInfoProvider && library.importComicInfoBook) ||
@ -156,7 +157,7 @@ class MetadataLifecycle(
else -> {
logger.debug { "Provider: $provider" }
val patches = bookRepository.findBySeriesId(series.id)
.mapNotNull { provider.getSeriesMetadataFromBook(it, mediaRepository.findById(it.id)) }
.mapNotNull { provider.getSeriesMetadataFromBook(BookWithMedia(it, mediaRepository.findById(it.id))) }
if (
(provider is ComicInfoProvider && library.importComicInfoSeries) ||

View file

@ -1,11 +1,10 @@
package org.gotson.komga.infrastructure.metadata
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookMetadataPatch
import org.gotson.komga.domain.model.BookMetadataPatchCapability
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.BookWithMedia
interface BookMetadataProvider {
fun getCapabilities(): List<BookMetadataPatchCapability>
fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch?
fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch?
}

View file

@ -1,9 +1,8 @@
package org.gotson.komga.infrastructure.metadata
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.SeriesMetadataPatch
interface SeriesMetadataProvider {
fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch?
fun getSeriesMetadataFromBook(book: BookWithMedia): SeriesMetadataPatch?
}

View file

@ -8,10 +8,9 @@ import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.HybridBinarizer
import mu.KotlinLogging
import org.apache.commons.validator.routines.ISBNValidator
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookMetadataPatch
import org.gotson.komga.domain.model.BookMetadataPatchCapability
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.service.BookAnalyzer
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
import org.springframework.stereotype.Service
@ -37,8 +36,8 @@ class IsbnBarcodeProvider(
override fun getCapabilities(): List<BookMetadataPatchCapability> =
listOf(BookMetadataPatchCapability.ISBN)
override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? {
val pagesToTry = (1..media.pages.size).toList().let {
override fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? {
val pagesToTry = (1..book.media.pages.size).toList().let {
(it.takeLast(PAGES_LAST).reversed() + it.take(PAGES_FIRST)).distinct()
}

View file

@ -3,10 +3,9 @@ package org.gotson.komga.infrastructure.metadata.comicrack
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import mu.KotlinLogging
import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookMetadataPatch
import org.gotson.komga.domain.model.BookMetadataPatchCapability
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.SeriesMetadataPatch
import org.gotson.komga.domain.service.BookAnalyzer
@ -40,8 +39,8 @@ class ComicInfoProvider(
BookMetadataPatchCapability.READ_LISTS,
)
override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? {
getComicInfo(book, media)?.let { comicInfo ->
override fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? {
getComicInfo(book)?.let { comicInfo ->
val releaseDate = comicInfo.year?.let {
LocalDate.of(comicInfo.year!!, comicInfo.month ?: 1, comicInfo.day ?: 1)
}
@ -83,8 +82,8 @@ class ComicInfoProvider(
return null
}
override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? {
getComicInfo(book, media)?.let { comicInfo ->
override fun getSeriesMetadataFromBook(book: BookWithMedia): SeriesMetadataPatch? {
getComicInfo(book)?.let { comicInfo ->
val readingDirection = when (comicInfo.manga) {
Manga.NO -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT
Manga.YES_AND_RIGHT_TO_LEFT -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT
@ -110,9 +109,9 @@ class ComicInfoProvider(
return null
}
private fun getComicInfo(book: Book, media: Media): ComicInfo? {
private fun getComicInfo(book: BookWithMedia): ComicInfo? {
try {
if (media.files.none { it == COMIC_INFO }) {
if (book.media.files.none { it == COMIC_INFO }) {
logger.debug { "Book does not contain any $COMIC_INFO file: $book" }
return null
}

View file

@ -2,10 +2,9 @@ package org.gotson.komga.infrastructure.metadata.epub
import org.apache.commons.validator.routines.ISBNValidator
import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.BookMetadataPatch
import org.gotson.komga.domain.model.BookMetadataPatchCapability
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.SeriesMetadataPatch
import org.gotson.komga.infrastructure.mediacontainer.EpubExtractor
@ -41,9 +40,9 @@ class EpubMetadataProvider(
BookMetadataPatchCapability.ISBN,
)
override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? {
if (media.mediaType != "application/epub+zip") return null
epubExtractor.getPackageFile(book.path())?.let { packageFile ->
override fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? {
if (book.media.mediaType != "application/epub+zip") return null
epubExtractor.getPackageFile(book.book.path())?.let { packageFile ->
val opf = Jsoup.parse(packageFile)
val title = opf.selectFirst("metadata > dc|title")?.text()?.ifBlank { null }
@ -80,9 +79,9 @@ class EpubMetadataProvider(
return null
}
override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? {
if (media.mediaType != "application/epub+zip") return null
epubExtractor.getPackageFile(book.path())?.let { packageFile ->
override fun getSeriesMetadataFromBook(book: BookWithMedia): SeriesMetadataPatch? {
if (book.media.mediaType != "application/epub+zip") return null
epubExtractor.getPackageFile(book.book.path())?.let { packageFile ->
val opf = Jsoup.parse(packageFile)
val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text()?.ifBlank { null }

View file

@ -5,6 +5,7 @@ import io.mockk.mockk
import org.apache.commons.validator.routines.ISBNValidator
import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.BookPage
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.service.BookAnalyzer
@ -25,7 +26,7 @@ class IsbnBarcodeProviderTest {
val media = Media(pages = listOf(BookPage("page", "image/jpeg")))
// when
val patch = isbnBarcodeProvider.getBookMetadataFromBook(book, media)
val patch = isbnBarcodeProvider.getBookMetadataFromBook(BookWithMedia(book, media))
// then
assertThat(patch?.isbn).isEqualTo("9782811632397")
@ -40,7 +41,7 @@ class IsbnBarcodeProviderTest {
val media = Media(pages = listOf(BookPage("page", "image/jpeg")))
// when
val patch = isbnBarcodeProvider.getBookMetadataFromBook(book, media)
val patch = isbnBarcodeProvider.getBookMetadataFromBook(BookWithMedia(book, media))
// then
assertThat(patch).isNull()
@ -56,7 +57,7 @@ class IsbnBarcodeProviderTest {
val media = Media(pages = listOf(BookPage("page", "image/jpeg")))
// when
val patch = isbnBarcodeProvider.getBookMetadataFromBook(book, media)
val patch = isbnBarcodeProvider.getBookMetadataFromBook(BookWithMedia(book, media))
// then
assertThat(patch).isNull()

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.BookWithMedia
import org.gotson.komga.domain.model.Media
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.makeBook
@ -53,7 +54,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(title).isEqualTo("title")
@ -86,7 +87,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(title).isNull()
@ -106,7 +107,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(releaseDate).isNull()
@ -121,7 +122,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(releaseDate).isEqualTo(LocalDate.of(2020, 1, 1))
@ -142,7 +143,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(authors).hasSize(7)
@ -165,7 +166,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
with(patch!!) {
assertThat(authors).hasSize(14)
@ -179,7 +180,7 @@ class ComicInfoProviderTest {
val book = makeBook("book")
val media = Media(Media.Status.READY)
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)
val patch = comicInfoProvider.getBookMetadataFromBook(BookWithMedia(book, media))
assertThat(patch).isNull()
}
@ -201,7 +202,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
val patch = comicInfoProvider.getSeriesMetadataFromBook(BookWithMedia(book, media))!!
with(patch) {
assertThat(title).isEqualTo("series")
@ -226,7 +227,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
val patch = comicInfoProvider.getSeriesMetadataFromBook(BookWithMedia(book, media))!!
with(patch) {
assertThat(title).isEqualTo("series (2020)")
@ -242,7 +243,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
val patch = comicInfoProvider.getSeriesMetadataFromBook(BookWithMedia(book, media))!!
with(patch) {
assertThat(title).isEqualTo("series")
@ -257,7 +258,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
val patch = comicInfoProvider.getSeriesMetadataFromBook(BookWithMedia(book, media))!!
with(patch) {
assertThat(language).isNull()
@ -277,7 +278,7 @@ class ComicInfoProviderTest {
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
val patch = comicInfoProvider.getSeriesMetadataFromBook(BookWithMedia(book, media))!!
with(patch) {
assertThat(title).isNull()