mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 16:03:03 +01:00
feat(api): metadata import settings per library
ability to edit a library fix filepath returned by API for Windows paths series metadata import is now looking at all the files from all books, instead of being imported for each book separately related to #199
This commit is contained in:
parent
5760a06b7a
commit
6824212514
25 changed files with 537 additions and 148 deletions
|
|
@ -0,0 +1,10 @@
|
|||
alter table library
|
||||
add column import_comicinfo_book boolean default true;
|
||||
alter table library
|
||||
add column import_comicinfo_series boolean default true;
|
||||
alter table library
|
||||
add column import_comicinfo_collection boolean default true;
|
||||
alter table library
|
||||
add column import_epub_book boolean default true;
|
||||
alter table library
|
||||
add column import_epub_series boolean default true;
|
||||
|
|
@ -21,6 +21,10 @@ sealed class Task : Serializable {
|
|||
override fun uniqueId() = "REFRESH_BOOK_METADATA_$bookId"
|
||||
}
|
||||
|
||||
data class RefreshSeriesMetadata(val seriesId: Long) : Task() {
|
||||
override fun uniqueId() = "REFRESH_SERIES_METADATA_$seriesId"
|
||||
}
|
||||
|
||||
object BackupDatabase : Task() {
|
||||
override fun uniqueId(): String = "BACKUP_DATABASE"
|
||||
override fun toString(): String = "BackupDatabase"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package org.gotson.komga.application.tasks
|
|||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.persistence.BookRepository
|
||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||
import org.gotson.komga.domain.service.BookLifecycle
|
||||
import org.gotson.komga.domain.service.LibraryScanner
|
||||
import org.gotson.komga.domain.service.MetadataLifecycle
|
||||
|
|
@ -20,6 +21,7 @@ class TaskHandler(
|
|||
private val taskReceiver: TaskReceiver,
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val bookRepository: BookRepository,
|
||||
private val seriesRepository: SeriesRepository,
|
||||
private val libraryScanner: LibraryScanner,
|
||||
private val bookLifecycle: BookLifecycle,
|
||||
private val metadataLifecycle: MetadataLifecycle,
|
||||
|
|
@ -53,8 +55,14 @@ class TaskHandler(
|
|||
is Task.RefreshBookMetadata ->
|
||||
bookRepository.findByIdOrNull(task.bookId)?.let {
|
||||
metadataLifecycle.refreshMetadata(it)
|
||||
taskReceiver.refreshSeriesMetadata(it.seriesId)
|
||||
} ?: logger.warn { "Cannot execute task $task: Book does not exist" }
|
||||
|
||||
is Task.RefreshSeriesMetadata ->
|
||||
seriesRepository.findByIdOrNull(task.seriesId)?.let {
|
||||
metadataLifecycle.refreshMetadata(it)
|
||||
} ?: logger.warn { "Cannot execute task $task: Series does not exist" }
|
||||
|
||||
is Task.BackupDatabase -> {
|
||||
databaseBackuper.backupDatabase()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ class TaskReceiver(
|
|||
submitTask(Task.RefreshBookMetadata(book.id))
|
||||
}
|
||||
|
||||
fun refreshSeriesMetadata(seriesId: Long) {
|
||||
submitTask(Task.RefreshSeriesMetadata(seriesId))
|
||||
}
|
||||
|
||||
fun databaseBackup() {
|
||||
submitTask(Task.BackupDatabase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,5 @@ data class BookMetadataPatch(
|
|||
val publisher: String?,
|
||||
val ageRating: Int?,
|
||||
val releaseDate: LocalDate?,
|
||||
val authors: List<Author>?,
|
||||
val series: SeriesMetadataPatch?
|
||||
val authors: List<Author>?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,17 @@ import java.time.LocalDateTime
|
|||
data class Library(
|
||||
val name: String,
|
||||
val root: URL,
|
||||
val importComicInfoBook: Boolean = true,
|
||||
val importComicInfoSeries: Boolean = true,
|
||||
val importComicInfoCollection: Boolean = true,
|
||||
val importEpubBook: Boolean = true,
|
||||
val importEpubSeries: Boolean = true,
|
||||
|
||||
val id: Long = 0,
|
||||
|
||||
override val createdDate: LocalDateTime = LocalDateTime.now(),
|
||||
override val lastModifiedDate: LocalDateTime = LocalDateTime.now()
|
||||
) : Auditable() {
|
||||
|
||||
constructor(name: String, root: String) : this(name, Paths.get(root).toUri().toURL())
|
||||
|
||||
fun path(): Path = Paths.get(this.root.toURI())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import org.gotson.komga.domain.model.Library
|
|||
|
||||
interface LibraryRepository {
|
||||
fun findByIdOrNull(libraryId: Long): Library?
|
||||
fun findById(libraryId: Long): Library
|
||||
fun findAll(): Collection<Library>
|
||||
fun findAllById(libraryIds: Collection<Long>): Collection<Library>
|
||||
|
||||
fun existsByName(name: String): Boolean
|
||||
|
||||
fun delete(libraryId: Long)
|
||||
fun deleteAll()
|
||||
|
||||
fun insert(library: Library): Library
|
||||
fun update(library: Library)
|
||||
|
||||
fun count(): Long
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,26 +31,40 @@ class LibraryLifecycle(
|
|||
fun addLibrary(library: Library): Library {
|
||||
logger.info { "Adding new library: ${library.name} with root folder: ${library.root}" }
|
||||
|
||||
val existing = libraryRepository.findAll()
|
||||
checkLibraryValidity(library, existing)
|
||||
|
||||
return libraryRepository.insert(library).also {
|
||||
taskReceiver.scanLibrary(it.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLibrary(toUpdate: Library) {
|
||||
logger.info { "Updating library: ${toUpdate.id}" }
|
||||
|
||||
val existing = libraryRepository.findAll().filter { it.id != toUpdate.id }
|
||||
checkLibraryValidity(toUpdate, existing)
|
||||
|
||||
libraryRepository.update(toUpdate)
|
||||
taskReceiver.scanLibrary(toUpdate.id)
|
||||
}
|
||||
|
||||
private fun checkLibraryValidity(library: Library, existing: Collection<Library>) {
|
||||
if (!Files.exists(library.path()))
|
||||
throw FileNotFoundException("Library root folder does not exist: ${library.root}")
|
||||
|
||||
if (!Files.isDirectory(library.path()))
|
||||
throw DirectoryNotFoundException("Library root folder is not a folder: ${library.root}")
|
||||
|
||||
if (libraryRepository.existsByName(library.name))
|
||||
if (existing.map { it.name }.contains(library.name))
|
||||
throw DuplicateNameException("Library name already exists")
|
||||
|
||||
libraryRepository.findAll().forEach {
|
||||
existing.forEach {
|
||||
if (library.path().startsWith(it.path()))
|
||||
throw PathContainedInPath("Library path ${library.path()} is a child of existing library ${it.name}: ${it.path()}")
|
||||
if (it.path().startsWith(library.path()))
|
||||
throw PathContainedInPath("Library path ${library.path()} is a parent of existing library ${it.name}: ${it.path()}")
|
||||
}
|
||||
|
||||
return libraryRepository.insert(library).let {
|
||||
taskReceiver.scanLibrary(it.id)
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLibrary(library: Library) {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,17 @@ package org.gotson.komga.domain.service
|
|||
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.Book
|
||||
import org.gotson.komga.domain.model.Series
|
||||
import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||
import org.gotson.komga.domain.persistence.BookRepository
|
||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||
import org.gotson.komga.domain.persistence.MediaRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.ComicInfoProvider
|
||||
import org.gotson.komga.infrastructure.metadata.epub.EpubMetadataProvider
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
|
@ -13,33 +20,71 @@ private val logger = KotlinLogging.logger {}
|
|||
@Service
|
||||
class MetadataLifecycle(
|
||||
private val bookMetadataProviders: List<BookMetadataProvider>,
|
||||
private val seriesMetadataProviders: List<SeriesMetadataProvider>,
|
||||
private val metadataApplier: MetadataApplier,
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val bookMetadataRepository: BookMetadataRepository,
|
||||
private val seriesMetadataRepository: SeriesMetadataRepository
|
||||
private val seriesMetadataRepository: SeriesMetadataRepository,
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val bookRepository: BookRepository
|
||||
) {
|
||||
|
||||
fun refreshMetadata(book: Book) {
|
||||
logger.info { "Refresh metadata for book: $book" }
|
||||
val media = mediaRepository.findById(book.id)
|
||||
|
||||
val library = libraryRepository.findById(book.libraryId)
|
||||
|
||||
bookMetadataProviders.forEach { provider ->
|
||||
provider.getBookMetadataFromBook(book, media)?.let { bPatch ->
|
||||
when {
|
||||
provider is ComicInfoProvider && !library.importComicInfoBook -> logger.info { "Library is not set to import book metadata from ComicInfo, skipping" }
|
||||
provider is EpubMetadataProvider && !library.importEpubBook -> logger.info { "Library is not set to import book metadata from Epub, skipping" }
|
||||
else -> {
|
||||
logger.debug { "Provider: $provider" }
|
||||
provider.getBookMetadataFromBook(book, media)?.let { bPatch ->
|
||||
|
||||
bookMetadataRepository.findById(book.id).let {
|
||||
logger.debug { "Original metadata: $it" }
|
||||
val patched = metadataApplier.apply(bPatch, it)
|
||||
logger.debug { "Patched metadata: $patched" }
|
||||
bookMetadataRepository.findById(book.id).let {
|
||||
logger.debug { "Original metadata: $it" }
|
||||
val patched = metadataApplier.apply(bPatch, it)
|
||||
logger.debug { "Patched metadata: $patched" }
|
||||
|
||||
bookMetadataRepository.update(patched)
|
||||
bookMetadataRepository.update(patched)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bPatch.series?.let { sPatch ->
|
||||
seriesMetadataRepository.findById(book.seriesId).let {
|
||||
logger.debug { "Apply metadata for series: ${book.seriesId}" }
|
||||
fun refreshMetadata(series: Series) {
|
||||
logger.info { "Refresh metadata for series: $series" }
|
||||
|
||||
val library = libraryRepository.findById(series.libraryId)
|
||||
|
||||
seriesMetadataProviders.forEach { provider ->
|
||||
when {
|
||||
provider is ComicInfoProvider && !library.importComicInfoSeries -> logger.info { "Library is not set to import series metadata from ComicInfo, skipping" }
|
||||
provider is EpubMetadataProvider && !library.importEpubSeries -> logger.info { "Library is not set to import series metadata from Epub, skipping" }
|
||||
else -> {
|
||||
logger.debug { "Provider: $provider" }
|
||||
val patches = bookRepository.findBySeriesId(series.id)
|
||||
.mapNotNull { provider.getSeriesMetadataFromBook(it, mediaRepository.findById(it.id)) }
|
||||
|
||||
val title = patches.uniqueOrNull { it.title }
|
||||
val titleSort = patches.uniqueOrNull { it.titleSort }
|
||||
val status = patches.uniqueOrNull { it.status }
|
||||
|
||||
if (title == null) logger.debug { "Ignoring title, values are not unique within series books" }
|
||||
if (titleSort == null) logger.debug { "Ignoring sort title, values are not unique within series books" }
|
||||
if (status == null) logger.debug { "Ignoring status, values are not unique within series books" }
|
||||
|
||||
val aggregatedPatch = SeriesMetadataPatch(title, titleSort, status)
|
||||
|
||||
seriesMetadataRepository.findById(series.id).let {
|
||||
logger.debug { "Apply metadata for series: $series" }
|
||||
|
||||
logger.debug { "Original metadata: $it" }
|
||||
val patched = metadataApplier.apply(sPatch, it)
|
||||
val patched = metadataApplier.apply(aggregatedPatch, it)
|
||||
logger.debug { "Patched metadata: $patched" }
|
||||
|
||||
seriesMetadataRepository.update(patched)
|
||||
|
|
@ -49,4 +94,13 @@ class MetadataLifecycle(
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T, R : Any> Iterable<T>.uniqueOrNull(transform: (T) -> R?): R? {
|
||||
return this
|
||||
.mapNotNull(transform)
|
||||
.distinct()
|
||||
.let {
|
||||
if (it.size == 1) it.first() else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.springframework.data.domain.PageImpl
|
|||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Component
|
||||
import toFilePath
|
||||
import java.net.URL
|
||||
|
||||
@Component
|
||||
|
|
@ -207,7 +208,7 @@ class BookDtoDao(
|
|||
seriesId = seriesId,
|
||||
libraryId = libraryId,
|
||||
name = name,
|
||||
url = URL(url).toURI().path,
|
||||
url = URL(url).toFilePath(),
|
||||
number = number,
|
||||
created = createdDate.toUTC(),
|
||||
lastModified = lastModifiedDate.toUTC(),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import org.gotson.komga.jooq.tables.records.LibraryRecord
|
|||
import org.jooq.DSLContext
|
||||
import org.springframework.stereotype.Component
|
||||
import java.net.URL
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class LibraryDao(
|
||||
|
|
@ -18,10 +19,17 @@ class LibraryDao(
|
|||
private val ul = Tables.USER_LIBRARY_SHARING
|
||||
|
||||
override fun findByIdOrNull(libraryId: Long): Library? =
|
||||
findOne(libraryId)
|
||||
?.toDomain()
|
||||
|
||||
override fun findById(libraryId: Long): Library =
|
||||
findOne(libraryId)
|
||||
.toDomain()
|
||||
|
||||
private fun findOne(libraryId: Long) =
|
||||
dsl.selectFrom(l)
|
||||
.where(l.ID.eq(libraryId))
|
||||
.fetchOneInto(l)
|
||||
?.toDomain()
|
||||
|
||||
override fun findAll(): Collection<Library> =
|
||||
dsl.selectFrom(l)
|
||||
|
|
@ -34,12 +42,6 @@ class LibraryDao(
|
|||
.fetchInto(l)
|
||||
.map { it.toDomain() }
|
||||
|
||||
override fun existsByName(name: String): Boolean =
|
||||
dsl.fetchExists(
|
||||
dsl.selectFrom(l)
|
||||
.where(l.NAME.equalIgnoreCase(name))
|
||||
)
|
||||
|
||||
override fun delete(libraryId: Long) {
|
||||
dsl.transaction { config ->
|
||||
with(config.dsl())
|
||||
|
|
@ -67,9 +69,28 @@ class LibraryDao(
|
|||
.set(l.ID, id)
|
||||
.set(l.NAME, library.name)
|
||||
.set(l.ROOT, library.root.toString())
|
||||
.set(l.IMPORT_COMICINFO_BOOK, library.importComicInfoBook)
|
||||
.set(l.IMPORT_COMICINFO_SERIES, library.importComicInfoSeries)
|
||||
.set(l.IMPORT_COMICINFO_COLLECTION, library.importComicInfoCollection)
|
||||
.set(l.IMPORT_EPUB_BOOK, library.importEpubBook)
|
||||
.set(l.IMPORT_EPUB_SERIES, library.importEpubSeries)
|
||||
.execute()
|
||||
|
||||
return findByIdOrNull(id)!!
|
||||
return findById(id)
|
||||
}
|
||||
|
||||
override fun update(library: Library) {
|
||||
dsl.update(l)
|
||||
.set(l.NAME, library.name)
|
||||
.set(l.ROOT, library.root.toString())
|
||||
.set(l.IMPORT_COMICINFO_BOOK, library.importComicInfoBook)
|
||||
.set(l.IMPORT_COMICINFO_SERIES, library.importComicInfoSeries)
|
||||
.set(l.IMPORT_COMICINFO_COLLECTION, library.importComicInfoCollection)
|
||||
.set(l.IMPORT_EPUB_BOOK, library.importEpubBook)
|
||||
.set(l.IMPORT_EPUB_SERIES, library.importEpubSeries)
|
||||
.set(l.LAST_MODIFIED_DATE, LocalDateTime.now())
|
||||
.where(l.ID.eq(library.id))
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun count(): Long = dsl.fetchCount(l).toLong()
|
||||
|
|
@ -79,6 +100,11 @@ class LibraryDao(
|
|||
Library(
|
||||
name = name,
|
||||
root = URL(root),
|
||||
importComicInfoBook = importComicinfoBook,
|
||||
importComicInfoSeries = importComicinfoSeries,
|
||||
importComicInfoCollection = importComicinfoCollection,
|
||||
importEpubBook = importEpubBook,
|
||||
importEpubSeries = importEpubSeries,
|
||||
id = id,
|
||||
createdDate = createdDate,
|
||||
lastModifiedDate = lastModifiedDate
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import org.springframework.data.domain.PageRequest
|
|||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.stereotype.Component
|
||||
import toFilePath
|
||||
import java.math.BigDecimal
|
||||
import java.net.URL
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ class SeriesDtoDao(
|
|||
id = id,
|
||||
libraryId = libraryId,
|
||||
name = name,
|
||||
url = URL(url).toURI().path,
|
||||
url = URL(url).toFilePath(),
|
||||
created = createdDate.toUTC(),
|
||||
lastModified = lastModifiedDate.toUTC(),
|
||||
fileLastModified = fileLastModified.toUTC(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
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.SeriesMetadataPatch
|
||||
|
||||
interface SeriesMetadataProvider {
|
||||
fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch?
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import org.gotson.komga.domain.model.Media
|
|||
import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||
import org.gotson.komga.domain.service.BookAnalyzer
|
||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
|
|
@ -24,7 +25,7 @@ private const val COMIC_INFO = "ComicInfo.xml"
|
|||
class ComicInfoProvider(
|
||||
@Autowired(required = false) private val mapper: XmlMapper = XmlMapper(),
|
||||
private val bookAnalyzer: BookAnalyzer
|
||||
) : BookMetadataProvider {
|
||||
) : BookMetadataProvider, SeriesMetadataProvider {
|
||||
|
||||
override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? {
|
||||
getComicInfo(book, media)?.let { comicInfo ->
|
||||
|
|
@ -56,12 +57,18 @@ class ComicInfoProvider(
|
|||
comicInfo.publisher,
|
||||
comicInfo.ageRating?.ageRating,
|
||||
releaseDate,
|
||||
authors.ifEmpty { null },
|
||||
SeriesMetadataPatch(
|
||||
comicInfo.series,
|
||||
comicInfo.series,
|
||||
null
|
||||
)
|
||||
authors.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? {
|
||||
getComicInfo(book, media)?.let { comicInfo ->
|
||||
return SeriesMetadataPatch(
|
||||
comicInfo.series,
|
||||
comicInfo.series,
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.Media
|
|||
import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||
import org.gotson.komga.infrastructure.mediacontainer.EpubExtractor
|
||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||
import org.jsoup.Jsoup
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDate
|
||||
|
|
@ -16,7 +17,7 @@ import java.time.format.DateTimeFormatter
|
|||
@Service
|
||||
class EpubMetadataProvider(
|
||||
private val epubExtractor: EpubExtractor
|
||||
) : BookMetadataProvider {
|
||||
) : BookMetadataProvider, SeriesMetadataProvider {
|
||||
|
||||
private val relators = mapOf(
|
||||
"aut" to "writer",
|
||||
|
|
@ -30,7 +31,7 @@ class EpubMetadataProvider(
|
|||
override fun getBookMetadataFromBook(book: Book, media: Media): BookMetadataPatch? {
|
||||
if (media.mediaType != "application/epub+zip") return null
|
||||
epubExtractor.getPackageFile(book.path())?.let { packageFile ->
|
||||
val opf = Jsoup.parse(packageFile.toString())
|
||||
val opf = Jsoup.parse(packageFile)
|
||||
|
||||
val title = opf.selectFirst("metadata > dc|title")?.text()
|
||||
val publisher = opf.selectFirst("metadata > dc|publisher")?.text()
|
||||
|
|
@ -57,8 +58,6 @@ class EpubMetadataProvider(
|
|||
Author(name, relators[role] ?: "writer")
|
||||
}
|
||||
|
||||
val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text()
|
||||
|
||||
return BookMetadataPatch(
|
||||
title = title,
|
||||
summary = description,
|
||||
|
|
@ -68,13 +67,24 @@ class EpubMetadataProvider(
|
|||
publisher = publisher,
|
||||
ageRating = null,
|
||||
releaseDate = date,
|
||||
authors = authors,
|
||||
series = SeriesMetadataPatch(series, series, null)
|
||||
authors = authors
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? {
|
||||
if (media.mediaType != "application/epub+zip") return null
|
||||
epubExtractor.getPackageFile(book.path())?.let { packageFile ->
|
||||
val opf = Jsoup.parse(packageFile)
|
||||
|
||||
val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text()
|
||||
|
||||
return SeriesMetadataPatch(series, series, null)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseDate(date: String): LocalDate? =
|
||||
try {
|
||||
LocalDate.parse(date, DateTimeFormatter.ISO_DATE)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import java.net.URL
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun URL.toFilePath(): String =
|
||||
Paths.get(this.toURI()).toString()
|
||||
|
||||
fun filePathToUrl(filePath: String): URL =
|
||||
Paths.get(filePath).toUri().toURL()
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package org.gotson.komga.interfaces.rest
|
||||
|
||||
import filePathToUrl
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.application.tasks.TaskReceiver
|
||||
import org.gotson.komga.domain.model.DirectoryNotFoundException
|
||||
|
|
@ -19,11 +20,13 @@ import org.springframework.web.bind.annotation.DeleteMapping
|
|||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import toFilePath
|
||||
import java.io.FileNotFoundException
|
||||
import javax.validation.Valid
|
||||
import javax.validation.constraints.NotBlank
|
||||
|
|
@ -66,7 +69,17 @@ class LibraryController(
|
|||
@Valid @RequestBody library: LibraryCreationDto
|
||||
): LibraryDto =
|
||||
try {
|
||||
libraryLifecycle.addLibrary(Library(library.name, library.root)).toDto(includeRoot = principal.user.roleAdmin)
|
||||
libraryLifecycle.addLibrary(
|
||||
Library(
|
||||
name = library.name,
|
||||
root = filePathToUrl(library.root),
|
||||
importComicInfoBook = library.importComicInfoBook,
|
||||
importComicInfoSeries = library.importComicInfoSeries,
|
||||
importComicInfoCollection = library.importComicInfoCollection,
|
||||
importEpubBook = library.importEpubBook,
|
||||
importEpubSeries = library.importEpubSeries
|
||||
)
|
||||
).toDto(includeRoot = principal.user.roleAdmin)
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is FileNotFoundException,
|
||||
|
|
@ -78,6 +91,28 @@ class LibraryController(
|
|||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun updateOne(
|
||||
@PathVariable id: Long,
|
||||
@Valid @RequestBody library: LibraryUpdateDto
|
||||
) {
|
||||
libraryRepository.findByIdOrNull(id)?.let {
|
||||
val toUpdate = Library(
|
||||
id = id,
|
||||
name = library.name,
|
||||
root = filePathToUrl(library.root),
|
||||
importComicInfoBook = library.importComicInfoBook,
|
||||
importComicInfoSeries = library.importComicInfoSeries,
|
||||
importComicInfoCollection = library.importComicInfoCollection,
|
||||
importEpubBook = library.importEpubBook,
|
||||
importEpubSeries = library.importEpubSeries
|
||||
)
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -117,17 +152,42 @@ class LibraryController(
|
|||
|
||||
data class LibraryCreationDto(
|
||||
@get:NotBlank val name: String,
|
||||
@get:NotBlank val root: String
|
||||
@get:NotBlank val root: String,
|
||||
val importComicInfoBook: Boolean = true,
|
||||
val importComicInfoSeries: Boolean = true,
|
||||
val importComicInfoCollection: Boolean = true,
|
||||
val importEpubBook: Boolean = true,
|
||||
val importEpubSeries: Boolean = true
|
||||
)
|
||||
|
||||
data class LibraryDto(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val root: String
|
||||
val root: String,
|
||||
val importComicInfoBook: Boolean,
|
||||
val importComicInfoSeries: Boolean,
|
||||
val importComicInfoCollection: Boolean,
|
||||
val importEpubBook: Boolean,
|
||||
val importEpubSeries: Boolean
|
||||
)
|
||||
|
||||
data class LibraryUpdateDto(
|
||||
@get:NotBlank val name: String,
|
||||
@get:NotBlank val root: String,
|
||||
val importComicInfoBook: Boolean,
|
||||
val importComicInfoSeries: Boolean,
|
||||
val importComicInfoCollection: Boolean,
|
||||
val importEpubBook: Boolean,
|
||||
val importEpubSeries: Boolean
|
||||
)
|
||||
|
||||
fun Library.toDto(includeRoot: Boolean) = LibraryDto(
|
||||
id = id,
|
||||
name = name,
|
||||
root = if (includeRoot) root.toURI().path else ""
|
||||
root = if (includeRoot) this.root.toFilePath() else "",
|
||||
importComicInfoBook = importComicInfoBook,
|
||||
importComicInfoSeries = importComicInfoSeries,
|
||||
importComicInfoCollection = importComicInfoCollection,
|
||||
importEpubBook = importEpubBook,
|
||||
importEpubSeries = importEpubSeries
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ package org.gotson.komga.application.tasks
|
|||
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.Book
|
||||
import org.gotson.komga.domain.model.Series
|
||||
import org.gotson.komga.domain.model.makeBook
|
||||
import org.gotson.komga.domain.model.makeLibrary
|
||||
import org.gotson.komga.domain.model.makeSeries
|
||||
|
|
@ -78,7 +82,8 @@ class TaskHandlerTest(
|
|||
seriesLifecycle.addBooks(it, listOf(book))
|
||||
}
|
||||
|
||||
every { mockMetadataLifecycle.refreshMetadata(any()) } answers { Thread.sleep(1_000) }
|
||||
every { mockMetadataLifecycle.refreshMetadata(any<Book>()) } answers { Thread.sleep(1_000) }
|
||||
every { mockMetadataLifecycle.refreshMetadata(any<Series>()) } just runs
|
||||
|
||||
val createdBook = bookRepository.findAll().first()
|
||||
|
||||
|
|
@ -88,6 +93,6 @@ class TaskHandlerTest(
|
|||
|
||||
Thread.sleep(5_000)
|
||||
|
||||
verify(atLeast = 1, atMost = 3) { mockMetadataLifecycle.refreshMetadata(any()) }
|
||||
verify(atLeast = 1, atMost = 3) { mockMetadataLifecycle.refreshMetadata(any<Book>()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.Library
|
|||
import org.gotson.komga.domain.model.PathContainedInPath
|
||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
|
|
@ -15,6 +16,7 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabas
|
|||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
|
|
@ -30,74 +32,213 @@ class LibraryLifecycleTest(
|
|||
libraryRepository.deleteAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when adding library with non-existent root folder then exception is thrown`() {
|
||||
// when
|
||||
val thrown = catchThrowable { libraryLifecycle.addLibrary(Library("test", "/non-existent")) }
|
||||
@Nested
|
||||
inner class Add {
|
||||
@Test
|
||||
fun `when adding library with non-existent root folder then exception is thrown`() {
|
||||
// when
|
||||
val thrown = catchThrowable { libraryLifecycle.addLibrary(Library("test", URL("file:/non-existent"))) }
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(FileNotFoundException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when adding library with non-directory root folder then exception is thrown`() {
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempFile(null, null).toUri().toURL()))
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(FileNotFoundException::class.java)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java)
|
||||
}
|
||||
@Test
|
||||
fun `when adding library with non-directory root folder then exception is thrown`() {
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempFile(null, null).toUri().toURL()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when adding library with same name then exception is thrown`() {
|
||||
// given
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL()))
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java)
|
||||
}
|
||||
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
@Test
|
||||
fun `given existing library when adding library with same name then exception is thrown`() {
|
||||
// given
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL()))
|
||||
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL()))
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DuplicateNameException::class.java)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DuplicateNameException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when adding library with root folder as child of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL()))
|
||||
|
||||
// when
|
||||
val child = Files.createTempDirectory(parent, "")
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("child")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
val child = Files.createTempDirectory(parent, null)
|
||||
libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
@Test
|
||||
fun `given existing library when adding library with root folder as child of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL()))
|
||||
|
||||
// when
|
||||
val child = Files.createTempDirectory(parent, "")
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("child")
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("parent")
|
||||
@Test
|
||||
fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
val child = Files.createTempDirectory(parent, null)
|
||||
libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL()))
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("parent")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Update {
|
||||
private val rootFolder = Files.createTempDirectory(null)
|
||||
private val library = Library("Existing", rootFolder.toUri().toURL())
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating with non-existent root folder then exception is thrown`() {
|
||||
// given
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val toUpdate = existing.copy(name = "test", root = URL("file:/non-existent"))
|
||||
val thrown = catchThrowable { libraryLifecycle.updateLibrary(toUpdate) }
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(FileNotFoundException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating with non-directory root folder then exception is thrown`() {
|
||||
// given
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val toUpdate = existing.copy(name = "test", root = Files.createTempFile(null, null).toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given single existing library when updating library with same name then it is updated`() {
|
||||
// given
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(existing)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).doesNotThrowAnyException()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating library with same name then exception is thrown`() {
|
||||
// given
|
||||
libraryLifecycle.addLibrary(Library("test", Files.createTempDirectory(null).toUri().toURL()))
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val toUpdate = existing.copy(name = "test", root = Files.createTempDirectory(null).toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).isInstanceOf(DuplicateNameException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given single existing library when updating library with root folder as child of existing library then no exception is thrown`() {
|
||||
// given
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val child = Files.createTempDirectory(rootFolder, "")
|
||||
val toUpdate = existing.copy(root = child.toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).doesNotThrowAnyException()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating library with root folder as child of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL()))
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val child = Files.createTempDirectory(parent, "")
|
||||
val toUpdate = existing.copy(root = child.toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("child")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given single existing library when updating library with root folder as parent of existing library then no exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
val child = Files.createTempDirectory(parent, null)
|
||||
val existing = libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
|
||||
// when
|
||||
val toUpdate = existing.copy(root = parent.toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown).doesNotThrowAnyException()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating library with root folder as parent of existing library then exception is thrown`() {
|
||||
// given
|
||||
val parent = Files.createTempDirectory(null)
|
||||
val child = Files.createTempDirectory(parent, null)
|
||||
libraryLifecycle.addLibrary(Library("child", child.toUri().toURL()))
|
||||
val existing = libraryLifecycle.addLibrary(library)
|
||||
|
||||
// when
|
||||
val toUpdate = existing.copy(root = parent.toUri().toURL())
|
||||
val thrown = catchThrowable {
|
||||
libraryLifecycle.updateLibrary(toUpdate)
|
||||
}
|
||||
|
||||
// then
|
||||
assertThat(thrown)
|
||||
.isInstanceOf(PathContainedInPath::class.java)
|
||||
.hasMessageContaining("parent")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,46 @@ class LibraryDaoTest(
|
|||
assertThat(created.root).isEqualTo(library.root)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing library when updating then it is persisted`() {
|
||||
val library = Library(
|
||||
name = "Library",
|
||||
root = URL("file://library")
|
||||
)
|
||||
val created = libraryDao.insert(library)
|
||||
|
||||
Thread.sleep(5)
|
||||
|
||||
val modificationDate = LocalDateTime.now()
|
||||
|
||||
val updated = created.copy(
|
||||
name = "LibraryUpdated",
|
||||
root = URL("file://library2"),
|
||||
importEpubSeries = false,
|
||||
importEpubBook = false,
|
||||
importComicInfoCollection = false,
|
||||
importComicInfoSeries = false,
|
||||
importComicInfoBook = false
|
||||
)
|
||||
|
||||
libraryDao.update(updated)
|
||||
val modified = libraryDao.findById(updated.id)
|
||||
|
||||
assertThat(modified.id).isEqualTo(updated.id)
|
||||
assertThat(modified.createdDate).isEqualTo(updated.createdDate)
|
||||
assertThat(modified.lastModifiedDate)
|
||||
.isAfterOrEqualTo(modificationDate)
|
||||
.isNotEqualTo(updated.lastModifiedDate)
|
||||
|
||||
assertThat(modified.name).isEqualTo(updated.name)
|
||||
assertThat(modified.root).isEqualTo(updated.root)
|
||||
assertThat(modified.importEpubSeries).isEqualTo(updated.importEpubSeries)
|
||||
assertThat(modified.importEpubBook).isEqualTo(updated.importEpubBook)
|
||||
assertThat(modified.importComicInfoCollection).isEqualTo(updated.importComicInfoCollection)
|
||||
assertThat(modified.importComicInfoSeries).isEqualTo(updated.importComicInfoSeries)
|
||||
assertThat(modified.importComicInfoBook).isEqualTo(updated.importComicInfoBook)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a library when deleting then it is deleted`() {
|
||||
val library = Library(
|
||||
|
|
@ -141,19 +181,4 @@ class LibraryDaoTest(
|
|||
|
||||
assertThat(found).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given libraries when checking if exists by name then returns true or false`() {
|
||||
val library = Library(
|
||||
name = "Library",
|
||||
root = URL("file://library")
|
||||
)
|
||||
libraryDao.insert(library)
|
||||
|
||||
val exists = libraryDao.existsByName("LIBRARY")
|
||||
val notExists = libraryDao.existsByName("LIBRARY2")
|
||||
|
||||
assertThat(exists).isTrue()
|
||||
assertThat(notExists).isFalse()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,19 +129,17 @@ class MediaDaoTest(
|
|||
|
||||
val modificationDate = LocalDateTime.now()
|
||||
|
||||
val updated = with(created) {
|
||||
copy(
|
||||
status = Media.Status.ERROR,
|
||||
mediaType = "application/rar",
|
||||
thumbnail = Random.nextBytes(1),
|
||||
pages = listOf(BookPage(
|
||||
fileName = "2.png",
|
||||
mediaType = "image/png"
|
||||
)),
|
||||
files = listOf("id.txt"),
|
||||
comment = "comment2"
|
||||
)
|
||||
}
|
||||
val updated = created.copy(
|
||||
status = Media.Status.ERROR,
|
||||
mediaType = "application/rar",
|
||||
thumbnail = Random.nextBytes(1),
|
||||
pages = listOf(BookPage(
|
||||
fileName = "2.png",
|
||||
mediaType = "image/png"
|
||||
)),
|
||||
files = listOf("id.txt"),
|
||||
comment = "comment2"
|
||||
)
|
||||
|
||||
mediaDao.update(updated)
|
||||
val modified = mediaDao.findById(updated.bookId)
|
||||
|
|
|
|||
|
|
@ -157,9 +157,9 @@ class ComicInfoProviderTest {
|
|||
|
||||
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
|
||||
|
||||
val patch = comicInfoProvider.getBookMetadataFromBook(book, media)!!.series
|
||||
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
|
||||
|
||||
with(patch!!) {
|
||||
with(patch) {
|
||||
assertThat(title).isEqualTo("series")
|
||||
assertThat(titleSort).isEqualTo("series")
|
||||
assertThat(status).isNull()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import org.gotson.komga.domain.service.KomgaUserLifecycle
|
|||
import org.gotson.komga.domain.service.LibraryLifecycle
|
||||
import org.gotson.komga.domain.service.SeriesLifecycle
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.hamcrest.Matchers
|
||||
import org.hamcrest.core.IsNull
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
|
@ -451,10 +452,9 @@ class BookControllerTest(
|
|||
|
||||
val book = bookRepository.findAll().first()
|
||||
|
||||
val url = "/1.cbr"
|
||||
val validation: MockMvcResultMatchersDsl.() -> Unit = {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].url") { value(url) }
|
||||
jsonPath("$.content[0].url") { value(Matchers.containsString("1.cbr")) }
|
||||
}
|
||||
|
||||
mockMvc.get("/api/v1/books")
|
||||
|
|
@ -469,7 +469,7 @@ class BookControllerTest(
|
|||
mockMvc.get("/api/v1/books/${book.id}")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value(url) }
|
||||
jsonPath("$.url") { value(Matchers.containsString("1.cbr")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.ROLE_ADMIN
|
|||
import org.gotson.komga.domain.model.ROLE_USER
|
||||
import org.gotson.komga.domain.model.makeLibrary
|
||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Nested
|
||||
|
|
@ -136,13 +137,13 @@ class LibraryControllerTest(
|
|||
mockMvc.get(route)
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$[0].root") { value("/library1") }
|
||||
jsonPath("$[0].root") { value(Matchers.containsString("library1")) }
|
||||
}
|
||||
|
||||
mockMvc.get("${route}/${library.id}")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.root") { value("/library1") }
|
||||
jsonPath("$.root") { value(Matchers.containsString("library1")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,10 +326,9 @@ class SeriesControllerTest(
|
|||
}
|
||||
}
|
||||
|
||||
val url = "/series"
|
||||
val validation: MockMvcResultMatchersDsl.() -> Unit = {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].url") { value(url) }
|
||||
jsonPath("$.content[0].url") { value(Matchers.containsString("series")) }
|
||||
}
|
||||
|
||||
mockMvc.get("/api/v1/series")
|
||||
|
|
@ -344,7 +343,7 @@ class SeriesControllerTest(
|
|||
mockMvc.get("/api/v1/series/${createdSeries.id}")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value(url) }
|
||||
jsonPath("$.url") { value(Matchers.containsString("series")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue