mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 08:32:25 +01:00
parent
e5343d7ab4
commit
c1e435762c
27 changed files with 787 additions and 24 deletions
|
|
@ -11,3 +11,10 @@ ERR_1005 | Unknown error while analyzing book
|
||||||
ERR_1006 | Book does not contain any page
|
ERR_1006 | Book does not contain any page
|
||||||
ERR_1007 | Some entries could not be analyzed
|
ERR_1007 | Some entries could not be analyzed
|
||||||
ERR_1008 | Unknown error while getting book's entries
|
ERR_1008 | Unknown error while getting book's entries
|
||||||
|
ERR_1009 | A read list with that name already exists
|
||||||
|
ERR_1010 | No books were matched within the read list request
|
||||||
|
ERR_1011 | No unique match for series
|
||||||
|
ERR_1012 | No match for series
|
||||||
|
ERR_1013 | No unique match for book number within series
|
||||||
|
ERR_1014 | No match for book number within series
|
||||||
|
ERR_1015 | Error while deserializing ComicRack ReadingList
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.gotson.komga.domain.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a request to create a reading list.
|
||||||
|
*/
|
||||||
|
data class ReadListRequest(
|
||||||
|
val name: String,
|
||||||
|
val books: List<ReadListRequestBook>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReadListRequestBook(
|
||||||
|
val series: String,
|
||||||
|
val number: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReadListRequestResult(
|
||||||
|
val readList: ReadList?,
|
||||||
|
val unmatchedBooks: List<ReadListRequestResultBook> = emptyList(),
|
||||||
|
val errorCode: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReadListRequestResultBook(
|
||||||
|
val book: ReadListRequestBook,
|
||||||
|
val errorCode: String = "",
|
||||||
|
)
|
||||||
|
|
@ -11,6 +11,7 @@ interface SeriesRepository {
|
||||||
fun findByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection<URL>): Collection<Series>
|
fun findByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection<URL>): Collection<Series>
|
||||||
fun findByLibraryIdAndUrl(libraryId: String, url: URL): Series?
|
fun findByLibraryIdAndUrl(libraryId: String, url: URL): Series?
|
||||||
fun findAll(search: SeriesSearch): Collection<Series>
|
fun findAll(search: SeriesSearch): Collection<Series>
|
||||||
|
fun findByTitle(title: String): Collection<Series>
|
||||||
|
|
||||||
fun getLibraryId(seriesId: String): String?
|
fun getLibraryId(seriesId: String): String?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.barcode.IsbnBarcodeProvider
|
import org.gotson.komga.infrastructure.metadata.barcode.IsbnBarcodeProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.ComicInfoProvider
|
import org.gotson.komga.infrastructure.metadata.comicrack.ComicInfoProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.epub.EpubMetadataProvider
|
import org.gotson.komga.infrastructure.metadata.epub.EpubMetadataProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.localartwork.LocalArtworkProvider
|
import org.gotson.komga.infrastructure.metadata.localartwork.LocalArtworkProvider
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package org.gotson.komga.domain.service
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.gotson.komga.domain.model.DuplicateNameException
|
import org.gotson.komga.domain.model.DuplicateNameException
|
||||||
import org.gotson.komga.domain.model.ReadList
|
import org.gotson.komga.domain.model.ReadList
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestResult
|
||||||
import org.gotson.komga.domain.persistence.ReadListRepository
|
import org.gotson.komga.domain.persistence.ReadListRepository
|
||||||
import org.gotson.komga.infrastructure.image.MosaicGenerator
|
import org.gotson.komga.infrastructure.image.MosaicGenerator
|
||||||
|
import org.gotson.komga.infrastructure.metadata.comicrack.ReadListProvider
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
@ -13,7 +15,9 @@ private val logger = KotlinLogging.logger {}
|
||||||
class ReadListLifecycle(
|
class ReadListLifecycle(
|
||||||
private val readListRepository: ReadListRepository,
|
private val readListRepository: ReadListRepository,
|
||||||
private val bookLifecycle: BookLifecycle,
|
private val bookLifecycle: BookLifecycle,
|
||||||
private val mosaicGenerator: MosaicGenerator
|
private val mosaicGenerator: MosaicGenerator,
|
||||||
|
private val readListMatcher: ReadListMatcher,
|
||||||
|
private val readListProvider: ReadListProvider,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Throws(
|
@Throws(
|
||||||
|
|
@ -55,4 +59,21 @@ class ReadListLifecycle(
|
||||||
val images = ids.mapNotNull { bookLifecycle.getThumbnailBytes(it) }
|
val images = ids.mapNotNull { bookLifecycle.getThumbnailBytes(it) }
|
||||||
return mosaicGenerator.createMosaic(images)
|
return mosaicGenerator.createMosaic(images)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun importReadList(fileContent: ByteArray): ReadListRequestResult {
|
||||||
|
val request = try {
|
||||||
|
readListProvider.importFromCbl(fileContent) ?: return ReadListRequestResult(null, emptyList(), "ERR_1015")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return ReadListRequestResult(null, emptyList(), "ERR_1015")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = readListMatcher.matchReadListRequest(request)
|
||||||
|
return when {
|
||||||
|
result.readList != null -> {
|
||||||
|
readListRepository.insert(result.readList)
|
||||||
|
result.copy(readList = readListRepository.findByIdOrNull(result.readList.id)!!)
|
||||||
|
}
|
||||||
|
else -> result
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.gotson.komga.domain.service
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.gotson.komga.domain.model.ReadList
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequest
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestResult
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestResultBook
|
||||||
|
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||||
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
|
import org.gotson.komga.domain.persistence.ReadListRepository
|
||||||
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
|
import org.gotson.komga.infrastructure.language.toIndexedMap
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ReadListMatcher(
|
||||||
|
private val readListRepository: ReadListRepository,
|
||||||
|
private val seriesRepository: SeriesRepository,
|
||||||
|
private val bookRepository: BookRepository,
|
||||||
|
private val bookMetadataRepository: BookMetadataRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun matchReadListRequest(request: ReadListRequest): ReadListRequestResult {
|
||||||
|
logger.info { "Trying to match $request" }
|
||||||
|
if (readListRepository.existsByName(request.name)) {
|
||||||
|
return ReadListRequestResult(readList = null, unmatchedBooks = request.books.map { ReadListRequestResultBook(it) }, errorCode = "ERR_1009")
|
||||||
|
}
|
||||||
|
|
||||||
|
val bookIds = mutableListOf<String>()
|
||||||
|
val unmatchedBooks = mutableListOf<ReadListRequestResultBook>()
|
||||||
|
|
||||||
|
request.books.forEach { book ->
|
||||||
|
val seriesMatches = seriesRepository.findByTitle(book.series)
|
||||||
|
when {
|
||||||
|
seriesMatches.size > 1 -> unmatchedBooks += ReadListRequestResultBook(book, "ERR_1011")
|
||||||
|
seriesMatches.isEmpty() -> unmatchedBooks += ReadListRequestResultBook(book, "ERR_1012")
|
||||||
|
else -> {
|
||||||
|
val seriesId = seriesMatches.first().id
|
||||||
|
val seriesBooks = bookRepository.findBySeriesId(seriesId)
|
||||||
|
val bookMatches = bookMetadataRepository.findByIds(seriesBooks.map { it.id })
|
||||||
|
.filter { it.number.toIntOrNull()?.equals(book.number) ?: false }
|
||||||
|
.map { it.bookId }
|
||||||
|
when {
|
||||||
|
bookMatches.size > 1 -> unmatchedBooks += ReadListRequestResultBook(book, "ERR_1013")
|
||||||
|
bookMatches.isEmpty() -> unmatchedBooks += ReadListRequestResultBook(book, "ERR_1014")
|
||||||
|
else -> bookIds.add(bookMatches.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (bookIds.isNotEmpty())
|
||||||
|
ReadListRequestResult(
|
||||||
|
readList = ReadList(name = request.name, bookIds = bookIds.toIndexedMap()),
|
||||||
|
unmatchedBooks = unmatchedBooks
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
ReadListRequestResult(
|
||||||
|
readList = null,
|
||||||
|
unmatchedBooks = unmatchedBooks,
|
||||||
|
errorCode = "ERR_1010"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,14 @@ class SeriesDao(
|
||||||
.fetchOneInto(s)
|
.fetchOneInto(s)
|
||||||
?.toDomain()
|
?.toDomain()
|
||||||
|
|
||||||
|
override fun findByTitle(title: String): Collection<Series> =
|
||||||
|
dsl.selectDistinct(*s.fields())
|
||||||
|
.from(s)
|
||||||
|
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
||||||
|
.where(d.TITLE.equalIgnoreCase(title))
|
||||||
|
.fetchInto(s)
|
||||||
|
.map { it.toDomain() }
|
||||||
|
|
||||||
override fun getLibraryId(seriesId: String): String? =
|
override fun getLibraryId(seriesId: String): String? =
|
||||||
dsl.select(s.LIBRARY_ID)
|
dsl.select(s.LIBRARY_ID)
|
||||||
.from(s)
|
.from(s)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo
|
package org.gotson.komga.infrastructure.metadata.comicrack
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
|
|
@ -11,8 +11,8 @@ import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||||
import org.gotson.komga.domain.service.BookAnalyzer
|
import org.gotson.komga.domain.service.BookAnalyzer
|
||||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.ComicInfo
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.Manga
|
||||||
import org.gotson.komga.infrastructure.validation.BCP47TagValidator
|
import org.gotson.komga.infrastructure.validation.BCP47TagValidator
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
@ -45,7 +45,12 @@ class ComicInfoProvider(
|
||||||
|
|
||||||
val readLists = mutableListOf<BookMetadataPatch.ReadListEntry>()
|
val readLists = mutableListOf<BookMetadataPatch.ReadListEntry>()
|
||||||
if (!comicInfo.alternateSeries.isNullOrBlank()) {
|
if (!comicInfo.alternateSeries.isNullOrBlank()) {
|
||||||
readLists.add(BookMetadataPatch.ReadListEntry(comicInfo.alternateSeries!!, comicInfo.alternateNumber?.toIntOrNull()))
|
readLists.add(
|
||||||
|
BookMetadataPatch.ReadListEntry(
|
||||||
|
comicInfo.alternateSeries!!,
|
||||||
|
comicInfo.alternateNumber?.toIntOrNull()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
comicInfo.storyArc?.let { value ->
|
comicInfo.storyArc?.let { value ->
|
||||||
|
|
@ -75,9 +80,7 @@ class ComicInfoProvider(
|
||||||
}
|
}
|
||||||
|
|
||||||
val genres = comicInfo.genre?.split(',')?.mapNotNull { it.trim().ifBlank { null } }
|
val genres = comicInfo.genre?.split(',')?.mapNotNull { it.trim().ifBlank { null } }
|
||||||
val series = comicInfo.series?.ifBlank { null }?.let { series ->
|
val series = computeSeriesFromSeriesAndVolume(comicInfo.series, comicInfo.volume)
|
||||||
series + (comicInfo.volume?.let { if (it != 1) " ($it)" else "" } ?: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return SeriesMetadataPatch(
|
return SeriesMetadataPatch(
|
||||||
title = series,
|
title = series,
|
||||||
|
|
@ -115,3 +118,8 @@ class ComicInfoProvider(
|
||||||
if (list.isNotEmpty()) list.map { Author(it, role) } else null
|
if (list.isNotEmpty()) list.map { Author(it, role) } else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun computeSeriesFromSeriesAndVolume(series: String?, volume: Int?): String? =
|
||||||
|
series?.ifBlank { null }?.let { s ->
|
||||||
|
s + (volume?.let { if (it != 1) " ($it)" else "" } ?: "")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.gotson.komga.infrastructure.metadata.comicrack
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequest
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestBook
|
||||||
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.ReadingList
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ReadListProvider(
|
||||||
|
@Autowired(required = false) private val mapper: XmlMapper = XmlMapper(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun importFromCbl(cbl: ByteArray): ReadListRequest? {
|
||||||
|
try {
|
||||||
|
val readingList = mapper.readValue(cbl, ReadingList::class.java)
|
||||||
|
if (readingList.books.isNotEmpty()) {
|
||||||
|
logger.debug { "Trying to convert ComicRack ReadingList to ReadListRequest: $readingList" }
|
||||||
|
if (readingList.name.isNullOrBlank()) {
|
||||||
|
logger.warn { "ReadingList has no name, skipping" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val books = readingList.books.mapNotNull {
|
||||||
|
val series = computeSeriesFromSeriesAndVolume(it.series, it.volume)
|
||||||
|
if (!series.isNullOrBlank() && it.number != null)
|
||||||
|
ReadListRequestBook(series, it.number!!)
|
||||||
|
else {
|
||||||
|
logger.warn { "Book is missing series or number, skipping: $it" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (books.isNotEmpty())
|
||||||
|
return ReadListRequest(name = readingList.name!!, books = books)
|
||||||
|
.also { logger.debug { "Converted request: $it" } }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Error while trying to parse ComicRack ReadingList" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo.dto
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator
|
import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
class Book {
|
||||||
|
@JsonProperty(value = "Series")
|
||||||
|
var series: String? = null
|
||||||
|
|
||||||
|
@JsonProperty(value = "Number")
|
||||||
|
var number: Int? = null
|
||||||
|
|
||||||
|
@JsonProperty(value = "Volume")
|
||||||
|
var volume: Int? = null
|
||||||
|
|
||||||
|
@JsonProperty(value = "Year")
|
||||||
|
var year: Int? = null
|
||||||
|
|
||||||
|
@JsonProperty(value = "FileName")
|
||||||
|
var fileName: String? = null
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Book(series=$series, number=$number, volume=$volume, year=$year, fileName=$fileName)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo.dto
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo.dto
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator
|
import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSetter
|
||||||
|
import com.fasterxml.jackson.annotation.Nulls
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
class ReadingList {
|
||||||
|
|
||||||
|
@JsonProperty(value = "Name")
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
|
@JacksonXmlElementWrapper(useWrapping = true)
|
||||||
|
@JacksonXmlProperty(localName = "Books")
|
||||||
|
@JsonSetter(nulls = Nulls.AS_EMPTY)
|
||||||
|
var books: List<Book> = emptyList()
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ReadingList(name=$name, books=$books)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo.dto
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator
|
import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@ import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||||
import org.gotson.komga.interfaces.rest.dto.BookDto
|
import org.gotson.komga.interfaces.rest.dto.BookDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.ReadListCreationDto
|
import org.gotson.komga.interfaces.rest.dto.ReadListCreationDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.ReadListDto
|
import org.gotson.komga.interfaces.rest.dto.ReadListDto
|
||||||
|
import org.gotson.komga.interfaces.rest.dto.ReadListRequestResultDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.ReadListUpdateDto
|
import org.gotson.komga.interfaces.rest.dto.ReadListUpdateDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.restrictUrl
|
import org.gotson.komga.interfaces.rest.dto.restrictUrl
|
||||||
import org.gotson.komga.interfaces.rest.dto.toDto
|
import org.gotson.komga.interfaces.rest.dto.toDto
|
||||||
|
|
@ -41,6 +42,7 @@ import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.validation.Valid
|
import javax.validation.Valid
|
||||||
|
|
@ -136,6 +138,13 @@ class ReadListController(
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/import")
|
||||||
|
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||||
|
fun importFromComicRackList(
|
||||||
|
@RequestParam("files") files: List<MultipartFile>,
|
||||||
|
): List<ReadListRequestResultDto> =
|
||||||
|
files.map { readListLifecycle.importReadList(it.bytes).toDto(it.originalFilename) }
|
||||||
|
|
||||||
@PatchMapping("{id}")
|
@PatchMapping("{id}")
|
||||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.gotson.komga.interfaces.rest.dto
|
||||||
|
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestBook
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestResult
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestResultBook
|
||||||
|
|
||||||
|
data class ReadListRequestBookDto(
|
||||||
|
val series: String,
|
||||||
|
val number: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReadListRequestResultDto(
|
||||||
|
val readList: ReadListDto?,
|
||||||
|
val unmatchedBooks: List<ReadListRequestResultBookDto> = emptyList(),
|
||||||
|
val errorCode: String = "",
|
||||||
|
val requestName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReadListRequestResultBookDto(
|
||||||
|
val book: ReadListRequestBookDto,
|
||||||
|
val errorCode: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ReadListRequestResult.toDto(requestName: String?) =
|
||||||
|
ReadListRequestResultDto(
|
||||||
|
readList = readList?.toDto(),
|
||||||
|
unmatchedBooks = unmatchedBooks.map { it.toDto() },
|
||||||
|
errorCode = errorCode,
|
||||||
|
requestName = requestName ?: "",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ReadListRequestResultBook.toDto() =
|
||||||
|
ReadListRequestResultBookDto(
|
||||||
|
book = book.toDto(),
|
||||||
|
errorCode = errorCode,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ReadListRequestBook.toDto() =
|
||||||
|
ReadListRequestBookDto(
|
||||||
|
series = series,
|
||||||
|
number = number,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package org.gotson.komga.domain.service
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.gotson.komga.domain.model.ReadList
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequest
|
||||||
|
import org.gotson.komga.domain.model.ReadListRequestBook
|
||||||
|
import org.gotson.komga.domain.model.makeBook
|
||||||
|
import org.gotson.komga.domain.model.makeLibrary
|
||||||
|
import org.gotson.komga.domain.model.makeSeries
|
||||||
|
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||||
|
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||||
|
import org.gotson.komga.domain.persistence.ReadListRepository
|
||||||
|
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||||
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
|
import org.junit.jupiter.api.AfterAll
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension::class)
|
||||||
|
@SpringBootTest
|
||||||
|
class ReadListMatcherTest(
|
||||||
|
@Autowired private val seriesLifecycle: SeriesLifecycle,
|
||||||
|
@Autowired private val seriesRepository: SeriesRepository,
|
||||||
|
@Autowired private val seriesMetadataRepository: SeriesMetadataRepository,
|
||||||
|
@Autowired private val readListLifecycle: ReadListLifecycle,
|
||||||
|
@Autowired private val readListRepository: ReadListRepository,
|
||||||
|
@Autowired private val libraryRepository: LibraryRepository,
|
||||||
|
@Autowired private val bookMetadataRepository: BookMetadataRepository,
|
||||||
|
@Autowired private val readListMatcher: ReadListMatcher,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val library = makeLibrary()
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
fun `setup library`() {
|
||||||
|
libraryRepository.insert(library)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun `teardown library`() {
|
||||||
|
libraryRepository.deleteAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun `clear repository`() {
|
||||||
|
readListRepository.deleteAll()
|
||||||
|
seriesLifecycle.deleteMany(seriesRepository.findAll().map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given request with existing series and books when matching then result contains a read list with all books`() {
|
||||||
|
// given
|
||||||
|
val booksSeries1 = listOf(
|
||||||
|
makeBook("book1", libraryId = library.id),
|
||||||
|
makeBook("book5", libraryId = library.id),
|
||||||
|
)
|
||||||
|
makeSeries(name = "batman", libraryId = library.id).let { s ->
|
||||||
|
seriesLifecycle.createSeries(s)
|
||||||
|
seriesLifecycle.addBooks(s, booksSeries1)
|
||||||
|
seriesLifecycle.sortBooks(s)
|
||||||
|
seriesMetadataRepository.findById(s.id).let {
|
||||||
|
seriesMetadataRepository.update(it.copy(title = "Batman: White Knight"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val booksSeries2 = listOf(
|
||||||
|
makeBook("book1", libraryId = library.id),
|
||||||
|
makeBook("book2", libraryId = library.id),
|
||||||
|
)
|
||||||
|
makeSeries(name = "joker", libraryId = library.id).let { s ->
|
||||||
|
seriesLifecycle.createSeries(s)
|
||||||
|
seriesLifecycle.addBooks(s, booksSeries2)
|
||||||
|
seriesLifecycle.sortBooks(s)
|
||||||
|
|
||||||
|
bookMetadataRepository.findById(booksSeries2[0].id).let {
|
||||||
|
bookMetadataRepository.update(it.copy(number = "025"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = ReadListRequest(
|
||||||
|
name = "readlist",
|
||||||
|
books = listOf(
|
||||||
|
ReadListRequestBook(series = "Batman: White Knight", number = 1),
|
||||||
|
ReadListRequestBook(series = "joker", number = 2),
|
||||||
|
ReadListRequestBook(series = "Batman: White Knight", number = 2),
|
||||||
|
ReadListRequestBook(series = "joker", number = 25),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = readListMatcher.matchReadListRequest(request)
|
||||||
|
|
||||||
|
// then
|
||||||
|
with(result) {
|
||||||
|
assertThat(readList).isNotNull
|
||||||
|
assertThat(unmatchedBooks).isEmpty()
|
||||||
|
assertThat(errorCode).isBlank()
|
||||||
|
with(readList!!) {
|
||||||
|
assertThat(name).isEqualTo(request.name)
|
||||||
|
assertThat(bookIds).hasSize(4)
|
||||||
|
assertThat(bookIds).containsExactlyEntriesOf(
|
||||||
|
mapOf(
|
||||||
|
0 to booksSeries1[0].id,
|
||||||
|
1 to booksSeries2[1].id,
|
||||||
|
2 to booksSeries1[1].id,
|
||||||
|
3 to booksSeries2[0].id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given request with existing read list when matching then result has no readlist and appropriate error code`() {
|
||||||
|
// given
|
||||||
|
readListLifecycle.addReadList(
|
||||||
|
ReadList(name = "my ReadList")
|
||||||
|
)
|
||||||
|
|
||||||
|
val request = ReadListRequest(
|
||||||
|
name = "my readlist",
|
||||||
|
books = listOf(
|
||||||
|
ReadListRequestBook(series = "batman: white knight", number = 1),
|
||||||
|
ReadListRequestBook(series = "joker", number = 2),
|
||||||
|
ReadListRequestBook(series = "BATMAN: WHITE KNIGHT", number = 2),
|
||||||
|
ReadListRequestBook(series = "joker", number = 25),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = readListMatcher.matchReadListRequest(request)
|
||||||
|
|
||||||
|
// then
|
||||||
|
with(result) {
|
||||||
|
assertThat(readList).isNull()
|
||||||
|
assertThat(errorCode).isEqualTo("ERR_1009")
|
||||||
|
assertThat(unmatchedBooks.map { it.book }).containsExactlyElementsOf(request.books)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given request and some matching series or books when matching then returns result with appropriate error codes`() {
|
||||||
|
// given
|
||||||
|
val booksSeries1 = listOf(
|
||||||
|
makeBook("book1", libraryId = library.id),
|
||||||
|
makeBook("book5", libraryId = library.id),
|
||||||
|
)
|
||||||
|
makeSeries(name = "batman", libraryId = library.id).let { s ->
|
||||||
|
seriesLifecycle.createSeries(s)
|
||||||
|
seriesLifecycle.addBooks(s, booksSeries1)
|
||||||
|
seriesLifecycle.sortBooks(s)
|
||||||
|
|
||||||
|
bookMetadataRepository.findById(booksSeries1[0].id).let {
|
||||||
|
bookMetadataRepository.update(it.copy(number = "2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val booksSeries2 = listOf(
|
||||||
|
makeBook("book1", libraryId = library.id),
|
||||||
|
makeBook("book2", libraryId = library.id),
|
||||||
|
)
|
||||||
|
makeSeries(name = "joker", libraryId = library.id).let { s ->
|
||||||
|
seriesLifecycle.createSeries(s)
|
||||||
|
seriesLifecycle.addBooks(s, booksSeries2)
|
||||||
|
seriesLifecycle.sortBooks(s)
|
||||||
|
}
|
||||||
|
makeSeries(name = "joker", libraryId = library.id).let { s ->
|
||||||
|
seriesLifecycle.createSeries(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = ReadListRequest(
|
||||||
|
name = "readlist",
|
||||||
|
books = listOf(
|
||||||
|
ReadListRequestBook(series = "tokyo ghost", number = 1),
|
||||||
|
ReadListRequestBook(series = "batman", number = 3),
|
||||||
|
ReadListRequestBook(series = "joker", number = 3),
|
||||||
|
ReadListRequestBook(series = "batman", number = 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = readListMatcher.matchReadListRequest(request)
|
||||||
|
|
||||||
|
// then
|
||||||
|
with(result) {
|
||||||
|
assertThat(readList).isNull()
|
||||||
|
assertThat(errorCode).isEqualTo("ERR_1010")
|
||||||
|
|
||||||
|
assertThat(unmatchedBooks).hasSize(4)
|
||||||
|
|
||||||
|
assertThat(unmatchedBooks[0].book).isEqualTo(request.books[0])
|
||||||
|
assertThat(unmatchedBooks[0].errorCode).isEqualTo("ERR_1012")
|
||||||
|
|
||||||
|
assertThat(unmatchedBooks[1].book).isEqualTo(request.books[1])
|
||||||
|
assertThat(unmatchedBooks[1].errorCode).isEqualTo("ERR_1014")
|
||||||
|
|
||||||
|
assertThat(unmatchedBooks[2].book).isEqualTo(request.books[2])
|
||||||
|
assertThat(unmatchedBooks[2].errorCode).isEqualTo("ERR_1011")
|
||||||
|
|
||||||
|
assertThat(unmatchedBooks[3].book).isEqualTo(request.books[3])
|
||||||
|
assertThat(unmatchedBooks[3].errorCode).isEqualTo("ERR_1013")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo
|
package org.gotson.komga.infrastructure.metadata.comicrack
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
|
@ -8,12 +8,16 @@ import org.gotson.komga.domain.model.Media
|
||||||
import org.gotson.komga.domain.model.SeriesMetadata
|
import org.gotson.komga.domain.model.SeriesMetadata
|
||||||
import org.gotson.komga.domain.model.makeBook
|
import org.gotson.komga.domain.model.makeBook
|
||||||
import org.gotson.komga.domain.service.BookAnalyzer
|
import org.gotson.komga.domain.service.BookAnalyzer
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.AgeRating
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.AgeRating
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.ComicInfo
|
||||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.Manga
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
class ComicInfoProviderTest {
|
class ComicInfoProviderTest {
|
||||||
|
|
||||||
|
|
@ -285,4 +289,18 @@ class ComicInfoProviderTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun computeSeriesFromSeriesAndVolumeArguments() = Stream.of(
|
||||||
|
Arguments.of("", null, null),
|
||||||
|
Arguments.of(null, null, null),
|
||||||
|
Arguments.of("Series", null, "Series"),
|
||||||
|
Arguments.of("Series", 1, "Series"),
|
||||||
|
Arguments.of("Series", 10, "Series (10)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("computeSeriesFromSeriesAndVolumeArguments")
|
||||||
|
fun `given series and volume when computing series name then it is correct`(series: String?, volume: Int?, expected: String?) {
|
||||||
|
assertThat(computeSeriesFromSeriesAndVolume(series, volume)).isEqualTo(expected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package org.gotson.komga.infrastructure.metadata.comicrack
|
||||||
|
|
||||||
|
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.infrastructure.metadata.comicrack.dto.Book
|
||||||
|
import org.gotson.komga.infrastructure.metadata.comicrack.dto.ReadingList
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class ReadListProviderTest {
|
||||||
|
|
||||||
|
private val mockMapper = mockk<XmlMapper>()
|
||||||
|
private val readListProvider = ReadListProvider(mockMapper)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given CBL list with books when getting ReadListRequest then it is valid`() {
|
||||||
|
// given
|
||||||
|
val cbl = ReadingList().apply {
|
||||||
|
name = "my read list"
|
||||||
|
books = listOf(
|
||||||
|
Book().apply {
|
||||||
|
series = "series 1"
|
||||||
|
number = 4
|
||||||
|
volume = 2005
|
||||||
|
},
|
||||||
|
Book().apply {
|
||||||
|
series = "series 2"
|
||||||
|
number = 1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
every { mockMapper.readValue(any<ByteArray>(), ReadingList::class.java) } returns cbl
|
||||||
|
|
||||||
|
// when
|
||||||
|
val request = readListProvider.importFromCbl(ByteArray(0))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(request).isNotNull
|
||||||
|
with(request!!) {
|
||||||
|
assertThat(name).isEqualTo(cbl.name)
|
||||||
|
assertThat(books).hasSize(2)
|
||||||
|
|
||||||
|
with(books[0]) {
|
||||||
|
assertThat(series).isEqualTo("series 1 (2005)")
|
||||||
|
assertThat(number).isEqualTo(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(books[1]) {
|
||||||
|
assertThat(series).isEqualTo("series 2")
|
||||||
|
assertThat(number).isEqualTo(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given CBL list with invalid books when getting ReadListRequest then it is null`() {
|
||||||
|
// given
|
||||||
|
val cbl = ReadingList().apply {
|
||||||
|
name = "my read list"
|
||||||
|
books = listOf(
|
||||||
|
Book().apply {
|
||||||
|
series = " "
|
||||||
|
number = 4
|
||||||
|
volume = 2005
|
||||||
|
},
|
||||||
|
Book().apply {
|
||||||
|
series = null
|
||||||
|
number = 1
|
||||||
|
},
|
||||||
|
Book().apply {
|
||||||
|
series = "Series"
|
||||||
|
number = null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
every { mockMapper.readValue(any<ByteArray>(), ReadingList::class.java) } returns cbl
|
||||||
|
|
||||||
|
// when
|
||||||
|
val request = readListProvider.importFromCbl(ByteArray(0))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(request).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given CBL list without books when getting ReadListRequest then it is null`() {
|
||||||
|
// given
|
||||||
|
val cbl = ReadingList().apply {
|
||||||
|
name = "my read list"
|
||||||
|
books = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
every { mockMapper.readValue(any<ByteArray>(), ReadingList::class.java) } returns cbl
|
||||||
|
|
||||||
|
// when
|
||||||
|
val request = readListProvider.importFromCbl(ByteArray(0))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(request).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given CBL list without name when getting ReadListRequest then it is null`() {
|
||||||
|
// given
|
||||||
|
val cbl = ReadingList().apply {
|
||||||
|
name = null
|
||||||
|
books = listOf(
|
||||||
|
Book().apply {
|
||||||
|
series = "series 1"
|
||||||
|
number = 4
|
||||||
|
volume = 2005
|
||||||
|
},
|
||||||
|
Book().apply {
|
||||||
|
series = "series 2"
|
||||||
|
number = 1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
every { mockMapper.readValue(any<ByteArray>(), ReadingList::class.java) } returns cbl
|
||||||
|
|
||||||
|
// when
|
||||||
|
val request = readListProvider.importFromCbl(ByteArray(0))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(request).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given CBL list with blank name when getting ReadListRequest then it is null`() {
|
||||||
|
// given
|
||||||
|
val cbl = ReadingList().apply {
|
||||||
|
name = " "
|
||||||
|
books = listOf(
|
||||||
|
Book().apply {
|
||||||
|
series = "series 1"
|
||||||
|
number = 4
|
||||||
|
volume = 2005
|
||||||
|
},
|
||||||
|
Book().apply {
|
||||||
|
series = "series 2"
|
||||||
|
number = 1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
every { mockMapper.readValue(any<ByteArray>(), ReadingList::class.java) } returns cbl
|
||||||
|
|
||||||
|
// when
|
||||||
|
val request = readListProvider.importFromCbl(ByteArray(0))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(request).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.infrastructure.metadata.comicinfo.dto
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.core.io.ClassPathResource
|
import org.springframework.core.io.ClassPathResource
|
||||||
|
|
@ -9,9 +10,9 @@ class ComicInfoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given valid xml file when deserializing then properties are available`() {
|
fun `given valid xml file when deserializing then properties are available`() {
|
||||||
val file = ClassPathResource("comicinfo/ComicInfo.xml")
|
val file = ClassPathResource("comicrack/ComicInfo.xml")
|
||||||
val mapper = XmlMapper()
|
val mapper = XmlMapper()
|
||||||
val comicInfo = mapper.readValue(file.url, ComicInfo::class.java)
|
val comicInfo = mapper.readValue<ComicInfo>(file.url)
|
||||||
|
|
||||||
with(comicInfo) {
|
with(comicInfo) {
|
||||||
assertThat(title).isEqualTo("v01 - Preludes & Nocturnes - 30th Anniversary Edition")
|
assertThat(title).isEqualTo("v01 - Preludes & Nocturnes - 30th Anniversary Edition")
|
||||||
|
|
@ -34,9 +35,9 @@ class ComicInfoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given another valid xml file when deserializing then properties are available`() {
|
fun `given another valid xml file when deserializing then properties are available`() {
|
||||||
val file = ClassPathResource("comicinfo/ComicInfo2.xml")
|
val file = ClassPathResource("comicrack/ComicInfo2.xml")
|
||||||
val mapper = XmlMapper()
|
val mapper = XmlMapper()
|
||||||
val comicInfo = mapper.readValue(file.url, ComicInfo::class.java)
|
val comicInfo = mapper.readValue<ComicInfo>(file.url)
|
||||||
|
|
||||||
with(comicInfo) {
|
with(comicInfo) {
|
||||||
assertThat(title).isEqualTo("v01 - Preludes & Nocturnes - 30th Anniversary Edition")
|
assertThat(title).isEqualTo("v01 - Preludes & Nocturnes - 30th Anniversary Edition")
|
||||||
|
|
@ -62,9 +63,9 @@ class ComicInfoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given incorrect enum values when deserializing then it is ignored`() {
|
fun `given incorrect enum values when deserializing then it is ignored`() {
|
||||||
val file = ClassPathResource("comicinfo/InvalidEnumValues.xml")
|
val file = ClassPathResource("comicrack/InvalidEnumValues.xml")
|
||||||
val mapper = XmlMapper()
|
val mapper = XmlMapper()
|
||||||
val comicInfo = mapper.readValue(file.url, ComicInfo::class.java)
|
val comicInfo = mapper.readValue<ComicInfo>(file.url)
|
||||||
|
|
||||||
with(comicInfo) {
|
with(comicInfo) {
|
||||||
assertThat(ageRating).isNull()
|
assertThat(ageRating).isNull()
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.gotson.komga.infrastructure.metadata.comicrack.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.core.io.ClassPathResource
|
||||||
|
|
||||||
|
class ReadingListTest {
|
||||||
|
|
||||||
|
private val mapper = XmlMapper()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given valid xml file when deserializing then properties are available`() {
|
||||||
|
val file = ClassPathResource("comicrack/ReadingList.xml")
|
||||||
|
val readingList = mapper.readValue<ReadingList>(file.url)
|
||||||
|
|
||||||
|
with(readingList) {
|
||||||
|
assertThat(name).isEqualTo("Civil War")
|
||||||
|
assertThat(books).hasSize(3)
|
||||||
|
|
||||||
|
with(books[0]) {
|
||||||
|
assertThat(series).isEqualTo("Civil War")
|
||||||
|
assertThat(number).isEqualTo(1)
|
||||||
|
assertThat(volume).isEqualTo(2006)
|
||||||
|
assertThat(year).isEqualTo(2006)
|
||||||
|
assertThat(fileName).isEqualTo("Civil War Vol.2006 #01 (July, 2006)")
|
||||||
|
}
|
||||||
|
|
||||||
|
with(books[1]) {
|
||||||
|
assertThat(series).isEqualTo("Wolverine")
|
||||||
|
assertThat(number).isEqualTo(42)
|
||||||
|
assertThat(volume).isEqualTo(2003)
|
||||||
|
assertThat(year).isEqualTo(2006)
|
||||||
|
assertThat(fileName).isEqualTo("Wolverine Vol.2003 #42 (July, 2006)")
|
||||||
|
}
|
||||||
|
|
||||||
|
with(books[2]) {
|
||||||
|
assertThat(series).isEqualTo("X-Factor")
|
||||||
|
assertThat(number).isEqualTo(8)
|
||||||
|
assertThat(volume).isEqualTo(2006)
|
||||||
|
assertThat(year).isEqualTo(2006)
|
||||||
|
assertThat(fileName).isEqualTo("X-Factor Vol.2006 #08 (August, 2006)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given valid xml file for smart list when deserializing then properties are available`() {
|
||||||
|
val file = ClassPathResource("comicrack/ReadingList_SmartList.xml")
|
||||||
|
val mapper = XmlMapper()
|
||||||
|
val readingList = mapper.readValue<ReadingList>(file.url)
|
||||||
|
|
||||||
|
with(readingList) {
|
||||||
|
assertThat(name).isEqualTo("Golden Age")
|
||||||
|
assertThat(books).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
komga/src/test/resources/comicrack/ReadingList.xml
Normal file
19
komga/src/test/resources/comicrack/ReadingList.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<ReadingList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<Name>Civil War</Name>
|
||||||
|
<Books>
|
||||||
|
<Book Series="Civil War" Number="1" Volume="2006" Year="2006">
|
||||||
|
<Id>1b21c8c4-e8d7-44f6-992d-97d24ce8123e</Id>
|
||||||
|
<FileName>Civil War Vol.2006 #01 (July, 2006)</FileName>
|
||||||
|
</Book>
|
||||||
|
<Book Series="Wolverine" Number="42" Volume="2003" Year="2006">
|
||||||
|
<Id>29a69cbf-af64-471d-889c-8fc0f0080f7c</Id>
|
||||||
|
<FileName>Wolverine Vol.2003 #42 (July, 2006)</FileName>
|
||||||
|
</Book>
|
||||||
|
<Book Series="X-Factor" Number="8" Volume="2006" Year="2006">
|
||||||
|
<Id>ec70e585-5a80-428c-a67e-fd22b668449b</Id>
|
||||||
|
<FileName>X-Factor Vol.2006 #08 (August, 2006)</FileName>
|
||||||
|
</Book>
|
||||||
|
</Books>
|
||||||
|
<Matchers />
|
||||||
|
</ReadingList>
|
||||||
13
komga/src/test/resources/comicrack/ReadingList_SmartList.xml
Normal file
13
komga/src/test/resources/comicrack/ReadingList_SmartList.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<ReadingList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<Name>Golden Age</Name>
|
||||||
|
<Books />
|
||||||
|
<Matchers>
|
||||||
|
<ComicBookMatcher xsi:type="ComicBookSeriesMatcher" MatchOperator="4">
|
||||||
|
<MatchValue>Marvel Masterworks</MatchValue>
|
||||||
|
</ComicBookMatcher>
|
||||||
|
<ComicBookMatcher xsi:type="ComicBookSeriesMatcher" MatchOperator="1">
|
||||||
|
<MatchValue>Golden Age</MatchValue>
|
||||||
|
</ComicBookMatcher>
|
||||||
|
</Matchers>
|
||||||
|
</ReadingList>
|
||||||
Loading…
Reference in a new issue