diff --git a/build.gradle.kts b/build.gradle.kts index 2993ac844..bad6cf5af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,10 +77,10 @@ jreleaser { links = true content = (if (Path("./release_notes/release_notes.md").exists()) "{{#f_file_read}}{{basedir}}/release_notes/release_notes.md{{/f_file_read}}" else "") + """ - ## Changelog + ## Changelog - {{changelogChanges}} - {{changelogContributors}} + {{changelogChanges}} + {{changelogContributors}} """.trimIndent() format = "- {{#commitIsConventional}}{{#conventionalCommitIsBreakingChange}}🚨 {{/conventionalCommitIsBreakingChange}}{{#conventionalCommitScope}}**{{conventionalCommitScope}}**: {{/conventionalCommitScope}}{{conventionalCommitDescription}}{{#conventionalCommitBreakingChangeContent}}: *{{conventionalCommitBreakingChangeContent}}*{{/conventionalCommitBreakingChangeContent}} ({{commitShortHash}}){{/commitIsConventional}}{{^commitIsConventional}}{{commitTitle}} ({{commitShortHash}}){{/commitIsConventional}}{{#commitHasIssues}}, closes{{#commitIssues}} {{issue}}{{/commitIssues}}{{/commitHasIssues}}" hide { @@ -126,10 +126,11 @@ jreleaser { enabled = true title = "# [{{projectVersion}}]({{repoUrl}}/compare/{{previousTagName}}...{{tagName}}) ({{#f_now}}YYYY-MM-dd{{/f_now}})" target = rootDir.resolve("CHANGELOG.md") - content = """ + content = + """ {{changelogTitle}} {{changelogChanges}} - """.trimIndent() + """.trimIndent() } } @@ -173,11 +174,12 @@ jreleaser { templateDirectory = rootDir.resolve("komga/docker") repository.active = Active.NEVER buildArgs = listOf("--cache-from", "gotson/komga:latest") - imageNames = listOf( - "komga:latest", - "komga:{{projectVersion}}", - "komga:{{projectVersionMajor}}.x", - ) + imageNames = + listOf( + "komga:latest", + "komga:{{projectVersion}}", + "komga:{{projectVersionMajor}}.x", + ) registries { create("docker.io") { externalLogin = true } create("ghcr.io") { externalLogin = true } @@ -185,11 +187,12 @@ jreleaser { buildx { enabled = true createBuilder = false - platforms = listOf( - "linux/amd64", - "linux/arm/v7", - "linux/arm64/v8", - ) + platforms = + listOf( + "linux/amd64", + "linux/arm/v7", + "linux/arm64/v8", + ) } } } diff --git a/komga-tray/src/main/kotlin/org/gotson/komga/DesktopApplication.kt b/komga-tray/src/main/kotlin/org/gotson/komga/DesktopApplication.kt index 42fbc89fc..558d9d06e 100644 --- a/komga-tray/src/main/kotlin/org/gotson/komga/DesktopApplication.kt +++ b/komga-tray/src/main/kotlin/org/gotson/komga/DesktopApplication.kt @@ -22,10 +22,11 @@ fun main(args: Array) { run(*args) } } catch (e: Exception) { - val (message, stackTrace) = when (e.cause) { - is PortInUseException -> RB.getString("error_message.port_in_use", (e.cause as PortInUseException).port) to null - else -> RB.getString("error_message.unexpected") to e.stackTraceToString() - } + val (message, stackTrace) = + when (e.cause) { + is PortInUseException -> RB.getString("error_message.port_in_use", (e.cause as PortInUseException).port) to null + else -> RB.getString("error_message.unexpected") to e.stackTraceToString() + } showErrorDialog(message, stackTrace) } diff --git a/komga-tray/src/main/kotlin/org/gotson/komga/RB.kt b/komga-tray/src/main/kotlin/org/gotson/komga/RB.kt index ec4249476..ecc8eff32 100644 --- a/komga-tray/src/main/kotlin/org/gotson/komga/RB.kt +++ b/komga-tray/src/main/kotlin/org/gotson/komga/RB.kt @@ -7,8 +7,13 @@ class RB private constructor() { companion object { private val BUNDLE: ResourceBundle = ResourceBundle.getBundle("org.gotson.komga.messages") - fun getString(key: String, vararg args: Any?): String = - if (args.isEmpty()) BUNDLE.getString(key) - else MessageFormatter.arrayFormat(BUNDLE.getString(key), args).message + fun getString( + key: String, + vararg args: Any?, + ): String = + if (args.isEmpty()) + BUNDLE.getString(key) + else + MessageFormatter.arrayFormat(BUNDLE.getString(key), args).message } } diff --git a/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/ErrorDialog.kt b/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/ErrorDialog.kt index 8f5e99c44..15911fc91 100644 --- a/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/ErrorDialog.kt +++ b/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/ErrorDialog.kt @@ -30,21 +30,26 @@ import org.gotson.komga.RB import org.springframework.core.io.ClassPathResource @Preview -fun showErrorDialog(text: String, stackTrace: String? = null) { +fun showErrorDialog( + text: String, + stackTrace: String? = null, +) { application { Window( title = RB.getString("dialog_error.title"), onCloseRequest = ::exitApplication, visible = true, resizable = false, - state = WindowState( - placement = WindowPlacement.Floating, - position = WindowPosition(alignment = Alignment.Center), - size = DpSize( - if (stackTrace != null) 800.dp else Dp.Unspecified, - Dp.Unspecified, + state = + WindowState( + placement = WindowPlacement.Floating, + position = WindowPosition(alignment = Alignment.Center), + size = + DpSize( + if (stackTrace != null) 800.dp else Dp.Unspecified, + Dp.Unspecified, + ), ), - ), icon = loadSvgPainter(ClassPathResource("icons/komga-color.svg").inputStream, LocalDensity.current), ) { Column( @@ -57,9 +62,10 @@ fun showErrorDialog(text: String, stackTrace: String? = null) { Image( painter = loadSvgPainter(ClassPathResource("icons/komga-color.svg").inputStream, LocalDensity.current), contentDescription = "Komga logo", - modifier = Modifier - .size(96.dp) - .align(Alignment.Top), + modifier = + Modifier + .size(96.dp) + .align(Alignment.Top), ) Text( text, @@ -77,10 +83,11 @@ fun showErrorDialog(text: String, stackTrace: String? = null) { Row( horizontalArrangement = if (stackTrace != null) Arrangement.SpaceBetween else Arrangement.End, - modifier = if (stackTrace != null) - Modifier.align(Alignment.End).fillMaxWidth() - else - Modifier.align(Alignment.End), + modifier = + if (stackTrace != null) + Modifier.align(Alignment.End).fillMaxWidth() + else + Modifier.align(Alignment.End), ) { if (stackTrace != null) { val clipboardManager = LocalClipboardManager.current diff --git a/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/TrayIconRunner.kt b/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/TrayIconRunner.kt index 2a055d594..da76b7407 100644 --- a/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/TrayIconRunner.kt +++ b/komga-tray/src/main/kotlin/org/gotson/komga/application/gui/TrayIconRunner.kt @@ -26,7 +26,6 @@ class TrayIconRunner( @Value("\${server.port}") serverPort: Int, env: Environment, ) : ApplicationRunner { - val komgaUrl = "http://localhost:$serverPort$servletContextPath" val komgaConfigDir = File(komgaConfigDir) val logFile = File(logFileName) diff --git a/komga/build.gradle.kts b/komga/build.gradle.kts index 794487979..a79d80601 100644 --- a/komga/build.gradle.kts +++ b/komga/build.gradle.kts @@ -26,12 +26,13 @@ kotlin { jvmToolchain(17) } -val benchmarkSourceSet = sourceSets.create("benchmark") { - java { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().runtimeClasspath +val benchmarkSourceSet = + sourceSets.create("benchmark") { + java { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().runtimeClasspath + } } -} val benchmarkImplementation by configurations.getting { extendsFrom(configurations.testImplementation.get()) @@ -147,10 +148,11 @@ tasks { withType { kotlinOptions { jvmTarget = "17" - freeCompilerArgs = listOf( - "-Xjsr305=strict", - "-opt-in=kotlin.time.ExperimentalTime", - ) + freeCompilerArgs = + listOf( + "-Xjsr305=strict", + "-opt-in=kotlin.time.ExperimentalTime", + ) } } @@ -243,31 +245,36 @@ springBoot { } } -val sqliteUrls = mapOf( - "main" to "jdbc:sqlite:${project.layout.buildDirectory.get()}/generated/flyway/main/database.sqlite", - "tasks" to "jdbc:sqlite:${project.layout.buildDirectory.get()}/generated/flyway/tasks/tasks.sqlite", -) -val sqliteMigrationDirs = mapOf( - "main" to listOf( - "$projectDir/src/flyway/resources/db/migration/sqlite", - "$projectDir/src/flyway/kotlin/db/migration/sqlite", - ), - "tasks" to listOf( - "$projectDir/src/flyway/resources/tasks/migration/sqlite", +val sqliteUrls = + mapOf( + "main" to "jdbc:sqlite:${project.layout.buildDirectory.get()}/generated/flyway/main/database.sqlite", + "tasks" to "jdbc:sqlite:${project.layout.buildDirectory.get()}/generated/flyway/tasks/tasks.sqlite", + ) +val sqliteMigrationDirs = + mapOf( + "main" to + listOf( + "$projectDir/src/flyway/resources/db/migration/sqlite", + "$projectDir/src/flyway/kotlin/db/migration/sqlite", + ), + "tasks" to + listOf( + "$projectDir/src/flyway/resources/tasks/migration/sqlite", // "$projectDir/src/flyway/kotlin/tasks/migration/sqlite", - ), -) + ), + ) task("flywayMigrateMain", FlywayMigrateTask::class) { val id = "main" url = sqliteUrls[id] locations = arrayOf("classpath:db/migration/sqlite") - placeholders = mapOf( - "library-file-hashing" to "true", - "library-scan-startup" to "false", - "delete-empty-collections" to "true", - "delete-empty-read-lists" to "true", - ) + placeholders = + mapOf( + "library-file-hashing" to "true", + "library-scan-startup" to "false", + "delete-empty-collections" to "true", + "delete-empty-read-lists" to "true", + ) // in order to include the Java migrations, flywayClasses must be run before flywayMigrate dependsOn("flywayClasses") sqliteMigrationDirs[id]?.forEach { inputs.dir(it) } diff --git a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/AbstractBenchmark.kt b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/AbstractBenchmark.kt index 012375ebe..40e358fa7 100644 --- a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/AbstractBenchmark.kt +++ b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/AbstractBenchmark.kt @@ -25,28 +25,30 @@ abstract class AbstractBenchmark { fun executeJmhRunner() { if (this.javaClass.simpleName.contains("jmhType")) return Files.createDirectories(Paths.get(benchmarkProperties.resultFolder)) - val extension = when (benchmarkProperties.resultFormat) { - ResultFormatType.TEXT -> "txt" - ResultFormatType.CSV -> "csv" - ResultFormatType.SCSV -> "scsv" - ResultFormatType.JSON -> "json" - ResultFormatType.LATEX -> "tex" - } + val extension = + when (benchmarkProperties.resultFormat) { + ResultFormatType.TEXT -> "txt" + ResultFormatType.CSV -> "csv" + ResultFormatType.SCSV -> "scsv" + ResultFormatType.JSON -> "json" + ResultFormatType.LATEX -> "tex" + } val resultFile = Paths.get(benchmarkProperties.resultFolder, "${this.javaClass.name}.$extension").absolutePathString() - val opt: Options = OptionsBuilder() - .include("\\." + this.javaClass.simpleName + "\\.") // set the class name regex for benchmarks to search for to the current class - .apply { if (benchmarkProperties.warmupIterations > 0) warmupIterations(benchmarkProperties.warmupIterations) } - .measurementIterations(benchmarkProperties.measurementIterations) - .forks(0) // do not use forking or the benchmark methods will not see references stored within its class - .threads(1) // do not use multiple threads - .mode(benchmarkProperties.mode) - .shouldDoGC(true) - .shouldFailOnError(true) - .resultFormat(benchmarkProperties.resultFormat) - .result(resultFile) - .shouldFailOnError(true) - .jvmArgs("-server") - .build() + val opt: Options = + OptionsBuilder() + .include("\\." + this.javaClass.simpleName + "\\.") // set the class name regex for benchmarks to search for to the current class + .apply { if (benchmarkProperties.warmupIterations > 0) warmupIterations(benchmarkProperties.warmupIterations) } + .measurementIterations(benchmarkProperties.measurementIterations) + .forks(0) // do not use forking or the benchmark methods will not see references stored within its class + .threads(1) // do not use multiple threads + .mode(benchmarkProperties.mode) + .shouldDoGC(true) + .shouldFailOnError(true) + .resultFormat(benchmarkProperties.resultFormat) + .result(resultFile) + .shouldFailOnError(true) + .jvmArgs("-server") + .build() Runner(opt).run() } } diff --git a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/AbstractRestBenchmark.kt b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/AbstractRestBenchmark.kt index 2d6769027..b6fa4de08 100644 --- a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/AbstractRestBenchmark.kt +++ b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/AbstractRestBenchmark.kt @@ -12,7 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired internal const val DEFAULT_PAGE_SIZE = 20 abstract class AbstractRestBenchmark : AbstractBenchmark() { - companion object { lateinit var userRepository: KomgaUserRepository lateinit var seriesController: SeriesController @@ -38,9 +37,10 @@ abstract class AbstractRestBenchmark : AbstractBenchmark() { @Setup(Level.Trial) fun prepareData() { - principal = KomgaPrincipal( - userRepository.findByEmailIgnoreCaseOrNull("admin@example.org") - ?: throw IllegalStateException("no user found"), - ) + principal = + KomgaPrincipal( + userRepository.findByEmailIgnoreCaseOrNull("admin@example.org") + ?: throw IllegalStateException("no user found"), + ) } } diff --git a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/BrowseBenchmark.kt b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/BrowseBenchmark.kt index 61a458769..f54e3a517 100644 --- a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/BrowseBenchmark.kt +++ b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/BrowseBenchmark.kt @@ -11,7 +11,6 @@ import java.util.concurrent.TimeUnit @OutputTimeUnit(TimeUnit.MILLISECONDS) class BrowseBenchmark : AbstractRestBenchmark() { - companion object { private lateinit var biggestSeriesId: String } @@ -24,9 +23,10 @@ class BrowseBenchmark : AbstractRestBenchmark() { super.prepareData() // find series with most books - biggestSeriesId = seriesController.getAllSeries(principal, page = PageRequest.of(0, 1, Sort.by(Sort.Order.desc("booksCount")))) - .content.first() - .id + biggestSeriesId = + seriesController.getAllSeries(principal, page = PageRequest.of(0, 1, Sort.by(Sort.Order.desc("booksCount")))) + .content.first() + .id } @Benchmark diff --git a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/DashboardBenchmark.kt b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/DashboardBenchmark.kt index 4290d2483..f879ee9d1 100644 --- a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/DashboardBenchmark.kt +++ b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/DashboardBenchmark.kt @@ -19,7 +19,6 @@ import java.util.concurrent.TimeUnit @OutputTimeUnit(TimeUnit.MILLISECONDS) class DashboardBenchmark : AbstractRestBenchmark() { - companion object { lateinit var bookLatestReleaseDate: LocalDate } diff --git a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/UnsortedBenchmark.kt b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/UnsortedBenchmark.kt index c36dc8da1..a0558e433 100644 --- a/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/UnsortedBenchmark.kt +++ b/komga/src/benchmark/kotlin/org/gotson/komga/benchmark/rest/UnsortedBenchmark.kt @@ -11,7 +11,6 @@ import java.util.concurrent.TimeUnit @OutputTimeUnit(TimeUnit.MILLISECONDS) class UnsortedBenchmark : AbstractRestBenchmark() { - companion object { private lateinit var biggestSeriesId: String } @@ -23,9 +22,10 @@ class UnsortedBenchmark : AbstractRestBenchmark() { super.prepareData() // find series with most books - biggestSeriesId = seriesController.getAllSeries(principal, page = PageRequest.of(0, 1, Sort.by(Sort.Order.desc("booksCount")))) - .content.first() - .id + biggestSeriesId = + seriesController.getAllSeries(principal, page = PageRequest.of(0, 1, Sort.by(Sort.Order.desc("booksCount")))) + .content.first() + .id } @Benchmark diff --git a/komga/src/main/kotlin/org/gotson/komga/application/scheduler/LibraryScanScheduler.kt b/komga/src/main/kotlin/org/gotson/komga/application/scheduler/LibraryScanScheduler.kt index 53de99bfd..5d2512482 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/scheduler/LibraryScanScheduler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/scheduler/LibraryScanScheduler.kt @@ -28,9 +28,10 @@ class LibraryScanScheduler( // map the libraryId to the scan scheduled task private val registry = ConcurrentHashMap() - private val registrar = ScheduledTaskRegistrar().apply { - setTaskScheduler(taskScheduler) - } + private val registrar = + ScheduledTaskRegistrar().apply { + setTaskScheduler(taskScheduler) + } fun scheduleScan(library: Library) { registry.remove(library.id)?.cancel(false) diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt index c83866fd1..f3fedb3b8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt @@ -17,122 +17,146 @@ sealed class Task(val priority: Int = DEFAULT_PRIORITY, val groupId: String? = n class ScanLibrary(val libraryId: String, val scanDeep: Boolean, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "SCAN_LIBRARY_${libraryId}_DEEP_$scanDeep" + override fun toString(): String = "ScanLibrary(libraryId='$libraryId', scanDeep='$scanDeep', priority='$priority')" } class FindBooksToConvert(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "FIND_BOOKS_TO_CONVERT_$libraryId" + override fun toString(): String = "FindBooksToConvert(libraryId='$libraryId', priority='$priority')" } class FindBooksWithMissingPageHash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "FIND_BOOKS_WITH_MISSING_PAGE_HASH_$libraryId" + override fun toString(): String = "FindBooksWithMissingPageHash(libraryId='$libraryId', priority='$priority')" } class FindDuplicatePagesToDelete(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "FIND_DUPLICATE_PAGES_TO_DELETE_$libraryId" + override fun toString(): String = "FindDuplicatePagesToDelete(libraryId='$libraryId', priority='$priority')" } class EmptyTrash(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "EMPTY_TRASH_$libraryId" + override fun toString(): String = "EmptyTrash(libraryId='$libraryId', priority='$priority')" } class AnalyzeBook(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId) { override val uniqueId = "ANALYZE_BOOK_$bookId" + override fun toString(): String = "AnalyzeBook(bookId='$bookId', priority='$priority')" } class GenerateBookThumbnail(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "GENERATE_BOOK_THUMBNAIL_$bookId" + override fun toString(): String = "GenerateBookThumbnail(bookId='$bookId', priority='$priority')" } class RefreshBookMetadata(val bookId: String, val capabilities: Set, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId) { override val uniqueId = "REFRESH_BOOK_METADATA_$bookId" + override fun toString(): String = "RefreshBookMetadata(bookId='$bookId', capabilities=$capabilities, priority='$priority')" } class HashBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "HASH_BOOK_$bookId" + override fun toString(): String = "HashBook(bookId='$bookId', priority='$priority')" } class HashBookPages(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "HASH_BOOK_PAGES_$bookId" + override fun toString(): String = "HashBookPages(bookId='$bookId', priority='$priority')" } class RefreshSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId) { override val uniqueId = "REFRESH_SERIES_METADATA_$seriesId" + override fun toString(): String = "RefreshSeriesMetadata(seriesId='$seriesId', priority='$priority')" } class AggregateSeriesMetadata(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId) { override val uniqueId = "AGGREGATE_SERIES_METADATA_$seriesId" + override fun toString(): String = "AggregateSeriesMetadata(seriesId='$seriesId', priority='$priority')" } class RefreshBookLocalArtwork(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId: String = "REFRESH_BOOK_LOCAL_ARTWORK_$bookId" + override fun toString(): String = "RefreshBookLocalArtwork(bookId='$bookId', priority='$priority')" } class RefreshSeriesLocalArtwork(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId: String = "REFRESH_SERIES_LOCAL_ARTWORK_$seriesId" + override fun toString(): String = "RefreshSeriesLocalArtwork(seriesId=$seriesId, priority='$priority')" } class ImportBook(val sourceFile: String, val seriesId: String, val copyMode: CopyMode, val destinationName: String?, val upgradeBookId: String?, priority: Int = DEFAULT_PRIORITY) : Task(priority, seriesId) { override val uniqueId: String = "IMPORT_BOOK_${seriesId}_$sourceFile" + override fun toString(): String = "ImportBook(sourceFile='$sourceFile', seriesId='$seriesId', copyMode=$copyMode, destinationName=$destinationName, upgradeBookId=$upgradeBookId, priority='$priority')" } class ConvertBook(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId) { override val uniqueId: String = "CONVERT_BOOK_$bookId" + override fun toString(): String = "ConvertBook(bookId='$bookId', priority='$priority')" } class RepairExtension(val bookId: String, priority: Int = DEFAULT_PRIORITY, groupId: String) : Task(priority, groupId) { override val uniqueId: String = "REPAIR_EXTENSION_$bookId" + override fun toString(): String = "RepairExtension(bookId='$bookId', priority='$priority')" } class RemoveHashedPages(val bookId: String, val pages: Collection, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId: String = "REMOVE_HASHED_PAGES_$bookId" + override fun toString(): String = "RemoveHashedPages(bookId='$bookId', priority='$priority')" } class RebuildIndex(val entities: Set?, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "REBUILD_INDEX" + override fun toString(): String = "RebuildIndex(priority='$priority',entities='${entities?.map { it.type }}')" } class UpgradeIndex(priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "UPGRADE_INDEX" + override fun toString(): String = "UpgradeIndex(priority='$priority')" } class DeleteBook(val bookId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "DELETE_BOOK_$bookId" + override fun toString(): String = "DeleteBook(bookId='$bookId', priority='$priority')" } class DeleteSeries(val seriesId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "DELETE_SERIES_$seriesId" + override fun toString(): String = "DeleteSeries(seriesId='$seriesId', priority='$priority')" } class FixThumbnailsWithoutMetadata(priority: Int = DEFAULT_PRIORITY) : Task(priority, "FixThumbnailsWithoutMetadata") { override val uniqueId = "FIX_THUMBNAILS_WITHOUT_METADATA_${LocalDateTime.now()}" + override fun toString(): String = "FixThumbnailsWithoutMetadata(priority='$priority')" } class FindBookThumbnailsToRegenerate(val forBiggerResultOnly: Boolean, priority: Int = DEFAULT_PRIORITY) : Task(priority) { override val uniqueId = "FIND_BOOK_THUMBNAILS_TO_REGENERATE" + override fun toString(): String = "FindBookThumbnailsToRegenerate(forBiggerResultOnly='$forBiggerResultOnly', priority='$priority')" } } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEmitter.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEmitter.kt index 6ad09c6ff..5153c64f1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEmitter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEmitter.kt @@ -25,11 +25,18 @@ class TaskEmitter( private val tasksRepository: TasksRepository, private val eventPublisher: ApplicationEventPublisher, ) { - fun scanLibrary(libraryId: String, scanDeep: Boolean = false, priority: Int = DEFAULT_PRIORITY) { + fun scanLibrary( + libraryId: String, + scanDeep: Boolean = false, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.ScanLibrary(libraryId, scanDeep, priority)) } - fun emptyTrash(libraryId: String, priority: Int = DEFAULT_PRIORITY) { + fun emptyTrash( + libraryId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.EmptyTrash(libraryId, priority)) } @@ -55,27 +62,42 @@ class TaskEmitter( .let { submitTasks(it) } } - fun findBooksWithMissingPageHash(library: Library, priority: Int = DEFAULT_PRIORITY) { + fun findBooksWithMissingPageHash( + library: Library, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.FindBooksWithMissingPageHash(library.id, priority)) } - fun hashBookPages(bookIdToSeriesId: Collection, priority: Int = DEFAULT_PRIORITY) { + fun hashBookPages( + bookIdToSeriesId: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { bookIdToSeriesId .map { Task.HashBookPages(it, priority) } .let { submitTasks(it) } } - fun findBooksToConvert(library: Library, priority: Int = DEFAULT_PRIORITY) { + fun findBooksToConvert( + library: Library, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.FindBooksToConvert(library.id, priority)) } - fun convertBookToCbz(books: Collection, priority: Int = DEFAULT_PRIORITY) { + fun convertBookToCbz( + books: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { books .map { Task.ConvertBook(it.id, priority, it.seriesId) } .let { submitTasks(it) } } - fun repairExtensions(library: Library, priority: Int = DEFAULT_PRIORITY) { + fun repairExtensions( + library: Library, + priority: Int = DEFAULT_PRIORITY, + ) { if (library.repairExtensions) bookConverter .getMismatchedExtensionBooks(library) @@ -83,35 +105,57 @@ class TaskEmitter( .let { submitTasks(it) } } - fun findDuplicatePagesToDelete(library: Library, priority: Int = DEFAULT_PRIORITY) { + fun findDuplicatePagesToDelete( + library: Library, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.FindDuplicatePagesToDelete(library.id, priority)) } - fun removeDuplicatePages(bookId: String, pages: Collection, priority: Int = DEFAULT_PRIORITY) { + fun removeDuplicatePages( + bookId: String, + pages: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.RemoveHashedPages(bookId, pages, priority)) } - fun removeDuplicatePages(bookIdToPages: Map>, priority: Int = DEFAULT_PRIORITY) { + fun removeDuplicatePages( + bookIdToPages: Map>, + priority: Int = DEFAULT_PRIORITY, + ) { bookIdToPages .map { Task.RemoveHashedPages(it.key, it.value, priority) } .let { submitTasks(it) } } - fun analyzeBook(book: Book, priority: Int = DEFAULT_PRIORITY) { + fun analyzeBook( + book: Book, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.AnalyzeBook(book.id, priority, book.seriesId)) } - fun analyzeBook(books: Collection, priority: Int = DEFAULT_PRIORITY) { + fun analyzeBook( + books: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { books .map { Task.AnalyzeBook(it.id, priority, it.seriesId) } .let { submitTasks(it) } } - fun generateBookThumbnail(bookId: String, priority: Int = DEFAULT_PRIORITY) { + fun generateBookThumbnail( + bookId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.GenerateBookThumbnail(bookId, priority)) } - fun generateBookThumbnail(bookIds: Collection, priority: Int = DEFAULT_PRIORITY) { + fun generateBookThumbnail( + bookIds: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { bookIds .map { Task.GenerateBookThumbnail(it, priority) } .let { submitTasks(it) } @@ -135,39 +179,67 @@ class TaskEmitter( .let { submitTasks(it) } } - fun refreshSeriesMetadata(seriesId: String, priority: Int = DEFAULT_PRIORITY) { + fun refreshSeriesMetadata( + seriesId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.RefreshSeriesMetadata(seriesId, priority)) } - fun aggregateSeriesMetadata(seriesId: String, priority: Int = DEFAULT_PRIORITY) { + fun aggregateSeriesMetadata( + seriesId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.AggregateSeriesMetadata(seriesId, priority)) } - fun refreshBookLocalArtwork(book: Book, priority: Int = DEFAULT_PRIORITY) { + fun refreshBookLocalArtwork( + book: Book, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.RefreshBookLocalArtwork(book.id, priority)) } - fun refreshBookLocalArtwork(books: Collection, priority: Int = DEFAULT_PRIORITY) { + fun refreshBookLocalArtwork( + books: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { books .map { Task.RefreshBookLocalArtwork(it.id, priority) } .let { submitTasks(it) } } - fun refreshSeriesLocalArtwork(seriesId: String, priority: Int = DEFAULT_PRIORITY) { + fun refreshSeriesLocalArtwork( + seriesId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.RefreshSeriesLocalArtwork(seriesId, priority)) } - fun refreshSeriesLocalArtwork(seriesIds: Collection, priority: Int = DEFAULT_PRIORITY) { + fun refreshSeriesLocalArtwork( + seriesIds: Collection, + priority: Int = DEFAULT_PRIORITY, + ) { seriesIds .map { Task.RefreshSeriesLocalArtwork(it, priority) } .let { submitTasks(it) } } - fun importBook(sourceFile: String, seriesId: String, copyMode: CopyMode, destinationName: String?, upgradeBookId: String?, priority: Int = DEFAULT_PRIORITY) { + fun importBook( + sourceFile: String, + seriesId: String, + copyMode: CopyMode, + destinationName: String?, + upgradeBookId: String?, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.ImportBook(sourceFile, seriesId, copyMode, destinationName, upgradeBookId, priority)) } - fun rebuildIndex(priority: Int = DEFAULT_PRIORITY, entities: Set? = null) { + fun rebuildIndex( + priority: Int = DEFAULT_PRIORITY, + entities: Set? = null, + ) { submitTask(Task.RebuildIndex(entities, priority)) } @@ -175,11 +247,17 @@ class TaskEmitter( submitTask(Task.UpgradeIndex(priority)) } - fun deleteBook(bookId: String, priority: Int = DEFAULT_PRIORITY) { + fun deleteBook( + bookId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.DeleteBook(bookId, priority)) } - fun deleteSeries(seriesId: String, priority: Int = DEFAULT_PRIORITY) { + fun deleteSeries( + seriesId: String, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.DeleteSeries(seriesId, priority)) } @@ -187,7 +265,10 @@ class TaskEmitter( submitTask(Task.FixThumbnailsWithoutMetadata(priority)) } - fun findBookThumbnailsToRegenerate(forBiggerResultOnly: Boolean, priority: Int = DEFAULT_PRIORITY) { + fun findBookThumbnailsToRegenerate( + forBiggerResultOnly: Boolean, + priority: Int = DEFAULT_PRIORITY, + ) { submitTask(Task.FindBookThumbnailsToRegenerate(forBiggerResultOnly, priority)) } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt index 59673128d..8720fb2b9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskEvents.kt @@ -1,4 +1,5 @@ package org.gotson.komga.application.tasks class TaskAddedEvent + class TaskPoolSizeChangedEvent diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt index 773f6589c..8984875a7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt @@ -47,7 +47,6 @@ class TaskHandler( private val thumbnailLifecycle: ThumbnailLifecycle, private val meterRegistry: MeterRegistry, ) { - fun handleTask(task: Task) { logger.info { "Executing task: $task" } try { @@ -162,8 +161,10 @@ class TaskHandler( is Task.DeleteBook -> { bookRepository.findByIdOrNull(task.bookId)?.let { book -> - if (book.oneshot) seriesLifecycle.deleteSeriesFiles(seriesRepository.findByIdOrNull(book.seriesId)!!) - else bookLifecycle.deleteBookFiles(book) + if (book.oneshot) + seriesLifecycle.deleteSeriesFiles(seriesRepository.findByIdOrNull(book.seriesId)!!) + else + bookLifecycle.deleteBookFiles(book) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt index 2ba5c8769..66ee4ed11 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskProcessor.kt @@ -43,10 +43,13 @@ class TaskProcessor( fun processAvailableTask() { if (processTasks) { logger.debug { "Active count: ${executor.activeCount}, Core Pool Size: ${executor.corePoolSize}, Pool Size: ${executor.poolSize}" } - if (executor.corePoolSize == 1) executor.execute { takeAndProcess() } - // fan out while threads are available - else while (tasksRepository.hasAvailable() && executor.activeCount < executor.corePoolSize) + if (executor.corePoolSize == 1) { executor.execute { takeAndProcess() } + } else { + // fan out while threads are available + while (tasksRepository.hasAvailable() && executor.activeCount < executor.corePoolSize) + executor.execute { takeAndProcess() } + } } else { logger.debug { "Not processing tasks" } } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TasksRepository.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TasksRepository.kt index e487f1237..203b27210 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TasksRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TasksRepository.kt @@ -2,15 +2,26 @@ package org.gotson.komga.application.tasks interface TasksRepository { fun hasAvailable(): Boolean + fun takeFirst(owner: String = Thread.currentThread().name): Task? + fun findAll(): List + fun findAllGroupedByOwner(): Map> + fun count(): Int + fun countBySimpleType(): Map + fun save(task: Task) + fun save(tasks: Collection) + fun delete(taskId: String) + fun deleteAll() + fun deleteAllWithoutOwner(): Int + fun disown(): Int } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/AgeRestriction.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/AgeRestriction.kt index b44a93207..70d63d49e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/AgeRestriction.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/AgeRestriction.kt @@ -6,5 +6,6 @@ data class AgeRestriction( ) enum class AllowExclude { - ALLOW_ONLY, EXCLUDE, + ALLOW_ONLY, + EXCLUDE, } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt index f894e2a9b..d74f04c22 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Book.kt @@ -13,18 +13,14 @@ data class Book( val fileSize: Long = 0, val fileHash: String = "", val number: Int = 0, - val id: String = TsidCreator.getTsid256().toString(), val seriesId: String = "", val libraryId: String = "", - val deletedDate: LocalDateTime? = null, val oneshot: Boolean = false, - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - @delegate:Transient val path: Path by lazy { this.url.toURI().toPath() } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt index c63043732..b773a215d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadata.kt @@ -14,7 +14,6 @@ class BookMetadata( tags: Set = emptySet(), val isbn: String = "", val links: List = emptyList(), - val titleLock: Boolean = false, val summaryLock: Boolean = false, val numberLock: Boolean = false, @@ -24,13 +23,10 @@ class BookMetadata( val tagsLock: Boolean = false, val isbnLock: Boolean = false, val linksLock: Boolean = false, - val bookId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - val title = title.trim() val summary = summary.trim() val number = number.trim() diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataAggregation.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataAggregation.kt index bbc1baa78..4763ea276 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataAggregation.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataAggregation.kt @@ -9,9 +9,7 @@ data class BookMetadataAggregation( val releaseDate: LocalDate? = null, val summary: String = "", val summaryNumber: String = "", - val seriesId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt index a164c5ab5..8c902773f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookMetadataPatch.kt @@ -12,7 +12,6 @@ data class BookMetadataPatch( val isbn: String? = null, val links: List? = null, val tags: Set? = null, - val readLists: List = emptyList(), ) { data class ReadListEntry( diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPageNumbered.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPageNumbered.kt index c3e92208d..284bfbfa7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPageNumbered.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookPageNumbered.kt @@ -8,11 +8,11 @@ class BookPageNumbered( fileSize: Long? = null, val pageNumber: Int, ) : BookPage( - fileName = fileName, - mediaType = mediaType, - dimension = dimension, - fileHash = fileHash, - fileSize = fileSize, -) { + fileName = fileName, + mediaType = mediaType, + dimension = dimension, + fileHash = fileHash, + fileSize = fileSize, + ) { override fun toString(): String = "BookPageNumbered(fileName='$fileName', mediaType='$mediaType', dimension=$dimension, fileHash='$fileHash', fileSize=$fileSize, pageNumber=$pageNumber)" } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt index 1cea23370..370384bbe 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/BookSearch.kt @@ -22,10 +22,10 @@ class BookSearchWithReadProgress( val readStatus: Collection? = null, val authors: Collection? = null, ) : BookSearch( - libraryIds = libraryIds, - seriesIds = seriesIds, - searchTerm = searchTerm, - mediaStatus = mediaStatus, - deleted = deleted, - releasedAfter = releasedAfter, -) + libraryIds = libraryIds, + seriesIds = seriesIds, + searchTerm = searchTerm, + mediaStatus = mediaStatus, + deleted = deleted, + releasedAfter = releasedAfter, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/DomainEvent.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/DomainEvent.kt index 8a25aa655..08a44fcc9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/DomainEvent.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/DomainEvent.kt @@ -3,46 +3,65 @@ package org.gotson.komga.domain.model import java.net.URL sealed class DomainEvent { - data class LibraryAdded(val library: Library) : DomainEvent() + data class LibraryUpdated(val library: Library) : DomainEvent() + data class LibraryDeleted(val library: Library) : DomainEvent() + data class LibraryScanned(val library: Library) : DomainEvent() data class SeriesAdded(val series: Series) : DomainEvent() + data class SeriesUpdated(val series: Series) : DomainEvent() + data class SeriesDeleted(val series: Series) : DomainEvent() data class BookAdded(val book: Book) : DomainEvent() + data class BookUpdated(val book: Book) : DomainEvent() + data class BookDeleted(val book: Book) : DomainEvent() + data class BookImported(val book: Book?, val sourceFile: URL, val success: Boolean, val message: String? = null) : DomainEvent() data class CollectionAdded(val collection: SeriesCollection) : DomainEvent() + data class CollectionUpdated(val collection: SeriesCollection) : DomainEvent() + data class CollectionDeleted(val collection: SeriesCollection) : DomainEvent() data class ReadListAdded(val readList: ReadList) : DomainEvent() + data class ReadListUpdated(val readList: ReadList) : DomainEvent() + data class ReadListDeleted(val readList: ReadList) : DomainEvent() data class ReadProgressChanged(val progress: ReadProgress) : DomainEvent() + data class ReadProgressDeleted(val progress: ReadProgress) : DomainEvent() + data class ReadProgressSeriesChanged(val seriesId: String, val userId: String) : DomainEvent() + data class ReadProgressSeriesDeleted(val seriesId: String, val userId: String) : DomainEvent() data class ThumbnailBookAdded(val thumbnail: ThumbnailBook) : DomainEvent() + data class ThumbnailBookDeleted(val thumbnail: ThumbnailBook) : DomainEvent() data class ThumbnailSeriesAdded(val thumbnail: ThumbnailSeries) : DomainEvent() + data class ThumbnailSeriesDeleted(val thumbnail: ThumbnailSeries) : DomainEvent() data class ThumbnailSeriesCollectionAdded(val thumbnail: ThumbnailSeriesCollection) : DomainEvent() + data class ThumbnailSeriesCollectionDeleted(val thumbnail: ThumbnailSeriesCollection) : DomainEvent() data class ThumbnailReadListAdded(val thumbnail: ThumbnailReadList) : DomainEvent() + data class ThumbnailReadListDeleted(val thumbnail: ThumbnailReadList) : DomainEvent() data class UserUpdated(val user: KomgaUser, val expireSession: Boolean) : DomainEvent() + data class UserDeleted(val user: KomgaUser) : DomainEvent() } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt index a1538e15d..7fa5e6e71 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Exceptions.kt @@ -15,13 +15,23 @@ open class CodedException : Exception { fun Exception.withCode(code: String) = CodedException(this, code) class MediaNotReadyException : Exception() + class NoThumbnailFoundException : Exception() + class MediaUnsupportedException(message: String, code: String = "") : CodedException(message, code) + class ImageConversionException(message: String, code: String = "") : CodedException(message, code) + class DirectoryNotFoundException(message: String, code: String = "") : CodedException(message, code) + class DuplicateNameException(message: String, code: String = "") : CodedException(message, code) + class PathContainedInPath(message: String, code: String = "") : CodedException(message, code) + class UserEmailAlreadyExistsException(message: String, code: String = "") : CodedException(message, code) + class BookConversionException(message: String) : Exception(message) + class ComicRackListException(message: String, code: String = "") : CodedException(message, code) + class EntryNotFoundException(message: String) : Exception(message) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/HistoricalEvent.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/HistoricalEvent.kt index 5f178d1de..de0134950 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/HistoricalEvent.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/HistoricalEvent.kt @@ -16,19 +16,21 @@ sealed class HistoricalEvent( type = "BookFileDeleted", bookId = book.id, seriesId = book.seriesId, - properties = mapOf( - "reason" to reason, - "name" to book.path.toString(), - ), + properties = + mapOf( + "reason" to reason, + "name" to book.path.toString(), + ), ) class SeriesFolderDeleted(seriesId: String, seriesPath: Path, reason: String) : HistoricalEvent( type = "SeriesFolderDeleted", seriesId = seriesId, - properties = mapOf( - "reason" to reason, - "name" to seriesPath.toString(), - ), + properties = + mapOf( + "reason" to reason, + "name" to seriesPath.toString(), + ), ) { constructor(series: Series, reason: String) : this(series.id, series.path, reason) } @@ -37,34 +39,37 @@ sealed class HistoricalEvent( type = "BookConverted", bookId = book.id, seriesId = book.seriesId, - properties = mapOf( - "name" to book.path.toString(), - "former file" to previous.path.toString(), - ), + properties = + mapOf( + "name" to book.path.toString(), + "former file" to previous.path.toString(), + ), ) class BookImported(book: Book, series: Series, source: Path, upgrade: Boolean) : HistoricalEvent( type = "BookImported", bookId = book.id, seriesId = series.id, - properties = mapOf( - "name" to book.path.toString(), - "source" to source.toString(), - "upgrade" to if (upgrade) "Yes" else "No", - ), + properties = + mapOf( + "name" to book.path.toString(), + "source" to source.toString(), + "upgrade" to if (upgrade) "Yes" else "No", + ), ) class DuplicatePageDeleted(book: Book, page: BookPageNumbered) : HistoricalEvent( type = "DuplicatePageDeleted", bookId = book.id, seriesId = book.seriesId, - properties = mapOf( - "name" to book.path.toString(), - "page number" to page.pageNumber.toString(), - "page file name" to page.fileName, - "page file hash" to page.fileHash, - "page file size" to page.fileSize.toString(), - "page media type" to page.mediaType, - ), + properties = + mapOf( + "name" to book.path.toString(), + "page number" to page.pageNumber.toString(), + "page file name" to page.fileName, + "page file hash" to page.fileHash, + "page file size" to page.fileSize.toString(), + "page media type" to page.mediaType, + ), ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt index 6356e3491..4386b5864 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/KomgaUser.kt @@ -27,7 +27,6 @@ data class KomgaUser( override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - @delegate:Transient val roles: Set by lazy { buildSet { @@ -65,35 +64,43 @@ data class KomgaUser( return sharedAllLibraries || sharedLibrariesIds.any { it == library.id } } - fun isContentAllowed(ageRating: Int? = null, sharingLabels: Set = emptySet()): Boolean { + fun isContentAllowed( + ageRating: Int? = null, + sharingLabels: Set = emptySet(), + ): Boolean { val labels = sharingLabels.lowerNotBlank().toSet() val ageAllowed = if (restrictions.ageRestriction?.restriction == AllowExclude.ALLOW_ONLY) ageRating != null && ageRating <= restrictions.ageRestriction.age - else null + else + null val labelAllowed = if (restrictions.labelsAllow.isNotEmpty()) restrictions.labelsAllow.intersect(labels).isNotEmpty() - else null + else + null - val allowed = when { - ageAllowed == null -> labelAllowed != false - labelAllowed == null -> ageAllowed != false - else -> ageAllowed != false || labelAllowed != false - } + val allowed = + when { + ageAllowed == null -> labelAllowed != false + labelAllowed == null -> ageAllowed != false + else -> ageAllowed != false || labelAllowed != false + } if (!allowed) return false val ageDenied = if (restrictions.ageRestriction?.restriction == AllowExclude.EXCLUDE) ageRating != null && ageRating >= restrictions.ageRestriction.age - else false + else + false val labelDenied = if (restrictions.labelsExclude.isNotEmpty()) restrictions.labelsExclude.intersect(labels).isNotEmpty() - else false + else + false return !ageDenied && !labelDenied } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt index 0e2036f5c..a274badeb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Library.kt @@ -34,15 +34,11 @@ data class Library( val hashPages: Boolean = false, val analyzeDimensions: Boolean = true, val oneshotsDirectory: String? = null, - val unavailableDate: LocalDateTime? = null, - val id: String = TsidCreator.getTsid256().toString(), - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - enum class SeriesCover { FIRST, FIRST_UNREAD_OR_FIRST, diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/MarkSelectedPreference.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/MarkSelectedPreference.kt index 541785e1d..1ded0d881 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/MarkSelectedPreference.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/MarkSelectedPreference.kt @@ -1,5 +1,7 @@ package org.gotson.komga.domain.model enum class MarkSelectedPreference { - NO, YES, IF_NONE_OR_GENERATED + NO, + YES, + IF_NONE_OR_GENERATED, } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt index d3669f057..9ff2b65ba 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Media.kt @@ -15,12 +15,15 @@ data class Media( override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - @delegate:Transient val profile: MediaProfile? by lazy { MediaType.fromMediaType(mediaType)?.profile } enum class Status { - UNKNOWN, ERROR, READY, UNSUPPORTED, OUTDATED + UNKNOWN, + ERROR, + READY, + UNSUPPORTED, + OUTDATED, } override fun toString(): String { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaExtension.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaExtension.kt index b878014c1..77dc3af3b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaExtension.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaExtension.kt @@ -8,17 +8,19 @@ interface MediaExtension class ProxyExtension private constructor( val extensionClassName: String, ) : MediaExtension { - companion object { fun of(extensionClass: String?): ProxyExtension? = extensionClass?.let { val kClass = Class.forName(extensionClass).kotlin - if (kClass.qualifiedName != MediaExtension::class.qualifiedName && kClass.isSubclassOf(MediaExtension::class)) ProxyExtension(extensionClass) - else null + if (kClass.qualifiedName != MediaExtension::class.qualifiedName && kClass.isSubclassOf(MediaExtension::class)) + ProxyExtension(extensionClass) + else + null } } inline fun proxyForType(): Boolean = T::class.qualifiedName == extensionClassName + fun proxyForType(clazz: KClass): Boolean = clazz.qualifiedName == extensionClassName } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaFile.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaFile.kt index 4d0fc5057..fae7b91ba 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaFile.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/MediaFile.kt @@ -7,6 +7,7 @@ data class MediaFile( val fileSize: Long? = null, ) { enum class SubType { - EPUB_PAGE, EPUB_ASSET + EPUB_PAGE, + EPUB_ASSET, } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/MetadataPatchTarget.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/MetadataPatchTarget.kt index de951878c..ac6ba2e49 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/MetadataPatchTarget.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/MetadataPatchTarget.kt @@ -1,5 +1,8 @@ package org.gotson.komga.domain.model enum class MetadataPatchTarget { - BOOK, SERIES, READLIST, COLLECTION + BOOK, + SERIES, + READLIST, + COLLECTION, } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/PageHashKnown.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/PageHashKnown.kt index 4d30e28c7..50b9d11c6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/PageHashKnown.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/PageHashKnown.kt @@ -8,7 +8,6 @@ class PageHashKnown( val action: Action, val deleteCount: Int = 0, val matchCount: Int = 0, - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable, PageHash(hash, size) { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Locator.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Locator.kt index bcaf0fb04..4ce10486d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Locator.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Locator.kt @@ -44,7 +44,6 @@ data class R2Locator( */ val text: Text? = null, ) { - @JsonInclude(JsonInclude.Include.NON_EMPTY) data class Location( /** diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Progression.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Progression.kt index c61413772..dca913622 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Progression.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/R2Progression.kt @@ -9,8 +9,9 @@ data class R2Progression( val locator: R2Locator, ) -fun ReadProgress.toR2Progression() = R2Progression( - modified = readDate.toZonedDateTime(), - device = R2Device(deviceId, deviceName), - locator = locator ?: R2Locator("", ""), -) +fun ReadProgress.toR2Progression() = + R2Progression( + modified = readDate.toZonedDateTime(), + device = R2Device(deviceId, deviceName), + locator = locator ?: R2Locator("", ""), + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadList.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadList.kt index c3c11f465..7b6b6cc93 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadList.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadList.kt @@ -11,14 +11,10 @@ data class ReadList( * Indicates whether the read list is ordered manually */ val ordered: Boolean = true, - val bookIds: SortedMap = sortedMapOf(), - val id: String = TsidCreator.getTsid256().toString(), - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, - /** * Indicates that the bookIds have been filtered and is not exhaustive. */ diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt index 62c024ec0..c588cda0a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadProgress.kt @@ -11,7 +11,6 @@ data class ReadProgress( val deviceId: String = "", val deviceName: String = "", val locator: R2Locator? = null, - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadStatus.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadStatus.kt index 3f4163854..e1c384313 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadStatus.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ReadStatus.kt @@ -1,5 +1,7 @@ package org.gotson.komga.domain.model enum class ReadStatus { - UNREAD, READ, IN_PROGRESS + UNREAD, + READ, + IN_PROGRESS, } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt index 2d7237a3a..5d794dfd9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Series.kt @@ -10,18 +10,14 @@ data class Series( val name: String, val url: URL, val fileLastModified: LocalDateTime, - val id: String = TsidCreator.getTsid256().toString(), val libraryId: String = "", val bookCount: Int = 0, - val deletedDate: LocalDateTime? = null, val oneshot: Boolean = false, - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { - @delegate:Transient val path: Path by lazy { this.url.toURI().toPath() } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt index 60a51b8ee..9ea2b0b52 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt @@ -9,14 +9,10 @@ data class SeriesCollection( * Indicates whether the collection is ordered manually */ val ordered: Boolean = false, - val seriesIds: List = emptyList(), - val id: String = TsidCreator.getTsid256().toString(), - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, - /** * Indicates that the seriesIds have been filtered and is not exhaustive. */ diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt index ebf361f56..8e6972c06 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt @@ -18,7 +18,6 @@ class SeriesMetadata( sharingLabels: Set = emptySet(), val links: List = emptyList(), val alternateTitles: List = emptyList(), - val statusLock: Boolean = false, val titleLock: Boolean = false, val titleSortLock: Boolean = false, @@ -33,9 +32,7 @@ class SeriesMetadata( val sharingLabelsLock: Boolean = false, val linksLock: Boolean = false, val alternateTitlesLock: Boolean = false, - val seriesId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { @@ -116,7 +113,10 @@ class SeriesMetadata( ) enum class Status { - ENDED, ONGOING, ABANDONED, HIATUS + ENDED, + ONGOING, + ABANDONED, + HIATUS, } enum class ReadingDirection { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadataPatch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadataPatch.kt index b128c1ee9..3fe3ff5d0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadataPatch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadataPatch.kt @@ -11,6 +11,5 @@ data class SeriesMetadataPatch( val language: String?, val genres: Set?, val totalBookCount: Int?, - val collections: Set, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt index 8c0c4a3c3..6f3b8a303 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesSearch.kt @@ -12,7 +12,9 @@ open class SeriesSearch( val oneshot: Boolean? = null, ) { enum class SearchField { - NAME, TITLE, TITLE_SORT + NAME, + TITLE, + TITLE_SORT, } } @@ -35,13 +37,13 @@ class SeriesSearchWithReadProgress( val authors: Collection? = null, val sharingLabels: Collection? = null, ) : SeriesSearch( - libraryIds = libraryIds, - collectionIds = collectionIds, - searchTerm = searchTerm, - searchRegex = searchRegex, - metadataStatus = metadataStatus, - publishers = publishers, - deleted = deleted, - complete = complete, - oneshot = oneshot, -) + libraryIds = libraryIds, + collectionIds = collectionIds, + searchTerm = searchTerm, + searchRegex = searchRegex, + metadataStatus = metadataStatus, + publishers = publishers, + deleted = deleted, + complete = complete, + oneshot = oneshot, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/Sidecar.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/Sidecar.kt index e8f44f565..5ffcc6402 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/Sidecar.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/Sidecar.kt @@ -10,13 +10,14 @@ data class Sidecar( val type: Type, val source: Source, ) { - enum class Type { - ARTWORK, METADATA + ARTWORK, + METADATA, } enum class Source { - SERIES, BOOK + SERIES, + BOOK, } } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailBook.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailBook.kt index d0ce947a0..70ac7d807 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailBook.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailBook.kt @@ -14,15 +14,15 @@ data class ThumbnailBook( val mediaType: String, val fileSize: Long, val dimension: Dimension, - val id: String = TsidCreator.getTsid256().toString(), val bookId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { enum class Type { - GENERATED, SIDECAR, USER_UPLOADED + GENERATED, + SIDECAR, + USER_UPLOADED, } fun exists(): Boolean { @@ -39,7 +39,8 @@ data class ThumbnailBook( if (thumbnail != null) { if (other.thumbnail == null) return false if (!thumbnail.contentEquals(other.thumbnail)) return false - } else if (other.thumbnail != null) return false + } else if (other.thumbnail != null) + return false if (url != other.url) return false if (selected != other.selected) return false if (type != other.type) return false diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailReadList.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailReadList.kt index bd16ba482..f50136970 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailReadList.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailReadList.kt @@ -10,10 +10,8 @@ data class ThumbnailReadList( val mediaType: String, val fileSize: Long, val dimension: Dimension, - val id: String = TsidCreator.getTsid256().toString(), val readListId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeries.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeries.kt index 4e5989eb0..71916ee76 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeries.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeries.kt @@ -14,15 +14,14 @@ data class ThumbnailSeries( val mediaType: String, val fileSize: Long, val dimension: Dimension, - val id: String = TsidCreator.getTsid256().toString(), val seriesId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { enum class Type { - SIDECAR, USER_UPLOADED + SIDECAR, + USER_UPLOADED, } fun exists(): Boolean { @@ -39,7 +38,8 @@ data class ThumbnailSeries( if (thumbnail != null) { if (other.thumbnail == null) return false if (!thumbnail.contentEquals(other.thumbnail)) return false - } else if (other.thumbnail != null) return false + } else if (other.thumbnail != null) + return false if (url != other.url) return false if (selected != other.selected) return false if (type != other.type) return false diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeriesCollection.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeriesCollection.kt index fa235feda..8048a9c4d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeriesCollection.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ThumbnailSeriesCollection.kt @@ -10,10 +10,8 @@ data class ThumbnailSeriesCollection( val mediaType: String, val fileSize: Long, val dimension: Dimension, - val id: String = TsidCreator.getTsid256().toString(), val collectionId: String = "", - override val createdDate: LocalDateTime = LocalDateTime.now(), override val lastModifiedDate: LocalDateTime = createdDate, ) : Auditable { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/AuthenticationActivityRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/AuthenticationActivityRepository.kt index 15cb9333f..691289c17 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/AuthenticationActivityRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/AuthenticationActivityRepository.kt @@ -8,11 +8,17 @@ import java.time.LocalDateTime interface AuthenticationActivityRepository { fun findAll(pageable: Pageable): Page - fun findAllByUser(user: KomgaUser, pageable: Pageable): Page + + fun findAllByUser( + user: KomgaUser, + pageable: Pageable, + ): Page + fun findMostRecentByUser(user: KomgaUser): AuthenticationActivity? fun insert(activity: AuthenticationActivity) fun deleteByUser(user: KomgaUser) + fun deleteOlderThan(dateTime: LocalDateTime) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataAggregationRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataAggregationRepository.kt index 826beea30..bab6c8fa3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataAggregationRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataAggregationRepository.kt @@ -4,12 +4,15 @@ import org.gotson.komga.domain.model.BookMetadataAggregation interface BookMetadataAggregationRepository { fun findById(seriesId: String): BookMetadataAggregation + fun findByIdOrNull(seriesId: String): BookMetadataAggregation? fun insert(metadata: BookMetadataAggregation) + fun update(metadata: BookMetadataAggregation) fun delete(seriesId: String) + fun delete(seriesIds: Collection) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt index 95d60d9aa..ca13ddac6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookMetadataRepository.kt @@ -4,17 +4,21 @@ import org.gotson.komga.domain.model.BookMetadata interface BookMetadataRepository { fun findById(bookId: String): BookMetadata + fun findByIdOrNull(bookId: String): BookMetadata? fun findAllByIds(bookIds: Collection): Collection fun insert(metadata: BookMetadata) + fun insert(metadatas: Collection) fun update(metadata: BookMetadata) + fun update(metadatas: Collection) fun delete(bookId: String) + fun delete(bookIds: Collection) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt index c1ce382be..b254a3bef 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt @@ -10,40 +10,85 @@ import java.net.URL interface BookRepository { fun findByIdOrNull(bookId: String): Book? - fun findNotDeletedByLibraryIdAndUrlOrNull(libraryId: String, url: URL): Book? + + fun findNotDeletedByLibraryIdAndUrlOrNull( + libraryId: String, + url: URL, + ): Book? fun findAll(): Collection + fun findAllBySeriesId(seriesId: String): Collection + fun findAllBySeriesIds(seriesIds: Collection): Collection - fun findAllNotDeletedByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): Collection + + fun findAllNotDeletedByLibraryIdAndUrlNotIn( + libraryId: String, + urls: Collection, + ): Collection + fun findAll(bookSearch: BookSearch): Collection - fun findAll(bookSearch: BookSearch, pageable: Pageable): Page + + fun findAll( + bookSearch: BookSearch, + pageable: Pageable, + ): Page + fun findAllDeletedByFileSize(fileSize: Long): Collection + fun findAllByLibraryIdAndWithEmptyHash(libraryId: String): Collection - fun findAllByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection - fun findAllByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection + + fun findAllByLibraryIdAndMediaTypes( + libraryId: String, + mediaTypes: Collection, + ): Collection + + fun findAllByLibraryIdAndMismatchedExtension( + libraryId: String, + mediaType: String, + extension: String, + ): Collection fun getLibraryIdOrNull(bookId: String): String? + fun getSeriesIdOrNull(bookId: String): String? + fun findFirstIdInSeriesOrNull(seriesId: String): String? + fun findLastIdInSeriesOrNull(seriesId: String): String? - fun findFirstUnreadIdInSeriesOrNull(seriesId: String, userId: String): String? + + fun findFirstUnreadIdInSeriesOrNull( + seriesId: String, + userId: String, + ): String? fun findAllIdsBySeriesId(seriesId: String): Collection + fun findAllIdsBySeriesIds(seriesIds: Collection): Collection + fun findAllIdsByLibraryId(libraryId: String): Collection - fun findAllIds(bookSearch: BookSearch, sort: Sort): Collection + + fun findAllIds( + bookSearch: BookSearch, + sort: Sort, + ): Collection fun insert(book: Book) + fun insert(books: Collection) + fun update(book: Book) + fun update(books: Collection) fun delete(bookId: String) + fun delete(bookIds: Collection) + fun deleteAll() fun count(): Long + fun countGroupedByLibraryId(): Map fun getFilesizeGroupedByLibraryId(): Map diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt index 4cf734459..9005bc426 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/KomgaUserRepository.kt @@ -6,6 +6,7 @@ interface KomgaUserRepository { fun count(): Long fun findByIdOrNull(id: String): KomgaUser? + fun findByEmailIgnoreCaseOrNull(email: String): KomgaUser? fun findAll(): Collection @@ -13,11 +14,17 @@ interface KomgaUserRepository { fun existsByEmailIgnoreCase(email: String): Boolean fun insert(user: KomgaUser) + fun update(user: KomgaUser) fun delete(userId: String) + fun deleteAll() fun findAnnouncementIdsReadByUserId(userId: String): Set - fun saveAnnouncementIdsRead(user: KomgaUser, announcementIds: Set) + + fun saveAnnouncementIdsRead( + user: KomgaUser, + announcementIds: Set, + ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt index edf7bc26b..d6d8d2135 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/LibraryRepository.kt @@ -4,15 +4,19 @@ import org.gotson.komga.domain.model.Library interface LibraryRepository { fun findById(libraryId: String): Library + fun findByIdOrNull(libraryId: String): Library? fun findAll(): Collection + fun findAllByIds(libraryIds: Collection): Collection fun delete(libraryId: String) + fun deleteAll() fun insert(library: Library) + fun update(library: Library) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt index 35ece8396..431db645b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/MediaRepository.kt @@ -5,20 +5,29 @@ import org.gotson.komga.domain.model.MediaExtension interface MediaRepository { fun findById(bookId: String): Media + fun findByIdOrNull(bookId: String): Media? - fun findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(libraryId: String, mediaTypes: Collection, pageHashing: Int): Collection + fun findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash( + libraryId: String, + mediaTypes: Collection, + pageHashing: Int, + ): Collection fun getPagesSize(bookId: String): Int + fun getPagesSizes(bookIds: Collection): Collection> + fun findExtensionByIdOrNull(bookId: String): MediaExtension? fun insert(media: Media) + fun insert(medias: Collection) fun update(media: Media) fun delete(bookId: String) + fun deleteByBookIds(bookIds: Collection) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/PageHashRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/PageHashRepository.kt index be7788c1a..429c3b631 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/PageHashRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/PageHashRepository.kt @@ -9,14 +9,30 @@ import org.springframework.data.domain.Pageable interface PageHashRepository { fun findKnown(pageHash: String): PageHashKnown? - fun findAllKnown(actions: List?, pageable: Pageable): Page + + fun findAllKnown( + actions: List?, + pageable: Pageable, + ): Page + fun findAllUnknown(pageable: Pageable): Page - fun findMatchesByHash(pageHash: String, pageable: Pageable): Page - fun findMatchesByKnownHashAction(actions: List?, libraryId: String?): Map> + fun findMatchesByHash( + pageHash: String, + pageable: Pageable, + ): Page + + fun findMatchesByKnownHashAction( + actions: List?, + libraryId: String?, + ): Map> fun getKnownThumbnail(pageHash: String): ByteArray? - fun insert(pageHash: PageHashKnown, thumbnail: ByteArray?) + fun insert( + pageHash: PageHashKnown, + thumbnail: ByteArray?, + ) + fun update(pageHash: PageHashKnown) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadListRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadListRepository.kt index f1b25e1cf..ce687fd13 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadListRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadListRepository.kt @@ -10,33 +10,51 @@ interface ReadListRepository { * Find one ReadList by [readListId], * optionally with only bookIds filtered by the provided [filterOnLibraryIds] it not null. */ - fun findByIdOrNull(readListId: String, filterOnLibraryIds: Collection? = null, restrictions: ContentRestrictions = ContentRestrictions()): ReadList? + fun findByIdOrNull( + readListId: String, + filterOnLibraryIds: Collection? = null, + restrictions: ContentRestrictions = ContentRestrictions(), + ): ReadList? /** * Find all ReadList * optionally with at least one Book belonging to the provided [belongsToLibraryIds] if not null, * optionally with only bookIds filtered by the provided [filterOnLibraryIds] if not null. */ - fun findAll(belongsToLibraryIds: Collection? = null, filterOnLibraryIds: Collection? = null, search: String? = null, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page + fun findAll( + belongsToLibraryIds: Collection? = null, + filterOnLibraryIds: Collection? = null, + search: String? = null, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page /** * Find all ReadList that contains the provided [containsBookId], * optionally with only bookIds filtered by the provided [filterOnLibraryIds] if not null. */ - fun findAllContainingBookId(containsBookId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions = ContentRestrictions()): Collection + fun findAllContainingBookId( + containsBookId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Collection fun findAllEmpty(): Collection fun findByNameOrNull(name: String): ReadList? fun insert(readList: ReadList) + fun update(readList: ReadList) fun removeBookFromAll(bookId: String) + fun removeBooksFromAll(bookIds: Collection) fun delete(readListId: String) + fun delete(readListIds: Collection) + fun deleteAll() fun existsByName(name: String): Boolean diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt index 0b205803c..9456a3a86 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReadProgressRepository.kt @@ -3,21 +3,43 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.ReadProgress interface ReadProgressRepository { - fun findByBookIdAndUserIdOrNull(bookId: String, userId: String): ReadProgress? + fun findByBookIdAndUserIdOrNull( + bookId: String, + userId: String, + ): ReadProgress? fun findAll(): Collection + fun findAllByUserId(userId: String): Collection + fun findAllByBookId(bookId: String): Collection - fun findAllByBookIdsAndUserId(bookIds: Collection, userId: String): Collection + + fun findAllByBookIdsAndUserId( + bookIds: Collection, + userId: String, + ): Collection fun save(readProgress: ReadProgress) + fun save(readProgresses: Collection) - fun delete(bookId: String, userId: String) + fun delete( + bookId: String, + userId: String, + ) + fun deleteByUserId(userId: String) + fun deleteByBookId(bookId: String) + fun deleteByBookIds(bookIds: Collection) - fun deleteByBookIdsAndUserId(bookIds: Collection, userId: String) + + fun deleteByBookIdsAndUserId( + bookIds: Collection, + userId: String, + ) + fun deleteBySeriesIds(seriesIds: Collection) + fun deleteAll() } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt index c47694cc5..de32548eb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt @@ -6,51 +6,185 @@ import org.springframework.data.domain.Pageable import java.time.LocalDate interface ReferentialRepository { - fun findAllAuthorsByName(search: String, filterOnLibraryIds: Collection?): List - fun findAllAuthorsByNameAndLibrary(search: String, libraryId: String, filterOnLibraryIds: Collection?): List - fun findAllAuthorsByNameAndCollection(search: String, collectionId: String, filterOnLibraryIds: Collection?): List - fun findAllAuthorsByNameAndSeries(search: String, seriesId: String, filterOnLibraryIds: Collection?): List - fun findAllAuthorsNamesByName(search: String, filterOnLibraryIds: Collection?): List + fun findAllAuthorsByName( + search: String, + filterOnLibraryIds: Collection?, + ): List + + fun findAllAuthorsByNameAndLibrary( + search: String, + libraryId: String, + filterOnLibraryIds: Collection?, + ): List + + fun findAllAuthorsByNameAndCollection( + search: String, + collectionId: String, + filterOnLibraryIds: Collection?, + ): List + + fun findAllAuthorsByNameAndSeries( + search: String, + seriesId: String, + filterOnLibraryIds: Collection?, + ): List + + fun findAllAuthorsNamesByName( + search: String, + filterOnLibraryIds: Collection?, + ): List + fun findAllAuthorsRoles(filterOnLibraryIds: Collection?): List - fun findAllAuthorsByName(search: String?, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable): Page - fun findAllAuthorsByNameAndLibrary(search: String?, role: String?, libraryId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page - fun findAllAuthorsByNameAndCollection(search: String?, role: String?, collectionId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page - fun findAllAuthorsByNameAndSeries(search: String?, role: String?, seriesId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page - fun findAllAuthorsByNameAndReadList(search: String?, role: String?, readListId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page + fun findAllAuthorsByName( + search: String?, + role: String?, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page + + fun findAllAuthorsByNameAndLibrary( + search: String?, + role: String?, + libraryId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page + + fun findAllAuthorsByNameAndCollection( + search: String?, + role: String?, + collectionId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page + + fun findAllAuthorsByNameAndSeries( + search: String?, + role: String?, + seriesId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page + + fun findAllAuthorsByNameAndReadList( + search: String?, + role: String?, + readListId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page fun findAllGenres(filterOnLibraryIds: Collection?): Set - fun findAllGenresByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllGenresByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllGenresByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllGenresByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection?): Set - fun findAllSeriesAndBookTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllSeriesAndBookTagsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllSeriesAndBookTagsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllSeriesAndBookTagsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set + fun findAllSeriesTags(filterOnLibraryIds: Collection?): Set - fun findAllSeriesTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllSeriesTagsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllSeriesTagsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllSeriesTagsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set + fun findAllBookTags(filterOnLibraryIds: Collection?): Set - fun findAllBookTagsBySeries(seriesId: String, filterOnLibraryIds: Collection?): Set - fun findAllBookTagsByReadList(readListId: String, filterOnLibraryIds: Collection?): Set + + fun findAllBookTagsBySeries( + seriesId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllBookTagsByReadList( + readListId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllLanguages(filterOnLibraryIds: Collection?): Set - fun findAllLanguagesByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllLanguagesByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllLanguagesByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllLanguagesByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllPublishers(filterOnLibraryIds: Collection?): Set - fun findAllPublishers(filterOnLibraryIds: Collection?, pageable: Pageable): Page - fun findAllPublishersByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllPublishersByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllPublishers( + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page + + fun findAllPublishersByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllPublishersByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllAgeRatings(filterOnLibraryIds: Collection?): Set - fun findAllAgeRatingsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllAgeRatingsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllAgeRatingsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllAgeRatingsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllSeriesReleaseDates(filterOnLibraryIds: Collection?): Set - fun findAllSeriesReleaseDatesByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllSeriesReleaseDatesByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllSeriesReleaseDatesByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllSeriesReleaseDatesByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set fun findAllSharingLabels(filterOnLibraryIds: Collection?): Set - fun findAllSharingLabelsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set - fun findAllSharingLabelsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set + + fun findAllSharingLabelsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set + + fun findAllSharingLabelsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt index c40a3482f..2d9b4d5e6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt @@ -10,33 +10,51 @@ interface SeriesCollectionRepository { * Find one SeriesCollection by [collectionId], * optionally with only seriesId filtered by the provided [filterOnLibraryIds] if not null. */ - fun findByIdOrNull(collectionId: String, filterOnLibraryIds: Collection? = null, restrictions: ContentRestrictions = ContentRestrictions()): SeriesCollection? + fun findByIdOrNull( + collectionId: String, + filterOnLibraryIds: Collection? = null, + restrictions: ContentRestrictions = ContentRestrictions(), + ): SeriesCollection? /** * Find all SeriesCollection * optionally with at least one Series belonging to the provided [belongsToLibraryIds] if not null, * optionally with only seriesId filtered by the provided [filterOnLibraryIds] if not null. */ - fun findAll(belongsToLibraryIds: Collection? = null, filterOnLibraryIds: Collection? = null, search: String? = null, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page + fun findAll( + belongsToLibraryIds: Collection? = null, + filterOnLibraryIds: Collection? = null, + search: String? = null, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page /** * Find all SeriesCollection that contains the provided [containsSeriesId], * optionally with only seriesId filtered by the provided [filterOnLibraryIds] if not null. */ - fun findAllContainingSeriesId(containsSeriesId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions = ContentRestrictions()): Collection + fun findAllContainingSeriesId( + containsSeriesId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Collection fun findAllEmpty(): Collection fun findByNameOrNull(name: String): SeriesCollection? fun insert(collection: SeriesCollection) + fun update(collection: SeriesCollection) fun removeSeriesFromAll(seriesId: String) + fun removeSeriesFromAll(seriesIds: Collection) fun delete(collectionId: String) + fun delete(collectionIds: Collection) + fun deleteAll() fun existsByName(name: String): Boolean diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt index 0b2c9a655..a7895ead0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesMetadataRepository.kt @@ -4,12 +4,15 @@ import org.gotson.komga.domain.model.SeriesMetadata interface SeriesMetadataRepository { fun findById(seriesId: String): SeriesMetadata + fun findByIdOrNull(seriesId: String): SeriesMetadata? fun insert(metadata: SeriesMetadata) + fun update(metadata: SeriesMetadata) fun delete(seriesId: String) + fun delete(seriesIds: Collection) fun count(): Long diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt index e63636030..9b4c18fd3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesRepository.kt @@ -6,12 +6,23 @@ import java.net.URL interface SeriesRepository { fun findByIdOrNull(seriesId: String): Series? - fun findNotDeletedByLibraryIdAndUrlOrNull(libraryId: String, url: URL): Series? + + fun findNotDeletedByLibraryIdAndUrlOrNull( + libraryId: String, + url: URL, + ): Series? fun findAll(): Collection + fun findAllByLibraryId(libraryId: String): Collection - fun findAllNotDeletedByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): Collection + + fun findAllNotDeletedByLibraryIdAndUrlNotIn( + libraryId: String, + urls: Collection, + ): Collection + fun findAllByTitleContaining(title: String): Collection + fun findAll(search: SeriesSearch): Collection fun getLibraryId(seriesId: String): String? @@ -19,12 +30,19 @@ interface SeriesRepository { fun findAllIdsByLibraryId(libraryId: String): Collection fun insert(series: Series) - fun update(series: Series, updateModifiedTime: Boolean = true) + + fun update( + series: Series, + updateModifiedTime: Boolean = true, + ) fun delete(seriesId: String) + fun delete(seriesIds: Collection) + fun deleteAll() fun count(): Long + fun countGroupedByLibraryId(): Map } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SidecarRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SidecarRepository.kt index 75339940c..4f5a48362 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SidecarRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/SidecarRepository.kt @@ -7,9 +7,16 @@ import java.net.URL interface SidecarRepository { fun findAll(): Collection - fun save(libraryId: String, sidecar: Sidecar) + fun save( + libraryId: String, + sidecar: Sidecar, + ) + + fun deleteByLibraryIdAndUrls( + libraryId: String, + urls: Collection, + ) - fun deleteByLibraryIdAndUrls(libraryId: String, urls: Collection) fun deleteByLibraryId(libraryId: String) fun countGroupedByLibraryId(): Map diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailBookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailBookRepository.kt index c61db04f3..1749f3431 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailBookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailBookRepository.kt @@ -6,20 +6,39 @@ import org.springframework.data.domain.Pageable interface ThumbnailBookRepository { fun findByIdOrNull(thumbnailId: String): ThumbnailBook? + fun findSelectedByBookIdOrNull(bookId: String): ThumbnailBook? + fun findAllByBookId(bookId: String): Collection - fun findAllByBookIdAndType(bookId: String, type: ThumbnailBook.Type): Collection + + fun findAllByBookIdAndType( + bookId: String, + type: ThumbnailBook.Type, + ): Collection + fun findAllWithoutMetadata(pageable: Pageable): Page - fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan(type: ThumbnailBook.Type, size: Int): Collection + + fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan( + type: ThumbnailBook.Type, + size: Int, + ): Collection fun insert(thumbnail: ThumbnailBook) + fun update(thumbnail: ThumbnailBook) + fun updateMetadata(thumbnails: Collection) fun markSelected(thumbnail: ThumbnailBook) fun delete(thumbnailBookId: String) + fun deleteByBookId(bookId: String) - fun deleteByBookIdAndType(bookId: String, type: ThumbnailBook.Type) + + fun deleteByBookIdAndType( + bookId: String, + type: ThumbnailBook.Type, + ) + fun deleteByBookIds(bookIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailReadListRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailReadListRepository.kt index eabe58fe9..fa8ecab3b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailReadListRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailReadListRepository.kt @@ -6,17 +6,24 @@ import org.springframework.data.domain.Pageable interface ThumbnailReadListRepository { fun findByIdOrNull(thumbnailId: String): ThumbnailReadList? + fun findSelectedByReadListIdOrNull(readListId: String): ThumbnailReadList? + fun findAllByReadListId(readListId: String): Collection + fun findAllWithoutMetadata(pageable: Pageable): Page fun insert(thumbnail: ThumbnailReadList) + fun update(thumbnail: ThumbnailReadList) + fun updateMetadata(thumbnails: Collection) fun markSelected(thumbnail: ThumbnailReadList) fun delete(thumbnailReadListId: String) + fun deleteByReadListId(readListId: String) + fun deleteByReadListIds(readListIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesCollectionRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesCollectionRepository.kt index c22033913..8391fe19e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesCollectionRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesCollectionRepository.kt @@ -6,17 +6,24 @@ import org.springframework.data.domain.Pageable interface ThumbnailSeriesCollectionRepository { fun findByIdOrNull(thumbnailId: String): ThumbnailSeriesCollection? + fun findSelectedByCollectionIdOrNull(collectionId: String): ThumbnailSeriesCollection? + fun findAllByCollectionId(collectionId: String): Collection + fun findAllWithoutMetadata(pageable: Pageable): Page fun insert(thumbnail: ThumbnailSeriesCollection) + fun update(thumbnail: ThumbnailSeriesCollection) + fun updateMetadata(thumbnails: Collection) fun markSelected(thumbnail: ThumbnailSeriesCollection) fun delete(thumbnailCollectionId: String) + fun deleteByCollectionId(collectionId: String) + fun deleteByCollectionIds(collectionIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesRepository.kt index 04816ee4c..b383c6930 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ThumbnailSeriesRepository.kt @@ -6,17 +6,27 @@ import org.springframework.data.domain.Pageable interface ThumbnailSeriesRepository { fun findByIdOrNull(thumbnailId: String): ThumbnailSeries? + fun findSelectedBySeriesIdOrNull(seriesId: String): ThumbnailSeries? + fun findAllBySeriesId(seriesId: String): Collection - fun findAllBySeriesIdIdAndType(seriesId: String, type: ThumbnailSeries.Type): Collection + + fun findAllBySeriesIdIdAndType( + seriesId: String, + type: ThumbnailSeries.Type, + ): Collection + fun findAllWithoutMetadata(pageable: Pageable): Page fun insert(thumbnail: ThumbnailSeries) + fun updateMetadata(thumbnails: Collection) fun markSelected(thumbnail: ThumbnailSeries) fun delete(thumbnailSeriesId: String) + fun deleteBySeriesId(seriesId: String) + fun deleteBySeriesIds(seriesIds: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/TransientBookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/TransientBookRepository.kt index 1543ff86b..55cf1faec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/TransientBookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/TransientBookRepository.kt @@ -4,6 +4,8 @@ import org.gotson.komga.domain.model.TransientBook interface TransientBookRepository { fun findByIdOrNull(transientBookId: String): TransientBook? + fun save(transientBook: TransientBook) + fun save(transientBooks: Collection) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt index 0af67d830..dcb17bdc6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookAnalyzer.kt @@ -51,22 +51,27 @@ class BookAnalyzer( @Qualifier("pdfImageType") private val pdfImageType: ImageType, ) { + val divinaExtractors = + extractors + .flatMap { e -> e.mediaTypes().map { it to e } } + .toMap() - val divinaExtractors = extractors - .flatMap { e -> e.mediaTypes().map { it to e } } - .toMap() - - fun analyze(book: Book, analyzeDimensions: Boolean): Media { + fun analyze( + book: Book, + analyzeDimensions: Boolean, + ): Media { logger.info { "Trying to analyze book: $book" } return try { - var mediaType = contentDetector.detectMediaType(book.path).let { - logger.info { "Detected media type: $it" } - MediaType.fromMediaType(it) ?: return Media(mediaType = it, status = Media.Status.UNSUPPORTED, comment = "ERR_1001", bookId = book.id) - } + var mediaType = + contentDetector.detectMediaType(book.path).let { + logger.info { "Detected media type: $it" } + MediaType.fromMediaType(it) ?: return Media(mediaType = it, status = Media.Status.UNSUPPORTED, comment = "ERR_1001", bookId = book.id) + } if (book.path.extension.lowercase() == "epub" && mediaType != MediaType.EPUB) { - if (epubExtractor.isEpub(book.path)) mediaType = MediaType.EPUB - else { + if (epubExtractor.isEpub(book.path)) { + mediaType = MediaType.EPUB + } else { logger.warn { "Epub file is malformed, file is probably broken: ${book.path}" } return Media(mediaType = mediaType.type, status = Media.Status.ERROR, comment = "ERR_1032", bookId = book.id) } @@ -89,32 +94,39 @@ class BookAnalyzer( }.copy(bookId = book.id) } - private fun analyzeDivina(book: Book, mediaType: MediaType, analyzeDimensions: Boolean): Media { - val entries = try { - divinaExtractors[mediaType.type]?.getEntries(book.path, analyzeDimensions) - ?: return Media(status = Media.Status.UNSUPPORTED) - } catch (ex: MediaUnsupportedException) { - return Media(status = Media.Status.UNSUPPORTED, comment = ex.code) - } catch (ex: Exception) { - logger.error(ex) { "Error while analyzing book: $book" } - return Media(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(fileName = it.name, mediaType = it.mediaType!!, dimension = it.dimension, fileSize = it.fileSize) }, - others, - ) + private fun analyzeDivina( + book: Book, + mediaType: MediaType, + analyzeDimensions: Boolean, + ): Media { + val entries = + try { + divinaExtractors[mediaType.type]?.getEntries(book.path, analyzeDimensions) + ?: return Media(status = Media.Status.UNSUPPORTED) + } catch (ex: MediaUnsupportedException) { + return Media(status = Media.Status.UNSUPPORTED, comment = ex.code) + } catch (ex: Exception) { + logger.error(ex) { "Error while analyzing book: $book" } + return Media(status = Media.Status.ERROR, comment = "ERR_1008") } - val entriesErrorSummary = others - .filter { it.mediaType.isNullOrBlank() } - .map { it.name } - .ifEmpty { null } - ?.joinToString(prefix = "ERR_1007 [", postfix = "]") { it } + val (pages, others) = + entries + .partition { entry -> + entry.mediaType?.let { contentDetector.isImage(it) } ?: false + }.let { (images, others) -> + Pair( + images.map { BookPage(fileName = it.name, mediaType = it.mediaType!!, dimension = it.dimension, fileSize = it.fileSize) }, + others, + ) + } + + 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" } @@ -127,30 +139,38 @@ class BookAnalyzer( return Media(status = Media.Status.READY, pages = pages, pageCount = pages.size, files = files, comment = entriesErrorSummary) } - private fun analyzeEpub(book: Book, analyzeDimensions: Boolean): Media { + private fun analyzeEpub( + book: Book, + analyzeDimensions: Boolean, + ): Media { val manifest = epubExtractor.getManifest(book.path, analyzeDimensions) - val entriesErrorSummary = manifest.missingResources - .map { it.fileName } - .ifEmpty { null } - ?.joinToString(prefix = "ERR_1033 [", postfix = "]") { it } + val entriesErrorSummary = + manifest.missingResources + .map { it.fileName } + .ifEmpty { null } + ?.joinToString(prefix = "ERR_1033 [", postfix = "]") { it } return Media( status = Media.Status.READY, pages = manifest.divinaPages, files = manifest.resources, pageCount = manifest.pageCount, epubDivinaCompatible = manifest.divinaPages.isNotEmpty(), - extension = MediaExtensionEpub( - toc = manifest.toc, - landmarks = manifest.landmarks, - pageList = manifest.pageList, - isFixedLayout = manifest.isFixedLayout, - positions = manifest.positions, - ), + extension = + MediaExtensionEpub( + toc = manifest.toc, + landmarks = manifest.landmarks, + pageList = manifest.pageList, + isFixedLayout = manifest.isFixedLayout, + positions = manifest.positions, + ), comment = entriesErrorSummary, ) } - private fun analyzePdf(book: Book, analyzeDimensions: Boolean): Media { + private fun analyzePdf( + book: Book, + analyzeDimensions: Boolean, + ): Media { val pages = pdfExtractor.getPages(book.path, analyzeDimensions).map { BookPage(it.name, "", it.dimension) } return Media(status = Media.Status.READY, pages = pages) } @@ -167,9 +187,10 @@ class BookAnalyzer( throw MediaNotReadyException() } - val thumbnail = getPoster(book)?.let { cover -> - imageConverter.resizeImageToByteArray(cover.bytes, thumbnailType, komgaSettingsProvider.thumbnailSize.maxEdge) - } ?: throw NoThumbnailFoundException() + val thumbnail = + getPoster(book)?.let { cover -> + imageConverter.resizeImageToByteArray(cover.bytes, thumbnailType, komgaSettingsProvider.thumbnailSize.maxEdge) + } ?: throw NoThumbnailFoundException() return ThumbnailBook( thumbnail = thumbnail, @@ -181,24 +202,29 @@ class BookAnalyzer( ) } - fun getPoster(book: BookWithMedia): TypedBytes? = when (book.media.profile) { - MediaProfile.DIVINA -> divinaExtractors[book.media.mediaType]?.getEntryStream(book.book.path, book.media.pages.first().fileName)?.let { - TypedBytes( - it, - book.media.pages.first().mediaType, - ) - } + fun getPoster(book: BookWithMedia): TypedBytes? = + when (book.media.profile) { + MediaProfile.DIVINA -> + divinaExtractors[book.media.mediaType]?.getEntryStream(book.book.path, book.media.pages.first().fileName)?.let { + TypedBytes( + it, + book.media.pages.first().mediaType, + ) + } - MediaProfile.PDF -> pdfExtractor.getPageContentAsImage(book.book.path, 1) - MediaProfile.EPUB -> epubExtractor.getCover(book.book.path) - null -> null - } + MediaProfile.PDF -> pdfExtractor.getPageContentAsImage(book.book.path, 1) + MediaProfile.EPUB -> epubExtractor.getCover(book.book.path) + null -> null + } @Throws( MediaNotReadyException::class, IndexOutOfBoundsException::class, ) - fun getPageContent(book: BookWithMedia, number: Int): ByteArray { + fun getPageContent( + book: BookWithMedia, + number: Int, + ): ByteArray { logger.debug { "Get page #$number for book: $book" } if (book.media.status != Media.Status.READY) { @@ -215,8 +241,10 @@ class BookAnalyzer( MediaProfile.DIVINA -> divinaExtractors.getValue(book.media.mediaType!!).getEntryStream(book.book.path, book.media.pages[number - 1].fileName) MediaProfile.PDF -> pdfExtractor.getPageContentAsImage(book.book.path, number).bytes MediaProfile.EPUB -> - if (book.media.epubDivinaCompatible) epubExtractor.getEntryStream(book.book.path, book.media.pages[number - 1].fileName) - else throw MediaUnsupportedException("Epub profile does not support getting page content") + if (book.media.epubDivinaCompatible) + epubExtractor.getEntryStream(book.book.path, book.media.pages[number - 1].fileName) + else + throw MediaUnsupportedException("Epub profile does not support getting page content") null -> throw MediaNotReadyException() } @@ -226,7 +254,10 @@ class BookAnalyzer( MediaNotReadyException::class, IndexOutOfBoundsException::class, ) - fun getPageContentRaw(book: BookWithMedia, number: Int): TypedBytes { + fun getPageContentRaw( + book: BookWithMedia, + number: Int, + ): TypedBytes { logger.debug { "Get raw page #$number for book: $book" } if (book.media.profile != MediaProfile.PDF) throw MediaUnsupportedException("Extractor does not support raw extraction of pages") @@ -246,7 +277,10 @@ class BookAnalyzer( @Throws( MediaNotReadyException::class, ) - fun getFileContent(book: BookWithMedia, fileName: String): ByteArray { + fun getFileContent( + book: BookWithMedia, + fileName: String, + ): ByteArray { logger.debug { "Get file $fileName for book: $book" } if (book.media.status != Media.Status.READY) { @@ -268,13 +302,16 @@ class BookAnalyzer( * See [org.gotson.komga.infrastructure.configuration.KomgaProperties.pageHashing] */ fun hashPages(book: BookWithMedia): Media { - val hashedPages = book.media.pages.mapIndexed { index, bookPage -> - if (bookPage.fileHash.isBlank() && (index < pageHashing || index >= (book.media.pageCount - pageHashing))) { - val content = getPageContent(book, index + 1) - val hash = hashPage(bookPage, content) - bookPage.copy(fileHash = hash) - } else bookPage - } + val hashedPages = + book.media.pages.mapIndexed { index, bookPage -> + if (bookPage.fileHash.isBlank() && (index < pageHashing || index >= (book.media.pageCount - pageHashing))) { + val content = getPageContent(book, index + 1) + val hash = hashPage(bookPage, content) + bookPage.copy(fileHash = hash) + } else { + bookPage + } + } return book.media.copy(pages = hashedPages) } @@ -284,7 +321,10 @@ class BookAnalyzer( * * For JPEG, the image is read/written to remove the metadata. */ - fun hashPage(page: BookPage, content: ByteArray): String { + fun hashPage( + page: BookPage, + content: ByteArray, + ): String { val bytes = if (page.mediaType == ImageType.JPEG.mediaType) { // JPEG could contain different EXIF data, reading and writing back the image will get rid of it @@ -292,7 +332,9 @@ class BookAnalyzer( ImageIO.write(ImageIO.read(content.inputStream()), ImageType.JPEG.imageIOFormat, buffer) buffer.toByteArray() } - } else content + } else { + content + } return hasher.computeHash(bytes.inputStream()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt index c66ed8746..4dc08cbba 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookConverter.kt @@ -49,7 +49,6 @@ class BookConverter( private val eventPublisher: ApplicationEventPublisher, private val historicalEventRepository: HistoricalEventRepository, ) { - private val convertibleTypes = listOf(MediaType.RAR_4.type, MediaType.RAR_5.type) private val mediaTypeToExtension = @@ -60,10 +59,10 @@ class BookConverter( private val skippedRepairs = mutableListOf() fun getConvertibleBooks(library: Library): Collection = - if (library.convertToCbz) + if (library.convertToCbz) { bookRepository.findAllByLibraryIdAndMediaTypes(library.id, convertibleTypes) .also { logger.info { "Found ${it.size} books to convert" } } - else { + } else { logger.info { "CBZ conversion is not enabled, skipping" } emptyList() } @@ -111,13 +110,14 @@ class BookConverter( } // perform checks on new file - val convertedBook = fileSystemScanner.scanFile(destinationPath) - ?.copy( - id = book.id, - seriesId = book.seriesId, - libraryId = book.libraryId, - ) - ?: throw IllegalStateException("Newly converted book could not be scanned: $destinationFilename") + val convertedBook = + fileSystemScanner.scanFile(destinationPath) + ?.copy( + id = book.id, + seriesId = book.seriesId, + libraryId = book.libraryId, + ) + ?: throw IllegalStateException("Newly converted book could not be scanned: $destinationFilename") val convertedMedia = bookAnalyzer.analyze(convertedBook, libraryRepository.findById(book.libraryId).analyzeDimensions) @@ -200,13 +200,14 @@ class BookConverter( logger.info { "Renaming ${book.path} to $destinationPath" } book.path.moveTo(destinationPath) - val repairedBook = fileSystemScanner.scanFile(destinationPath) - ?.copy( - id = book.id, - seriesId = book.seriesId, - libraryId = book.libraryId, - ) - ?: throw IllegalStateException("Repaired book could not be scanned: $destinationFilename") + val repairedBook = + fileSystemScanner.scanFile(destinationPath) + ?.copy( + id = book.id, + seriesId = book.seriesId, + libraryId = book.libraryId, + ) + ?: throw IllegalStateException("Repaired book could not be scanned: $destinationFilename") bookRepository.update(repairedBook) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt index 7a7dc02bc..ea01ef6cf 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookImporter.kt @@ -60,8 +60,13 @@ class BookImporter( private val taskEmitter: TaskEmitter, private val historicalEventRepository: HistoricalEventRepository, ) { - - fun importBook(sourceFile: Path, series: Series, copyMode: CopyMode, destinationName: String? = null, upgradeBookId: String? = null): Book { + fun importBook( + sourceFile: Path, + series: Series, + copyMode: CopyMode, + destinationName: String? = null, + upgradeBookId: String? = null, + ): Book { try { if (sourceFile.notExists()) throw FileNotFoundException("File not found: $sourceFile").withCode("ERR_1018") if (series.oneshot) throw IllegalArgumentException("Destination series is oneshot") @@ -70,23 +75,31 @@ class BookImporter( if (sourceFile.startsWith(library.path)) throw PathContainedInPath("Cannot import file that is part of an existing library", "ERR_1019") } - val destFile = series.path.resolve( - if (destinationName != null) Paths.get("$destinationName.${sourceFile.extension}").name - else sourceFile.name, - ) - val sidecars = fileSystemScanner.scanBookSidecars(sourceFile).associateWith { + val destFile = series.path.resolve( - if (destinationName != null) it.url.toURI().toPath().name.replace(sourceFile.nameWithoutExtension, destinationName, true) - else it.url.toURI().toPath().name, + if (destinationName != null) + Paths.get("$destinationName.${sourceFile.extension}").name + else + sourceFile.name, ) - } + val sidecars = + fileSystemScanner.scanBookSidecars(sourceFile).associateWith { + series.path.resolve( + if (destinationName != null) + it.url.toURI().toPath().name.replace(sourceFile.nameWithoutExtension, destinationName, true) + else + it.url.toURI().toPath().name, + ) + } val bookToUpgrade = if (upgradeBookId != null) { bookRepository.findByIdOrNull(upgradeBookId)?.also { if (it.seriesId != series.id) throw IllegalArgumentException("Book to upgrade ($upgradeBookId) does not belong to series: $series").withCode("ERR_1020") } - } else null + } else { + null + } var deletedUpgradedFile = false when { @@ -135,28 +148,30 @@ class BookImporter( } } - CopyMode.HARDLINK -> try { - logger.info { "Hardlink file $sourceFile to $destFile" } - Files.createLink(destFile, sourceFile) - sidecars.forEach { - it.key.url.toURI().toPath().let { sourcePath -> - logger.info { "Hardlink file $sourcePath to ${it.value}" } - it.value.deleteIfExists() - Files.createLink(it.value, sourcePath) + CopyMode.HARDLINK -> + try { + logger.info { "Hardlink file $sourceFile to $destFile" } + Files.createLink(destFile, sourceFile) + sidecars.forEach { + it.key.url.toURI().toPath().let { sourcePath -> + logger.info { "Hardlink file $sourcePath to ${it.value}" } + it.value.deleteIfExists() + Files.createLink(it.value, sourcePath) + } + } + } catch (e: Exception) { + logger.warn(e) { "Filesystem does not support hardlinks, copying instead" } + sourceFile.copyTo(destFile) + sidecars.forEach { + it.key.url.toURI().toPath().copyTo(it.value, true) } } - } catch (e: Exception) { - logger.warn(e) { "Filesystem does not support hardlinks, copying instead" } - sourceFile.copyTo(destFile) - sidecars.forEach { - it.key.url.toURI().toPath().copyTo(it.value, true) - } - } } - val importedBook = fileSystemScanner.scanFile(destFile) - ?.copy(libraryId = series.libraryId) - ?: throw IllegalStateException("Newly imported book could not be scanned: $destFile").withCode("ERR_1022") + val importedBook = + fileSystemScanner.scanFile(destFile) + ?.copy(libraryId = series.libraryId) + ?: throw IllegalStateException("Newly imported book could not be scanned: $destFile").withCode("ERR_1022") seriesLifecycle.addBooks(series, listOf(importedBook)) @@ -208,11 +223,12 @@ class BookImporter( Sidecar.Type.ARTWORK -> taskEmitter.refreshBookLocalArtwork(importedBook) Sidecar.Type.METADATA -> taskEmitter.refreshBookMetadata(importedBook) } - val destSidecar = sourceSidecar.copy( - url = destPath.toUri().toURL(), - parentUrl = destPath.parent.toUri().toURL(), - lastModifiedTime = destPath.readAttributes().getUpdatedTime(), - ) + val destSidecar = + sourceSidecar.copy( + url = destPath.toUri().toURL(), + parentUrl = destPath.parent.toUri().toURL(), + lastModifiedTime = destPath.readAttributes().getUpdatedTime(), + ) sidecarRepository.save(importedBook.libraryId, destSidecar) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt index d3ac56ba1..74c056a36 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookLifecycle.kt @@ -69,7 +69,6 @@ class BookLifecycle( @Qualifier("pdfImageType") private val pdfImageType: ImageType, ) { - private val resizeTargetFormat = ImageType.JPEG fun analyzeAndPersist(book: Book): Set { @@ -80,8 +79,9 @@ class BookLifecycle( // if the number of pages has changed, delete all read progress for that book mediaRepository.findById(book.id).let { previous -> if (previous.status == Media.Status.OUTDATED && previous.pageCount != media.pageCount) { - val adjustedProgress = readProgressRepository.findAllByBookId(book.id) - .map { it.copy(page = if (it.completed) media.pageCount else 1) } + val adjustedProgress = + readProgressRepository.findAllByBookId(book.id) + .map { it.copy(page = if (it.completed) media.pageCount else 1) } if (adjustedProgress.isNotEmpty()) { logger.info { "Number of pages differ, adjust read progress for book" } readProgressRepository.save(adjustedProgress) @@ -130,7 +130,10 @@ class BookLifecycle( } } - fun addThumbnailForBook(thumbnail: ThumbnailBook, markSelected: MarkSelectedPreference): ThumbnailBook { + fun addThumbnailForBook( + thumbnail: ThumbnailBook, + markSelected: MarkSelectedPreference, + ): ThumbnailBook { when (thumbnail.type) { ThumbnailBook.Type.GENERATED -> { // only one generated thumbnail is allowed @@ -153,18 +156,21 @@ class BookLifecycle( } } - val selected = when (markSelected) { - MarkSelectedPreference.YES -> true - MarkSelectedPreference.IF_NONE_OR_GENERATED -> { - val selectedThumbnail = thumbnailBookRepository.findSelectedByBookIdOrNull(thumbnail.bookId) - selectedThumbnail == null || selectedThumbnail.type == ThumbnailBook.Type.GENERATED + val selected = + when (markSelected) { + MarkSelectedPreference.YES -> true + MarkSelectedPreference.IF_NONE_OR_GENERATED -> { + val selectedThumbnail = thumbnailBookRepository.findSelectedByBookIdOrNull(thumbnail.bookId) + selectedThumbnail == null || selectedThumbnail.type == ThumbnailBook.Type.GENERATED + } + + MarkSelectedPreference.NO -> false } - MarkSelectedPreference.NO -> false - } - - if (selected) thumbnailBookRepository.markSelected(thumbnail) - else thumbnailsHouseKeeping(thumbnail.bookId) + if (selected) + thumbnailBookRepository.markSelected(thumbnail) + else + thumbnailsHouseKeeping(thumbnail.bookId) val newThumbnail = thumbnail.copy(selected = selected) eventPublisher.publishEvent(DomainEvent.ThumbnailBookAdded(newThumbnail)) @@ -189,13 +195,17 @@ class BookLifecycle( return selected } - fun getThumbnailBytes(bookId: String, resizeTo: Int? = null): TypedBytes? { + fun getThumbnailBytes( + bookId: String, + resizeTo: Int? = null, + ): TypedBytes? { getThumbnail(bookId)?.let { - val thumbnailBytes = when { - it.thumbnail != null -> it.thumbnail - it.url != null -> File(it.url.toURI()).readBytes() - else -> return null - } + val thumbnailBytes = + when { + it.thumbnail != null -> it.thumbnail + it.url != null -> File(it.url.toURI()).readBytes() + else -> return null + } if (resizeTo != null) { try { @@ -238,14 +248,17 @@ class BookLifecycle( private fun thumbnailsHouseKeeping(bookId: String) { logger.info { "House keeping thumbnails for book: $bookId" } - val all = thumbnailBookRepository.findAllByBookId(bookId) - .mapNotNull { - if (!it.exists()) { - logger.warn { "Thumbnail doesn't exist, removing entry" } - thumbnailBookRepository.delete(it.id) - null - } else it - } + val all = + thumbnailBookRepository.findAllByBookId(bookId) + .mapNotNull { + if (!it.exists()) { + logger.warn { "Thumbnail doesn't exist, removing entry" } + thumbnailBookRepository.delete(it.id) + null + } else { + it + } + } val selected = all.filter { it.selected } when { @@ -274,20 +287,28 @@ class BookLifecycle( MediaNotReadyException::class, IndexOutOfBoundsException::class, ) - fun getBookPage(book: Book, number: Int, convertTo: ImageType? = null, resizeTo: Int? = null): TypedBytes { + fun getBookPage( + book: Book, + number: Int, + convertTo: ImageType? = null, + resizeTo: Int? = null, + ): TypedBytes { val media = mediaRepository.findById(book.id) val pageContent = bookAnalyzer.getPageContent(BookWithMedia(book, media), number) val pageMediaType = - if (media.profile == MediaProfile.PDF) pdfImageType.mediaType - else media.pages[number - 1].mediaType + if (media.profile == MediaProfile.PDF) + pdfImageType.mediaType + else + media.pages[number - 1].mediaType if (resizeTo != null) { - val convertedPage = try { - imageConverter.resizeImageToByteArray(pageContent, resizeTargetFormat, resizeTo) - } catch (e: Exception) { - logger.error(e) { "Resize page #$number of book $book to $resizeTo: failed" } - throw e - } + val convertedPage = + try { + imageConverter.resizeImageToByteArray(pageContent, resizeTargetFormat, resizeTo) + } catch (e: Exception) { + logger.error(e) { "Resize page #$number of book $book to $resizeTo: failed" } + throw e + } return TypedBytes(convertedPage, resizeTargetFormat.mediaType) } else { convertTo?.let { @@ -304,12 +325,13 @@ class BookLifecycle( } logger.info { msg } - val convertedPage = try { - imageConverter.convertImage(pageContent, it.imageIOFormat) - } catch (e: Exception) { - logger.error(e) { "$msg: conversion failed" } - throw e - } + val convertedPage = + try { + imageConverter.convertImage(pageContent, it.imageIOFormat) + } catch (e: Exception) { + logger.error(e) { "$msg: conversion failed" } + throw e + } return TypedBytes(convertedPage, it.mediaType) } @@ -360,7 +382,11 @@ class BookLifecycle( books.forEach { eventPublisher.publishEvent(DomainEvent.BookDeleted(it)) } } - fun markReadProgress(book: Book, user: KomgaUser, page: Int) { + fun markReadProgress( + book: Book, + user: KomgaUser, + page: Int, + ) { val pages = mediaRepository.getPagesSize(book.id) require(page in 1..pages) { "Page argument ($page) must be within 1 and book page count ($pages)" } @@ -369,7 +395,10 @@ class BookLifecycle( eventPublisher.publishEvent(DomainEvent.ReadProgressChanged(progress)) } - fun markReadProgressCompleted(bookId: String, user: KomgaUser) { + fun markReadProgressCompleted( + bookId: String, + user: KomgaUser, + ) { val media = mediaRepository.findById(bookId) val progress = ReadProgress(bookId, user.id, media.pageCount, true) @@ -377,74 +406,84 @@ class BookLifecycle( eventPublisher.publishEvent(DomainEvent.ReadProgressChanged(progress)) } - fun deleteReadProgress(book: Book, user: KomgaUser) { + fun deleteReadProgress( + book: Book, + user: KomgaUser, + ) { readProgressRepository.findByBookIdAndUserIdOrNull(book.id, user.id)?.let { progress -> readProgressRepository.delete(book.id, user.id) eventPublisher.publishEvent(DomainEvent.ReadProgressDeleted(progress)) } } - fun markProgression(book: Book, user: KomgaUser, newProgression: R2Progression) { + fun markProgression( + book: Book, + user: KomgaUser, + newProgression: R2Progression, + ) { readProgressRepository.findByBookIdAndUserIdOrNull(book.id, user.id)?.let { savedProgress -> check(newProgression.modified.toLocalDateTime().toCurrentTimeZone().isAfter(savedProgress.readDate)) { "Progression is older than existing" } } val media = mediaRepository.findById(book.id) requireNotNull(media.profile) { "Media has no profile" } - val progress = when (media.profile!!) { - MediaProfile.DIVINA, - MediaProfile.PDF, - -> { - require(newProgression.locator.locations?.position in 1..media.pageCount) { "Page argument (${newProgression.locator.locations?.position}) must be within 1 and book page count (${media.pageCount})" } - ReadProgress( - book.id, - user.id, - newProgression.locator.locations!!.position!!, - newProgression.locator.locations.position == media.pageCount, - newProgression.modified.toLocalDateTime().toCurrentTimeZone(), - newProgression.device.id, - newProgression.device.name, - newProgression.locator, - ) + val progress = + when (media.profile!!) { + MediaProfile.DIVINA, + MediaProfile.PDF, + -> { + require(newProgression.locator.locations?.position in 1..media.pageCount) { "Page argument (${newProgression.locator.locations?.position}) must be within 1 and book page count (${media.pageCount})" } + ReadProgress( + book.id, + user.id, + newProgression.locator.locations!!.position!!, + newProgression.locator.locations.position == media.pageCount, + newProgression.modified.toLocalDateTime().toCurrentTimeZone(), + newProgression.device.id, + newProgression.device.name, + newProgression.locator, + ) + } + + MediaProfile.EPUB -> { + val href = + newProgression.locator.href + .replaceBefore("/resource/", "").removePrefix("/resource/") + .replaceAfter("#", "").removeSuffix("#") + .let { UriUtils.decode(it, Charsets.UTF_8) } + require(href in media.files.map { it.fileName }) { "Resource does not exist in book: $href" } + requireNotNull(newProgression.locator.locations?.progression) { "location.progression is required" } + + val extension = + mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub + ?: throw IllegalArgumentException("Epub extension not found") + // match progression with positions + val matchingPositions = extension.positions.filter { it.href == href } + val matchedPosition = + matchingPositions.firstOrNull { it.locations!!.progression == newProgression.locator.locations!!.progression } + ?: run { + // no exact match + val before = matchingPositions.filter { it.locations!!.progression!! < newProgression.locator.locations!!.progression!! }.maxByOrNull { it.locations!!.position!! } + val after = matchingPositions.filter { it.locations!!.progression!! > newProgression.locator.locations!!.progression!! }.minByOrNull { it.locations!!.position!! } + if (before == null || after == null || before.locations!!.position!! > after.locations!!.position!!) + throw IllegalArgumentException("Invalid progression") + before + } + + val totalProgression = matchedPosition.locations?.totalProgression + ReadProgress( + book.id, + user.id, + totalProgression?.let { (media.pageCount * it).roundToInt() } ?: 0, + totalProgression?.let { it >= 0.99F } ?: false, + newProgression.modified.toLocalDateTime().toCurrentTimeZone(), + newProgression.device.id, + newProgression.device.name, + newProgression.locator, + ) + } } - MediaProfile.EPUB -> { - val href = newProgression.locator.href - .replaceBefore("/resource/", "").removePrefix("/resource/") - .replaceAfter("#", "").removeSuffix("#") - .let { UriUtils.decode(it, Charsets.UTF_8) } - require(href in media.files.map { it.fileName }) { "Resource does not exist in book: $href" } - requireNotNull(newProgression.locator.locations?.progression) { "location.progression is required" } - - val extension = mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub - ?: throw IllegalArgumentException("Epub extension not found") - // match progression with positions - val matchingPositions = extension.positions.filter { it.href == href } - val matchedPosition = - matchingPositions.firstOrNull { it.locations!!.progression == newProgression.locator.locations!!.progression } - ?: run { - // no exact match - val before = matchingPositions.filter { it.locations!!.progression!! < newProgression.locator.locations!!.progression!! }.maxByOrNull { it.locations!!.position!! } - val after = matchingPositions.filter { it.locations!!.progression!! > newProgression.locator.locations!!.progression!! }.minByOrNull { it.locations!!.position!! } - if (before == null || after == null || before.locations!!.position!! > after.locations!!.position!!) - throw IllegalArgumentException("Invalid progression") - before - } - - val totalProgression = matchedPosition.locations?.totalProgression - ReadProgress( - book.id, - user.id, - totalProgression?.let { (media.pageCount * it).roundToInt() } ?: 0, - totalProgression?.let { it >= 0.99F } ?: false, - newProgression.modified.toLocalDateTime().toCurrentTimeZone(), - newProgression.device.id, - newProgression.device.name, - newProgression.locator, - ) - } - } - readProgressRepository.save(progress) eventPublisher.publishEvent(DomainEvent.ReadProgressChanged(progress)) } @@ -453,9 +492,10 @@ class BookLifecycle( if (book.path.notExists()) return logger.info { "Cannot delete book file, path does not exist: ${book.path}" } if (!book.path.isWritable()) return logger.info { "Cannot delete book file, path is not writable: ${book.path}" } - val thumbnails = thumbnailBookRepository.findAllByBookIdAndType(book.id, ThumbnailBook.Type.SIDECAR) - .mapNotNull { it.url?.toURI()?.toPath() } - .filter { it.exists() && it.isWritable() } + val thumbnails = + thumbnailBookRepository.findAllByBookIdAndType(book.id, ThumbnailBook.Type.SIDECAR) + .mapNotNull { it.url?.toURI()?.toPath() } + .filter { it.exists() && it.isWritable() } if (book.path.deleteIfExists()) { logger.info { "Deleted file: ${book.path}" } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookMetadataLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookMetadataLifecycle.kt index ed4758230..8b1576fb8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookMetadataLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookMetadataLifecycle.kt @@ -26,8 +26,10 @@ class BookMetadataLifecycle( private val readListLifecycle: ReadListLifecycle, private val eventPublisher: ApplicationEventPublisher, ) { - - fun refreshMetadata(book: Book, capabilities: Set) { + fun refreshMetadata( + book: Book, + capabilities: Set, + ) { logger.info { "Refresh metadata for book: $book with capabilities: $capabilities" } val media = mediaRepository.findById(book.id) @@ -44,12 +46,13 @@ class BookMetadataLifecycle( else -> { logger.debug { "Provider: ${provider.javaClass.simpleName}" } - val patch = try { - provider.getBookMetadataFromBook(BookWithMedia(book, media)) - } catch (e: Exception) { - logger.error(e) { "Error while getting metadata from ${provider.javaClass.simpleName} for book: $book" } - null - } + val patch = + try { + provider.getBookMetadataFromBook(BookWithMedia(book, media)) + } catch (e: Exception) { + logger.error(e) { "Error while getting metadata from ${provider.javaClass.simpleName} for book: $book" } + null + } if (provider.shouldLibraryHandlePatch(library, MetadataPatchTarget.BOOK)) { handlePatchForBookMetadata(patch, book) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookPageEditor.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookPageEditor.kt index 0e6719fa2..eaf275d87 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookPageEditor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookPageEditor.kt @@ -52,7 +52,10 @@ class BookPageEditor( private val failedPageRemoval = mutableListOf() - fun removeHashedPages(book: Book, pagesToDelete: Collection): BookAction? { + fun removeHashedPages( + book: Book, + pagesToDelete: Collection, + ): BookAction? { // perform various checks if (failedPageRemoval.contains(book.id)) { logger.info { "Book page removal already failed before, skipping" } @@ -75,14 +78,15 @@ class BookPageEditor( throw MediaNotReadyException() // create a temp file with the pages removed - val pagesToKeep = media.pages.filterIndexed { index, page -> - pagesToDelete.find { candidate -> - candidate.fileHash == page.fileHash && - candidate.mediaType == page.mediaType && - candidate.fileName == page.fileName && - candidate.pageNumber == index + 1 - } == null - } + val pagesToKeep = + media.pages.filterIndexed { index, page -> + pagesToDelete.find { candidate -> + candidate.fileHash == page.fileHash && + candidate.mediaType == page.mediaType && + candidate.fileName == page.fileName && + candidate.pageNumber == index + 1 + } == null + } if (media.pages.size != (pagesToKeep.size + pagesToDelete.size)) { logger.info { "Should be removing ${pagesToDelete.size} pages from book, but count doesn't add up, skipping" } return null @@ -109,13 +113,14 @@ class BookPageEditor( } // perform checks on new file - val createdBook = fileSystemScanner.scanFile(tempFile) - ?.copy( - id = book.id, - seriesId = book.seriesId, - libraryId = book.libraryId, - ) - ?: throw IllegalStateException("Newly created book could not be scanned: $tempFile") + val createdBook = + fileSystemScanner.scanFile(tempFile) + ?.copy( + id = book.id, + seriesId = book.seriesId, + libraryId = book.libraryId, + ) + ?: throw IllegalStateException("Newly created book could not be scanned: $tempFile") val createdMedia = bookAnalyzer.analyze(createdBook, libraryRepository.findById(book.libraryId).analyzeDimensions) @@ -142,13 +147,14 @@ class BookPageEditor( } tempFile.moveTo(book.path, true) - val newBook = fileSystemScanner.scanFile(book.path) - ?.copy( - id = book.id, - seriesId = book.seriesId, - libraryId = book.libraryId, - ) - ?: throw IllegalStateException("Newly created book could not be scanned after replacing existing one: ${book.path}") + val newBook = + fileSystemScanner.scanFile(book.path) + ?.copy( + id = book.id, + seriesId = book.seriesId, + libraryId = book.libraryId, + ) + ?: throw IllegalStateException("Newly created book could not be scanned after replacing existing one: ${book.path}") val mediaWithHashes = createdMedia.copy(pages = createdMedia.pages.restoreHashFrom(media.pages)) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt index 0ff86fbd3..8c586e0ec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/FileSystemScanner.kt @@ -36,7 +36,6 @@ class FileSystemScanner( private val sidecarBookConsumers: List, private val sidecarSeriesConsumers: List, ) { - private data class TempSidecar( val name: String, val url: URL, @@ -55,11 +54,12 @@ class FileSystemScanner( scanEpub: Boolean = true, directoryExclusions: Set = emptySet(), ): ScanResult { - val scanForExtensions = buildList { - if (scanCbx) addAll(listOf("cbz", "zip", "cbr", "rar")) - if (scanPdf) add("pdf") - if (scanEpub) add("epub") - } + val scanForExtensions = + buildList { + if (scanCbx) addAll(listOf("cbz", "zip", "cbr", "rar")) + if (scanPdf) add("pdf") + if (scanEpub) add("epub") + } logger.info { "Scanning folder: $root" } logger.info { "Scan for extensions: $scanForExtensions" } logger.info { "Excluded directory patterns: $directoryExclusions" } @@ -84,24 +84,32 @@ class FileSystemScanner( setOf(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, object : FileVisitor { - override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { + override fun preVisitDirectory( + dir: Path, + attrs: BasicFileAttributes, + ): FileVisitResult { logger.trace { "preVisit: $dir (regularFile:${attrs.isRegularFile}, directory:${attrs.isDirectory}, symbolicLink:${attrs.isSymbolicLink}, other:${attrs.isOther})" } if (dir.name.startsWith(".") || directoryExclusions.any { exclude -> dir.pathString.contains(exclude, true) } - ) return FileVisitResult.SKIP_SUBTREE - - pathToSeries[dir] = Series( - name = dir.name.ifBlank { dir.pathString }, - url = dir.toUri().toURL(), - fileLastModified = attrs.getUpdatedTime(), ) + return FileVisitResult.SKIP_SUBTREE + + pathToSeries[dir] = + Series( + name = dir.name.ifBlank { dir.pathString }, + url = dir.toUri().toURL(), + fileLastModified = attrs.getUpdatedTime(), + ) return FileVisitResult.CONTINUE } - override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + override fun visitFile( + file: Path, + attrs: BasicFileAttributes, + ): FileVisitResult { logger.trace { "visitFile: $file (regularFile:${attrs.isRegularFile}, directory:${attrs.isDirectory}, symbolicLink:${attrs.isSymbolicLink}, other:${attrs.isOther})" } if (!attrs.isSymbolicLink && !attrs.isDirectory) { if (scanForExtensions.contains(file.extension.lowercase()) && @@ -131,24 +139,31 @@ class FileSystemScanner( return FileVisitResult.CONTINUE } - override fun visitFileFailed(file: Path?, exc: IOException?): FileVisitResult { + override fun visitFileFailed( + file: Path?, + exc: IOException?, + ): FileVisitResult { logger.warn { "Could not access: $file" } return FileVisitResult.SKIP_SUBTREE } - override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult { + override fun postVisitDirectory( + dir: Path, + exc: IOException?, + ): FileVisitResult { logger.trace { "postVisit: $dir" } val books = pathToBooks[dir] val tempSeries = pathToSeries[dir] if (!books.isNullOrEmpty() && tempSeries !== null) { if (!oneshotsDir.isNullOrBlank() && dir.pathString.contains(oneshotsDir, true)) { books.forEach { book -> - val series = Series( - name = book.name, - url = book.url, - fileLastModified = book.fileLastModified, - oneshot = true, - ) + val series = + Series( + name = book.name, + url = book.url, + fileLastModified = book.fileLastModified, + oneshot = true, + ) scannedSeries[series] = listOf(book.copy(oneshot = true)) } } else { @@ -166,12 +181,13 @@ class FileSystemScanner( // book sidecars are matched here, with the actual list of books books.forEach { book -> - val sidecars = pathToBookSidecars[dir] - ?.mapNotNull { sidecar -> - sidecarBookConsumers.firstOrNull { it.isSidecarBookMatch(book.name, sidecar.name) }?.let { - sidecar to it.getSidecarBookType() - } - }?.toMap() ?: emptyMap() + val sidecars = + pathToBookSidecars[dir] + ?.mapNotNull { sidecar -> + sidecarBookConsumers.firstOrNull { it.isSidecarBookMatch(book.name, sidecar.name) }?.let { + sidecar to it.getSidecarBookType() + } + }?.toMap() ?: emptyMap() pathToBookSidecars[dir]?.minusAssign(sidecars.keys) sidecars.mapTo(scannedSidecars) { (sidecar, type) -> @@ -210,7 +226,10 @@ class FileSystemScanner( } } - private fun pathToBook(path: Path, attrs: BasicFileAttributes): Book = + private fun pathToBook( + path: Path, + attrs: BasicFileAttributes, + ): Book = Book( name = path.nameWithoutExtension, url = path.toUri().toURL(), diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt index 76c833622..09f2698fb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/KomgaUserLifecycle.kt @@ -26,8 +26,11 @@ class KomgaUserLifecycle( private val transactionTemplate: TransactionTemplate, private val eventPublisher: ApplicationEventPublisher, ) { - - fun updatePassword(user: KomgaUser, newPassword: String, expireSessions: Boolean) { + fun updatePassword( + user: KomgaUser, + newPassword: String, + expireSessions: Boolean, + ) { logger.info { "Changing password for user ${user.email}" } val updatedUser = user.copy(password = passwordEncoder.encode(newPassword)) userRepository.update(updatedUser) @@ -45,10 +48,11 @@ class KomgaUserLifecycle( logger.info { "Update user: $toUpdate" } userRepository.update(toUpdate) - val expireSessions = existing.roles != user.roles || - existing.restrictions != user.restrictions || - existing.sharedAllLibraries != user.sharedAllLibraries || - existing.sharedLibrariesIds != user.sharedLibrariesIds + val expireSessions = + existing.roles != user.roles || + existing.restrictions != user.restrictions || + existing.sharedAllLibraries != user.sharedAllLibraries || + existing.sharedLibrariesIds != user.sharedLibrariesIds if (expireSessions) expireSessions(toUpdate) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt index 41673b68d..3752108c2 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycle.kt @@ -61,27 +61,30 @@ class LibraryContentLifecycle( private val thumbnailBookRepository: ThumbnailBookRepository, private val eventPublisher: ApplicationEventPublisher, ) { - - fun scanRootFolder(library: Library, scanDeep: Boolean = false) { + fun scanRootFolder( + library: Library, + scanDeep: Boolean = false, + ) { logger.info { "Scan root folder for library: $library" } measureTime { - val scanResult = try { - fileSystemScanner.scanRootFolder( - Paths.get(library.root.toURI()), - library.scanForceModifiedTime, - library.oneshotsDirectory, - library.scanCbx, - library.scanPdf, - library.scanEpub, - library.scanDirectoryExclusions, - ) - } catch (e: DirectoryNotFoundException) { - library.copy(unavailableDate = LocalDateTime.now()).let { - libraryRepository.update(it) - eventPublisher.publishEvent(DomainEvent.LibraryUpdated(it)) + val scanResult = + try { + fileSystemScanner.scanRootFolder( + Paths.get(library.root.toURI()), + library.scanForceModifiedTime, + library.oneshotsDirectory, + library.scanCbx, + library.scanPdf, + library.scanEpub, + library.scanDirectoryExclusions, + ) + } catch (e: DirectoryNotFoundException) { + library.copy(unavailableDate = LocalDateTime.now()).let { + libraryRepository.update(it) + eventPublisher.publishEvent(DomainEvent.LibraryUpdated(it)) + } + throw e } - throw e - } if (library.unavailableDate != null) { library.copy(unavailableDate = null).let { @@ -113,14 +116,17 @@ class LibraryContentLifecycle( } // delete books that don't exist anymore. We need to do this now, so trash bin can work - val seriesToSortAndRefresh = scannedSeries.values.flatten().map { it.url }.let { urls -> - val books = bookRepository.findAllNotDeletedByLibraryIdAndUrlNotIn(library.id, urls) - if (books.isNotEmpty()) { - logger.info { "Soft deleting books not on disk anymore: $books" } - bookLifecycle.softDeleteMany(books) - books.map { it.seriesId }.distinct().mapNotNull { seriesRepository.findByIdOrNull(it) }.toMutableList() - } else mutableListOf() - } + val seriesToSortAndRefresh = + scannedSeries.values.flatten().map { it.url }.let { urls -> + val books = bookRepository.findAllNotDeletedByLibraryIdAndUrlNotIn(library.id, urls) + if (books.isNotEmpty()) { + logger.info { "Soft deleting books not on disk anymore: $books" } + bookLifecycle.softDeleteMany(books) + books.map { it.seriesId }.distinct().mapNotNull { seriesRepository.findByIdOrNull(it) }.toMutableList() + } else { + mutableListOf() + } + } // we store the url of all the series that had deleted books // this can be used to detect changed series even if their file modified date did not change, for example because of NFS/SMB cache val seriesUrlWithDeletedBooks = seriesToSortAndRefresh.map { it.url } @@ -155,24 +161,29 @@ class LibraryContentLifecycle( existingBooks.find { it.url == newBook.url && it.deletedDate == null }?.let { existingBook -> logger.debug { "Matched existing book: $existingBook" } if (newBook.fileLastModified.notEquals(existingBook.fileLastModified)) { - val hash = if (existingBook.fileSize == newBook.fileSize && existingBook.fileHash.isNotBlank()) { - hasher.computeHash(newBook.path) - } else null + val hash = + if (existingBook.fileSize == newBook.fileSize && existingBook.fileHash.isNotBlank()) { + hasher.computeHash(newBook.path) + } else { + null + } if (hash == existingBook.fileHash) { logger.info { "Book changed on disk, but still has the same hash, no need to reset media status: $existingBook" } - val updatedBook = existingBook.copy( - fileLastModified = newBook.fileLastModified, - fileSize = newBook.fileSize, - fileHash = hash, - ) + val updatedBook = + existingBook.copy( + fileLastModified = newBook.fileLastModified, + fileSize = newBook.fileSize, + fileHash = hash, + ) bookRepository.update(updatedBook) } else { logger.info { "Book changed on disk, update and reset media status: $existingBook" } - val updatedBook = existingBook.copy( - fileLastModified = newBook.fileLastModified, - fileSize = newBook.fileSize, - fileHash = hash ?: "", - ) + val updatedBook = + existingBook.copy( + fileLastModified = newBook.fileLastModified, + fileSize = newBook.fileSize, + fileHash = hash ?: "", + ) transactionTemplate.executeWithoutResult { mediaRepository.findById(existingBook.id).let { mediaRepository.update(it.copy(status = Media.Status.OUTDATED)) @@ -237,8 +248,10 @@ class LibraryContentLifecycle( } } - if (library.emptyTrashAfterScan) emptyTrash(library) - else cleanupEmptySets() + if (library.emptyTrashAfterScan) + emptyTrash(library) + else + cleanupEmptySets() }.also { logger.info { "Library updated in $it" } } eventPublisher.publishEvent(DomainEvent.LibraryScanned(library)) @@ -255,27 +268,34 @@ class LibraryContentLifecycle( * - Metadata. The metadata title will only be copied if locked. If not locked, the folder name is used. * - all books, via #tryRestoreBooks */ - private fun tryRestoreSeries(newSeries: Series, newBooks: List) { + private fun tryRestoreSeries( + newSeries: Series, + newBooks: List, + ) { logger.info { "Try to restore series: $newSeries" } val bookSizes = newBooks.map { it.fileSize } - val deletedCandidates = seriesRepository.findAll(SeriesSearch(deleted = true)) - .mapNotNull { deletedCandidate -> - val deletedBooks = bookRepository.findAllBySeriesId(deletedCandidate.id) - val deletedBooksSizes = deletedBooks.map { it.fileSize } - if (newBooks.size == deletedBooks.size && bookSizes.containsAll(deletedBooksSizes) && deletedBooksSizes.containsAll(bookSizes) && deletedBooks.all { it.fileHash.isNotBlank() }) { - deletedCandidate to deletedBooks - } else null - } + val deletedCandidates = + seriesRepository.findAll(SeriesSearch(deleted = true)) + .mapNotNull { deletedCandidate -> + val deletedBooks = bookRepository.findAllBySeriesId(deletedCandidate.id) + val deletedBooksSizes = deletedBooks.map { it.fileSize } + if (newBooks.size == deletedBooks.size && bookSizes.containsAll(deletedBooksSizes) && deletedBooksSizes.containsAll(bookSizes) && deletedBooks.all { it.fileHash.isNotBlank() }) { + deletedCandidate to deletedBooks + } else { + null + } + } logger.debug { "Deleted series candidates: $deletedCandidates" } if (deletedCandidates.isNotEmpty()) { val newBooksWithHash = newBooks.map { book -> bookRepository.findByIdOrNull(book.id)!!.copy(fileHash = hasher.computeHash(book.path)) } bookRepository.update(newBooksWithHash) - val match = deletedCandidates.find { (_, books) -> - books.map { it.fileHash }.containsAll(newBooksWithHash.map { it.fileHash }) && newBooksWithHash.map { it.fileHash }.containsAll(books.map { it.fileHash }) - } + val match = + deletedCandidates.find { (_, books) -> + books.map { it.fileHash }.containsAll(newBooksWithHash.map { it.fileHash }) && newBooksWithHash.map { it.fileHash }.containsAll(books.map { it.fileHash }) + } if (match != null) { // restore series @@ -332,8 +352,10 @@ class LibraryContentLifecycle( if (deletedCandidates.isNotEmpty()) { // if the book has no hash, compute the hash and store it val bookWithHash = - if (bookToAdd.fileHash.isNotBlank()) bookToAdd - else bookRepository.findByIdOrNull(bookToAdd.id)!!.copy(fileHash = hasher.computeHash(bookToAdd.path)).also { bookRepository.update(it) } + if (bookToAdd.fileHash.isNotBlank()) + bookToAdd + else + bookRepository.findByIdOrNull(bookToAdd.id)!!.copy(fileHash = hasher.computeHash(bookToAdd.path)).also { bookRepository.update(it) } val match = deletedCandidates.find { it.fileHash == bookWithHash.fileHash } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt index a50cf79f2..34912c2ca 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryLifecycle.kt @@ -30,7 +30,6 @@ class LibraryLifecycle( private val transactionTemplate: TransactionTemplate, private val libraryScanScheduler: LibraryScanScheduler, ) { - @Throws( FileNotFoundException::class, DirectoryNotFoundException::class, @@ -70,7 +69,10 @@ class LibraryLifecycle( eventPublisher.publishEvent(DomainEvent.LibraryUpdated(toUpdate)) } - private fun checkLibraryShouldRescan(existing: Library, updated: Library): Boolean { + private fun checkLibraryShouldRescan( + existing: Library, + updated: Library, + ): Boolean { if (existing.root != updated.root) return true if (existing.oneshotsDirectory != updated.oneshotsDirectory) return true if (existing.scanCbx != updated.scanCbx) return true @@ -81,7 +83,10 @@ class LibraryLifecycle( return false } - private fun checkLibraryValidity(library: Library, existing: Collection) { + private fun checkLibraryValidity( + library: Library, + existing: Collection, + ) { if (!Files.exists(library.path)) throw FileNotFoundException("Library root folder does not exist: ${library.root}") diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LocalArtworkLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LocalArtworkLifecycle.kt index 1248946c6..8e612aa1e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LocalArtworkLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LocalArtworkLifecycle.kt @@ -17,7 +17,6 @@ class LocalArtworkLifecycle( private val seriesLifecycle: SeriesLifecycle, private val localArtworkProvider: LocalArtworkProvider, ) { - fun refreshLocalArtwork(book: Book) { logger.info { "Refresh local artwork for book: $book" } val library = libraryRepository.findById(book.libraryId) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataAggregator.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataAggregator.kt index 42bd6654b..eb970a7a7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataAggregator.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataAggregator.kt @@ -6,16 +6,16 @@ import org.springframework.stereotype.Service @Service class MetadataAggregator { - fun aggregate(metadatas: Collection): BookMetadataAggregation { val authors = metadatas.flatMap { it.authors }.distinctBy { "${it.role}__${it.name}" } val tags = metadatas.flatMap { it.tags }.toSet() - val (summary, summaryNumber) = metadatas - .sortedBy { it.numberSort } - .find { it.summary.isNotBlank() } - ?.let { - it.summary to it.number - } ?: ("" to "") + val (summary, summaryNumber) = + metadatas + .sortedBy { it.numberSort } + .find { it.summary.isNotBlank() } + ?.let { + it.summary to it.number + } ?: ("" to "") val releaseDate = metadatas.mapNotNull { it.releaseDate }.minOrNull() return BookMetadataAggregation(authors = authors, tags = tags, releaseDate = releaseDate, summary = summary, summaryNumber = summaryNumber) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataApplier.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataApplier.kt index 1c58b7193..9168a607e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataApplier.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/MetadataApplier.kt @@ -8,12 +8,20 @@ import org.springframework.stereotype.Service @Service class MetadataApplier { + private fun getIfNotLocked( + original: T, + patched: T?, + lock: Boolean, + ): T = + if (patched != null && !lock) + patched + else + original - private fun getIfNotLocked(original: T, patched: T?, lock: Boolean): T = - if (patched != null && !lock) patched - else original - - fun apply(patch: BookMetadataPatch, metadata: BookMetadata): BookMetadata = + fun apply( + patch: BookMetadataPatch, + metadata: BookMetadata, + ): BookMetadata = with(metadata) { copy( title = getIfNotLocked(title, patch.title, titleLock), @@ -28,7 +36,10 @@ class MetadataApplier { ) } - fun apply(patch: SeriesMetadataPatch, metadata: SeriesMetadata): SeriesMetadata = + fun apply( + patch: SeriesMetadataPatch, + metadata: SeriesMetadata, + ): SeriesMetadata = with(metadata) { copy( status = getIfNotLocked(status, patch.status, statusLock), diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/PageHashLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/PageHashLifecycle.kt index bc0a41fd4..a4c0d95f0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/PageHashLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/PageHashLifecycle.kt @@ -23,19 +23,21 @@ class PageHashLifecycle( private val bookRepository: BookRepository, private val komgaProperties: KomgaProperties, ) { - private val hashableMediaTypes = listOf(MediaType.ZIP.type) fun getBookIdsWithMissingPageHash(library: Library): Collection = - if (library.hashPages) + if (library.hashPages) { mediaRepository.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(library.id, hashableMediaTypes, komgaProperties.pageHashing) .also { logger.info { "Found ${it.size} books with missing page hash" } } - else { + } else { logger.info { "Page hashing is not enabled, skipping" } emptyList() } - fun getPage(pageHash: String, resizeTo: Int? = null): TypedBytes? { + fun getPage( + pageHash: String, + resizeTo: Int? = null, + ): TypedBytes? { val match = pageHashRepository.findMatchesByHash(pageHash, Pageable.ofSize(1)).firstOrNull() ?: return null val book = bookRepository.findByIdOrNull(match.bookId) ?: return null diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListLifecycle.kt index 642b0d805..7c74dc3d1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListLifecycle.kt @@ -29,7 +29,6 @@ class ReadListLifecycle( private val eventPublisher: ApplicationEventPublisher, private val transactionTemplate: TransactionTemplate, ) { - @Throws( DuplicateNameException::class, ) @@ -50,8 +49,9 @@ class ReadListLifecycle( @Transactional fun updateReadList(toUpdate: ReadList) { logger.info { "Update read list: $toUpdate" } - val existing = readListRepository.findByIdOrNull(toUpdate.id) - ?: throw IllegalArgumentException("Cannot update read list that does not exist") + val existing = + readListRepository.findByIdOrNull(toUpdate.id) + ?: throw IllegalArgumentException("Cannot update read list that does not exist") if (!existing.name.equals(toUpdate.name, true) && readListRepository.existsByName(toUpdate.name)) throw DuplicateNameException("Read list name already exists") @@ -75,20 +75,25 @@ class ReadListLifecycle( * Read list will be created if it doesn't exist. */ @Transactional - fun addBookToReadList(readListName: String, book: Book, numberInList: Int?) { + fun addBookToReadList( + readListName: String, + book: Book, + numberInList: Int?, + ) { readListRepository.findByNameOrNull(readListName).let { existing -> if (existing != null) { - if (existing.bookIds.containsValue(book.id)) + if (existing.bookIds.containsValue(book.id)) { logger.debug { "Book is already in existing read list '${existing.name}'" } - else { + } else { val map = existing.bookIds.toSortedMap() - val key = if (numberInList != null && existing.bookIds.containsKey(numberInList)) { - logger.debug { "Existing read list '${existing.name}' already contains a book at position $numberInList, adding book '${book.name}' at the end" } - existing.bookIds.lastKey() + 1 - } else { - logger.debug { "Adding book '${book.name}' to existing read list '${existing.name}'" } - numberInList ?: (existing.bookIds.lastKey() + 1) - } + val key = + if (numberInList != null && existing.bookIds.containsKey(numberInList)) { + logger.debug { "Existing read list '${existing.name}' already contains a book at position $numberInList, adding book '${book.name}' at the end" } + existing.bookIds.lastKey() + 1 + } else { + logger.debug { "Adding book '${book.name}' to existing read list '${existing.name}'" } + numberInList ?: (existing.bookIds.lastKey() + 1) + } map[key] = book.id updateReadList( existing.copy(bookIds = map), @@ -150,12 +155,13 @@ class ReadListLifecycle( return it.thumbnail } - val ids = with(mutableListOf()) { - while (size < 4) { - this += readList.bookIds.values.take(4) + val ids = + with(mutableListOf()) { + while (size < 4) { + this += readList.bookIds.values.take(4) + } + this.take(4) } - this.take(4) - } val images = ids.mapNotNull { bookLifecycle.getThumbnailBytes(it)?.bytes } return mosaicGenerator.createMosaic(images) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListMatcher.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListMatcher.kt index 0eb430667..f737593ea 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListMatcher.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/ReadListMatcher.kt @@ -19,8 +19,10 @@ class ReadListMatcher( logger.info { "Trying to match $request" } val readListMatch = - if (readListRepository.existsByName(request.name)) ReadListMatch(request.name, "ERR_1009") - else ReadListMatch(request.name) + if (readListRepository.existsByName(request.name)) + ReadListMatch(request.name, "ERR_1009") + else + ReadListMatch(request.name) val matches = readListRequestRepository.matchBookRequests(request.books) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt index 58935bd56..e812b5f4e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt @@ -25,7 +25,6 @@ class SeriesCollectionLifecycle( private val eventPublisher: ApplicationEventPublisher, private val transactionTemplate: TransactionTemplate, ) { - @Throws( DuplicateNameException::class, ) @@ -47,8 +46,9 @@ class SeriesCollectionLifecycle( fun updateCollection(toUpdate: SeriesCollection) { logger.info { "Update collection: $toUpdate" } - val existing = collectionRepository.findByIdOrNull(toUpdate.id) - ?: throw IllegalArgumentException("Cannot update collection that does not exist") + val existing = + collectionRepository.findByIdOrNull(toUpdate.id) + ?: throw IllegalArgumentException("Cannot update collection that does not exist") if (!existing.name.equals(toUpdate.name, true) && collectionRepository.existsByName(toUpdate.name)) throw DuplicateNameException("Collection name already exists") @@ -71,12 +71,15 @@ class SeriesCollectionLifecycle( * Collection will be created if it doesn't exist. */ @Transactional - fun addSeriesToCollection(collectionName: String, series: Series) { + fun addSeriesToCollection( + collectionName: String, + series: Series, + ) { collectionRepository.findByNameOrNull(collectionName).let { existing -> if (existing != null) { - if (existing.seriesIds.contains(series.id)) + if (existing.seriesIds.contains(series.id)) { logger.debug { "Series is already in existing collection '${existing.name}'" } - else { + } else { logger.debug { "Adding series '${series.name}' to existing collection '${existing.name}'" } updateCollection( existing.copy(seriesIds = existing.seriesIds + series.id), @@ -133,17 +136,21 @@ class SeriesCollectionLifecycle( fun getThumbnailBytes(thumbnailId: String): ByteArray? = thumbnailSeriesCollectionRepository.findByIdOrNull(thumbnailId)?.thumbnail - fun getThumbnailBytes(collection: SeriesCollection, userId: String): ByteArray { + fun getThumbnailBytes( + collection: SeriesCollection, + userId: String, + ): ByteArray { thumbnailSeriesCollectionRepository.findSelectedByCollectionIdOrNull(collection.id)?.let { return it.thumbnail } - val ids = with(mutableListOf()) { - while (size < 4) { - this += collection.seriesIds.take(4) + val ids = + with(mutableListOf()) { + while (size < 4) { + this += collection.seriesIds.take(4) + } + this.take(4) } - this.take(4) - } val images = ids.mapNotNull { seriesLifecycle.getThumbnailBytes(it, userId) } return mosaicGenerator.createMosaic(images) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt index 59e774cc2..1ccef6f57 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesLifecycle.kt @@ -62,7 +62,6 @@ class SeriesLifecycle( private val transactionTemplate: TransactionTemplate, private val historicalEventRepository: HistoricalEventRepository, ) { - private val whitespacePattern = """\s+""".toRegex() fun sortBooks(series: Series) { @@ -73,33 +72,37 @@ class SeriesLifecycle( logger.debug { "Existing books: $books" } logger.debug { "Existing metadata: $metadatas" } - val sorted = books - .sortedWith( - compareBy(natSortComparator) { - it.name - .trim() - .stripAccents() - .replace(whitespacePattern, " ") - }, - ) - .map { book -> book to metadatas.first { it.bookId == book.id } } + val sorted = + books + .sortedWith( + compareBy(natSortComparator) { + it.name + .trim() + .stripAccents() + .replace(whitespacePattern, " ") + }, + ) + .map { book -> book to metadatas.first { it.bookId == book.id } } logger.debug { "Sorted books: $sorted" } bookRepository.update( sorted.mapIndexed { index, (book, _) -> book.copy(number = index + 1) }, ) - val oldToNew = sorted.mapIndexedNotNull { index, (book, metadata) -> - if (metadata.numberLock && metadata.numberSortLock) null - else Triple( - book, - metadata, - metadata.copy( - number = if (!metadata.numberLock) (index + 1).toString() else metadata.number, - numberSort = if (!metadata.numberSortLock) (index + 1).toFloat() else metadata.numberSort, - ), - ) - } + val oldToNew = + sorted.mapIndexedNotNull { index, (book, metadata) -> + if (metadata.numberLock && metadata.numberSortLock) + null + else + Triple( + book, + metadata, + metadata.copy( + number = if (!metadata.numberLock) (index + 1).toString() else metadata.number, + numberSort = if (!metadata.numberSortLock) (index + 1).toFloat() else metadata.numberSort, + ), + ) + } bookMetadataRepository.update(oldToNew.map { it.third }) // refresh metadata to reimport book number, else the series resorting would overwrite it @@ -116,7 +119,10 @@ class SeriesLifecycle( } } - fun addBooks(series: Series, booksToAdd: Collection) { + fun addBooks( + series: Series, + booksToAdd: Collection, + ) { booksToAdd.forEach { check(it.libraryId == series.libraryId) { "Cannot add book to series if they don't share the same libraryId" } } @@ -197,21 +203,29 @@ class SeriesLifecycle( series.forEach { eventPublisher.publishEvent(DomainEvent.SeriesDeleted(it)) } } - fun markReadProgressCompleted(seriesId: String, user: KomgaUser) { - val bookIds = bookRepository.findAllIdsBySeriesId(seriesId) - .filter { bookId -> - val readProgress = readProgressRepository.findByBookIdAndUserIdOrNull(bookId, user.id) - readProgress == null || !readProgress.completed - } - val progresses = mediaRepository.getPagesSizes(bookIds) - .map { (bookId, pageSize) -> ReadProgress(bookId, user.id, pageSize, true) } + fun markReadProgressCompleted( + seriesId: String, + user: KomgaUser, + ) { + val bookIds = + bookRepository.findAllIdsBySeriesId(seriesId) + .filter { bookId -> + val readProgress = readProgressRepository.findByBookIdAndUserIdOrNull(bookId, user.id) + readProgress == null || !readProgress.completed + } + val progresses = + mediaRepository.getPagesSizes(bookIds) + .map { (bookId, pageSize) -> ReadProgress(bookId, user.id, pageSize, true) } readProgressRepository.save(progresses) progresses.forEach { eventPublisher.publishEvent(DomainEvent.ReadProgressChanged(it)) } eventPublisher.publishEvent(DomainEvent.ReadProgressSeriesChanged(seriesId, user.id)) } - fun deleteReadProgress(seriesId: String, user: KomgaUser) { + fun deleteReadProgress( + seriesId: String, + user: KomgaUser, + ) { val bookIds = bookRepository.findAllIdsBySeriesId(seriesId) val progresses = readProgressRepository.findAllByBookIdsAndUserId(bookIds, user.id) readProgressRepository.deleteByBookIdsAndUserId(bookIds, user.id) @@ -243,27 +257,38 @@ class SeriesLifecycle( getBytesFromThumbnailSeries(it) } - fun getThumbnailBytes(seriesId: String, userId: String): ByteArray? { + fun getThumbnailBytes( + seriesId: String, + userId: String, + ): ByteArray? { getSelectedThumbnail(seriesId)?.let { return getBytesFromThumbnailSeries(it) } seriesRepository.findByIdOrNull(seriesId)?.let { series -> - val bookId = when (libraryRepository.findById(series.libraryId).seriesCover) { - Library.SeriesCover.FIRST -> bookRepository.findFirstIdInSeriesOrNull(seriesId) - Library.SeriesCover.FIRST_UNREAD_OR_FIRST -> bookRepository.findFirstUnreadIdInSeriesOrNull(seriesId, userId) - ?: bookRepository.findFirstIdInSeriesOrNull(seriesId) - Library.SeriesCover.FIRST_UNREAD_OR_LAST -> bookRepository.findFirstUnreadIdInSeriesOrNull(seriesId, userId) - ?: bookRepository.findLastIdInSeriesOrNull(seriesId) - Library.SeriesCover.LAST -> bookRepository.findLastIdInSeriesOrNull(seriesId) - } + val bookId = + when (libraryRepository.findById(series.libraryId).seriesCover) { + Library.SeriesCover.FIRST -> bookRepository.findFirstIdInSeriesOrNull(seriesId) + Library.SeriesCover.FIRST_UNREAD_OR_FIRST -> + bookRepository.findFirstUnreadIdInSeriesOrNull(seriesId, userId) + ?: bookRepository.findFirstIdInSeriesOrNull(seriesId) + + Library.SeriesCover.FIRST_UNREAD_OR_LAST -> + bookRepository.findFirstUnreadIdInSeriesOrNull(seriesId, userId) + ?: bookRepository.findLastIdInSeriesOrNull(seriesId) + + Library.SeriesCover.LAST -> bookRepository.findLastIdInSeriesOrNull(seriesId) + } if (bookId != null) return bookLifecycle.getThumbnailBytes(bookId)?.bytes } return null } - fun addThumbnailForSeries(thumbnail: ThumbnailSeries, markSelected: MarkSelectedPreference): ThumbnailSeries { + fun addThumbnailForSeries( + thumbnail: ThumbnailSeries, + markSelected: MarkSelectedPreference, + ): ThumbnailSeries { // delete existing thumbnail with the same url if (thumbnail.url != null) { thumbnailsSeriesRepository.findAllBySeriesId(thumbnail.seriesId) @@ -274,13 +299,15 @@ class SeriesLifecycle( } thumbnailsSeriesRepository.insert(thumbnail.copy(selected = false)) - val selected = when (markSelected) { - MarkSelectedPreference.YES -> true - MarkSelectedPreference.IF_NONE_OR_GENERATED -> { - thumbnailsSeriesRepository.findSelectedBySeriesIdOrNull(thumbnail.seriesId) == null + val selected = + when (markSelected) { + MarkSelectedPreference.YES -> true + MarkSelectedPreference.IF_NONE_OR_GENERATED -> { + thumbnailsSeriesRepository.findSelectedBySeriesIdOrNull(thumbnail.seriesId) == null + } + + MarkSelectedPreference.NO -> false } - MarkSelectedPreference.NO -> false - } if (selected) thumbnailsSeriesRepository.markSelected(thumbnail) @@ -299,9 +326,10 @@ class SeriesLifecycle( if (series.path.notExists()) return logger.info { "Cannot delete series folder, path does not exist: ${series.path}" } if (!series.path.isWritable()) return logger.info { "Cannot delete series folder, path is not writable: ${series.path}" } - val thumbnails = thumbnailsSeriesRepository.findAllBySeriesIdIdAndType(series.id, ThumbnailSeries.Type.SIDECAR) - .mapNotNull { it.url?.toURI()?.toPath() } - .filter { it.exists() && it.isWritable() } + val thumbnails = + thumbnailsSeriesRepository.findAllBySeriesIdIdAndType(series.id, ThumbnailSeries.Type.SIDECAR) + .mapNotNull { it.url?.toURI()?.toPath() } + .filter { it.exists() && it.isWritable() } bookRepository.findAllBySeriesId(series.id) .forEach { bookLifecycle.deleteBookFiles(it) } @@ -320,14 +348,17 @@ class SeriesLifecycle( private fun thumbnailsHouseKeeping(seriesId: String) { logger.info { "House keeping thumbnails for series: $seriesId" } - val all = thumbnailsSeriesRepository.findAllBySeriesId(seriesId) - .mapNotNull { - if (!it.exists()) { - logger.warn { "Thumbnail doesn't exist, removing entry" } - thumbnailsSeriesRepository.delete(it.id) - null - } else it - } + val all = + thumbnailsSeriesRepository.findAllBySeriesId(seriesId) + .mapNotNull { + if (!it.exists()) { + logger.warn { "Thumbnail doesn't exist, removing entry" } + thumbnailsSeriesRepository.delete(it.id) + null + } else { + it + } + } val selected = all.filter { it.selected } when { diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesMetadataLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesMetadataLifecycle.kt index e25d7b9cb..d36aa04c7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesMetadataLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesMetadataLifecycle.kt @@ -35,7 +35,6 @@ class SeriesMetadataLifecycle( private val collectionLifecycle: SeriesCollectionLifecycle, private val eventPublisher: ApplicationEventPublisher, ) { - fun refreshMetadata(series: Series) { logger.info { "Refresh metadata for series: $series" } @@ -49,15 +48,16 @@ class SeriesMetadataLifecycle( else -> { logger.debug { "Provider: ${provider.javaClass.simpleName}" } - val patches = bookRepository.findAllBySeriesId(series.id) - .mapNotNull { book -> - try { - provider.getSeriesMetadataFromBook(BookWithMedia(book, mediaRepository.findById(book.id)), library.importComicInfoSeriesAppendVolume) - } catch (e: Exception) { - logger.error(e) { "Error while getting metadata from ${provider.javaClass.simpleName} for book: $book" } - null + val patches = + bookRepository.findAllBySeriesId(series.id) + .mapNotNull { book -> + try { + provider.getSeriesMetadataFromBook(BookWithMedia(book, mediaRepository.findById(book.id)), library.importComicInfoSeriesAppendVolume) + } catch (e: Exception) { + logger.error(e) { "Error while getting metadata from ${provider.javaClass.simpleName} for book: $book" } + null + } } - } if (provider.shouldLibraryHandlePatch(library, MetadataPatchTarget.SERIES)) { handlePatchForSeriesMetadata(patches, series) @@ -79,12 +79,13 @@ class SeriesMetadataLifecycle( logger.info { "Library is not set to import series metadata for this provider, skipping: ${provider.javaClass.simpleName}" } else -> { logger.debug { "Provider: ${provider.javaClass.simpleName}" } - val patch = try { - provider.getSeriesMetadata(series) - } catch (e: Exception) { - logger.error(e) { "Error while getting metadata from ${provider::class.simpleName} for series: $series" } - null - } + val patch = + try { + provider.getSeriesMetadata(series) + } catch (e: Exception) { + logger.error(e) { "Error while getting metadata from ${provider::class.simpleName} for series: $series" } + null + } if (provider.shouldLibraryHandlePatch(library, MetadataPatchTarget.SERIES)) { handlePatchForSeriesMetadata(patch, series) @@ -101,19 +102,20 @@ class SeriesMetadataLifecycle( patches: List, series: Series, ) { - val aggregatedPatch = SeriesMetadataPatch( - title = patches.mostFrequent { it.title }, - titleSort = patches.mostFrequent { it.titleSort }, - status = patches.mostFrequent { it.status }, - genres = patches.mapNotNull { it.genres }.flatten().toSet().ifEmpty { null }, - language = patches.mostFrequent { it.language }, - summary = null, - readingDirection = patches.mostFrequent { it.readingDirection }, - ageRating = patches.mapNotNull { it.ageRating }.maxOrNull(), - publisher = patches.mostFrequent { it.publisher }, - totalBookCount = patches.mapNotNull { it.totalBookCount }.maxOrNull(), - collections = emptySet(), - ) + val aggregatedPatch = + SeriesMetadataPatch( + title = patches.mostFrequent { it.title }, + titleSort = patches.mostFrequent { it.titleSort }, + status = patches.mostFrequent { it.status }, + genres = patches.mapNotNull { it.genres }.flatten().toSet().ifEmpty { null }, + language = patches.mostFrequent { it.language }, + summary = null, + readingDirection = patches.mostFrequent { it.readingDirection }, + ageRating = patches.mapNotNull { it.ageRating }.maxOrNull(), + publisher = patches.mostFrequent { it.publisher }, + totalBookCount = patches.mapNotNull { it.totalBookCount }.maxOrNull(), + collections = emptySet(), + ) handlePatchForSeriesMetadata(aggregatedPatch, series) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/ThumbnailLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/ThumbnailLifecycle.kt index 66bc76e11..c01990e94 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/ThumbnailLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/ThumbnailLifecycle.kt @@ -122,24 +122,28 @@ class ThumbnailLifecycle( copier: (T, ThumbnailMetadata) -> T, updater: (Collection) -> Unit, ): Boolean { - val (result, duration) = measureTimedValue { - val thumbs = fetcher(Pageable.ofSize(1000)) - logger.info { "Fetched ${thumbs.numberOfElements} ${clazz.simpleName} to fix, total: ${thumbs.totalElements}" } + val (result, duration) = + measureTimedValue { + val thumbs = fetcher(Pageable.ofSize(1000)) + logger.info { "Fetched ${thumbs.numberOfElements} ${clazz.simpleName} to fix, total: ${thumbs.totalElements}" } - val fixedThumbs = thumbs.mapNotNull { - try { - val meta = supplier(it) - if (meta == null) null - else copier(it, meta) - } catch (e: Exception) { - logger.error(e) { "Could not fix thumbnail: $it" } - null - } + val fixedThumbs = + thumbs.mapNotNull { + try { + val meta = supplier(it) + if (meta == null) + null + else + copier(it, meta) + } catch (e: Exception) { + logger.error(e) { "Could not fix thumbnail: $it" } + null + } + } + + updater(fixedThumbs) + Result(fixedThumbs.size, (thumbs.numberOfElements < thumbs.totalElements)) } - - updater(fixedThumbs) - Result(fixedThumbs.size, (thumbs.numberOfElements < thumbs.totalElements)) - } logger.info { "Fixed ${result.processed} ${clazz.simpleName} in $duration" } return result.hasMore } @@ -159,5 +163,6 @@ class ThumbnailLifecycle( ) private data class Result(val processed: Int, val hasMore: Boolean) + private data class ThumbnailMetadata(val mediaType: String, val fileSize: Long, val dimension: Dimension) } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt index 6ae1266a0..b06282498 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/TransientBookLifecycle.kt @@ -30,7 +30,6 @@ class TransientBookLifecycle( private val seriesMetadataProviders: List, bookMetadataProviders: List, ) { - val bookMetadataProviders = bookMetadataProviders.filter { it.capabilities.contains(BookMetadataPatchCapability.NUMBER_SORT) } fun scanAndPersist(filePath: String): List { @@ -60,15 +59,16 @@ class TransientBookLifecycle( fun getMetadata(transientBook: TransientBook): Pair { val bookWithMedia = transientBook.toBookWithMedia() val number = bookMetadataProviders.firstNotNullOfOrNull { it.getBookMetadataFromBook(bookWithMedia)?.numberSort } - val series = seriesMetadataProviders - .flatMap { - buildList { - if (it.supportsAppendVolume) add(it.getSeriesMetadataFromBook(bookWithMedia, true)?.title) - add(it.getSeriesMetadataFromBook(bookWithMedia, false)?.title) + val series = + seriesMetadataProviders + .flatMap { + buildList { + if (it.supportsAppendVolume) add(it.getSeriesMetadataFromBook(bookWithMedia, true)?.title) + add(it.getSeriesMetadataFromBook(bookWithMedia, false)?.title) + } } - } - .filterNotNull() - .firstNotNullOfOrNull { seriesRepository.findAllByTitleContaining(it).firstOrNull() } + .filterNotNull() + .firstNotNullOfOrNull { seriesRepository.findAllByTitleContaining(it).firstOrNull() } return series?.id to number } @@ -77,11 +77,16 @@ class TransientBookLifecycle( MediaNotReadyException::class, IndexOutOfBoundsException::class, ) - fun getBookPage(transientBook: TransientBook, number: Int): TypedBytes { + fun getBookPage( + transientBook: TransientBook, + number: Int, + ): TypedBytes { val pageContent = bookAnalyzer.getPageContent(transientBook.toBookWithMedia(), number) val pageMediaType = - if (transientBook.media.profile == MediaProfile.PDF) pdfImageType.mediaType - else transientBook.media.pages[number - 1].mediaType + if (transientBook.media.profile == MediaProfile.PDF) + pdfImageType.mediaType + else + transientBook.media.pages[number - 1].mediaType return TypedBytes(pageContent, pageMediaType) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/cache/TransientBookCache.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/cache/TransientBookCache.kt index b14a24190..dbc551578 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/cache/TransientBookCache.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/cache/TransientBookCache.kt @@ -11,9 +11,10 @@ private val logger = KotlinLogging.logger {} @Service class TransientBookCache : TransientBookRepository { - private val cache = Caffeine.newBuilder() - .expireAfterAccess(1, TimeUnit.HOURS) - .build() + private val cache = + Caffeine.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build() override fun findByIdOrNull(transientBookId: String): TransientBook? = cache.getIfPresent(transientBookId) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt index d31b8de4d..7fb2fb866 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/DataSourcesConfiguration.kt @@ -14,7 +14,6 @@ import javax.sql.DataSource class DataSourcesConfiguration( private val komgaProperties: KomgaProperties, ) { - @Bean("sqliteDataSource") @Primary fun sqliteDataSource(): DataSource = @@ -28,17 +27,25 @@ class DataSourcesConfiguration( this.maximumPoolSize = 1 } - private fun buildDataSource(poolName: String, dataSourceClass: Class, databaseProps: KomgaProperties.Database): HikariDataSource { - val extraPragmas = databaseProps.pragmas.let { - if (it.isEmpty()) "" - else "?" + it.map { (key, value) -> "$key=$value" }.joinToString(separator = "&") - } + private fun buildDataSource( + poolName: String, + dataSourceClass: Class, + databaseProps: KomgaProperties.Database, + ): HikariDataSource { + val extraPragmas = + databaseProps.pragmas.let { + if (it.isEmpty()) + "" + else + "?" + it.map { (key, value) -> "$key=$value" }.joinToString(separator = "&") + } - val dataSource = DataSourceBuilder.create() - .driverClassName("org.sqlite.JDBC") - .url("jdbc:sqlite:${databaseProps.file}$extraPragmas") - .type(dataSourceClass) - .build() + val dataSource = + DataSourceBuilder.create() + .driverClassName("org.sqlite.JDBC") + .url("jdbc:sqlite:${databaseProps.file}$extraPragmas") + .type(dataSourceClass) + .build() dataSource.setEnforceForeignKeys(true) with(databaseProps) { @@ -47,9 +54,12 @@ class DataSourcesConfiguration( } val poolSize = - if (databaseProps.file.contains(":memory:") || databaseProps.file.contains("mode=memory")) 1 - else if (databaseProps.poolSize != null) databaseProps.poolSize!! - else Runtime.getRuntime().availableProcessors().coerceAtMost(databaseProps.maxPoolSize) + if (databaseProps.file.contains(":memory:") || databaseProps.file.contains("mode=memory")) + 1 + else if (databaseProps.poolSize != null) + databaseProps.poolSize!! + else + Runtime.getRuntime().availableProcessors().coerceAtMost(databaseProps.maxPoolSize) return HikariDataSource( HikariConfig().apply { diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/FlywaySecondaryMigrationInitializer.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/FlywaySecondaryMigrationInitializer.kt index 7964b5a5a..de2e7be89 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/FlywaySecondaryMigrationInitializer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/FlywaySecondaryMigrationInitializer.kt @@ -11,7 +11,6 @@ class FlywaySecondaryMigrationInitializer( @Qualifier("tasksDataSource") private val tasksDataSource: DataSource, ) : InitializingBean { - // by default Spring Boot will perform migration only on the @Primary datasource override fun afterPropertiesSet() { Flyway.configure() diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/SqliteUdfDataSource.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/SqliteUdfDataSource.kt index 194fbe693..c8bc1049f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/SqliteUdfDataSource.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/datasource/SqliteUdfDataSource.kt @@ -12,16 +12,18 @@ import java.sql.Connection private val log = KotlinLogging.logger {} class SqliteUdfDataSource : SQLiteDataSource() { - companion object { - const val udfStripAccents = "UDF_STRIP_ACCENTS" - const val collationUnicode3 = "COLLATION_UNICODE_3" + const val UDF_STRIP_ACCENTS = "UDF_STRIP_ACCENTS" + const val COLLATION_UNICODE_3 = "COLLATION_UNICODE_3" } override fun getConnection(): Connection = super.getConnection().also { addAllUdf(it as SQLiteConnection) } - override fun getConnection(username: String?, password: String?): SQLiteConnection = + override fun getConnection( + username: String?, + password: String?, + ): SQLiteConnection = super.getConnection(username, password).also { addAllUdf(it) } private fun addAllUdf(connection: SQLiteConnection) { @@ -47,10 +49,10 @@ class SqliteUdfDataSource : SQLiteDataSource() { } private fun createUdfStripAccents(connection: SQLiteConnection) { - log.debug { "Adding custom $udfStripAccents function" } + log.debug { "Adding custom $UDF_STRIP_ACCENTS function" } Function.create( connection, - udfStripAccents, + UDF_STRIP_ACCENTS, object : Function() { override fun xFunc() = when (val text = value_text(0)) { @@ -62,17 +64,21 @@ class SqliteUdfDataSource : SQLiteDataSource() { } private fun createUnicode3Collation(connection: SQLiteConnection) { - log.debug { "Adding custom $collationUnicode3 collation" } + log.debug { "Adding custom $COLLATION_UNICODE_3 collation" } Collation.create( connection, - collationUnicode3, + COLLATION_UNICODE_3, object : Collation() { - val collator = Collator.getInstance().apply { - strength = Collator.TERTIARY - decomposition = Collator.CANONICAL_DECOMPOSITION - } + val collator = + Collator.getInstance().apply { + strength = Collator.TERTIARY + decomposition = Collator.CANONICAL_DECOMPOSITION + } - override fun xCompare(str1: String, str2: String): Int = collator.compare(str1, str2) + override fun xCompare( + str1: String, + str2: String, + ): Int = collator.compare(str1, str2) }, ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/hash/Hasher.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/hash/Hasher.kt index 550796338..37ec44ced 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/hash/Hasher.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/hash/Hasher.kt @@ -14,7 +14,6 @@ private const val SEED = 0 @Component class Hasher { - fun computeHash(path: Path): String { logger.debug { "Hashing: $path" } @@ -38,7 +37,8 @@ class Hasher { } @OptIn(ExperimentalUnsignedTypes::class) - private fun ByteArray.toHexString(): String = asUByteArray().joinToString("") { - it.toString(16).padStart(2, '0') - } + private fun ByteArray.toHexString(): String = + asUByteArray().joinToString("") { + it.toString(16).padStart(2, '0') + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/httpexchange/HttpExchangeConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/httpexchange/HttpExchangeConfiguration.kt index 9938f4026..776c0e0f6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/httpexchange/HttpExchangeConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/httpexchange/HttpExchangeConfiguration.kt @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Configuration @Configuration class HttpExchangeConfiguration { - private val httpExchangeRepository = InMemoryHttpExchangeRepository() @Bean diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageAnalyzer.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageAnalyzer.kt index abbafdbcd..024de2dae 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageAnalyzer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageAnalyzer.kt @@ -10,7 +10,6 @@ private val logger = KotlinLogging.logger {} @Service class ImageAnalyzer { - /** * Returns the Dimension of the image contained in the stream. * The stream will not be closed, nor marked or reset. diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageConverter.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageConverter.kt index 4740f8d89..56ff063e5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageConverter.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/image/ImageConverter.kt @@ -16,14 +16,13 @@ import kotlin.math.min private val logger = KotlinLogging.logger {} -private const val webpNightMonkeys = "com.github.gotson.nightmonkeys.webp.imageio.plugins.WebpImageReaderSpi" +private const val WEBP_NIGHT_MONKEYS = "com.github.gotson.nightmonkeys.webp.imageio.plugins.WebpImageReaderSpi" @Service class ImageConverter( private val imageAnalyzer: ImageAnalyzer, private val contentDetector: ContentDetector, ) { - val supportedReadFormats by lazy { ImageIO.getReaderFormatNames().toList() } val supportedReadMediaTypes by lazy { ImageIO.getReaderMIMETypes().toList() } val supportedWriteFormats by lazy { ImageIO.getWriterFormatNames().toList() } @@ -38,15 +37,16 @@ class ImageConverter( } private fun chooseWebpReader() { - val providers = IIORegistry.getDefaultInstance().getServiceProviders( - ImageReaderSpi::class.java, - { it is ImageReaderSpi && it.mimeTypes.contains("image/webp") }, - false, - ).asSequence().toList() + val providers = + IIORegistry.getDefaultInstance().getServiceProviders( + ImageReaderSpi::class.java, + { it is ImageReaderSpi && it.mimeTypes.contains("image/webp") }, + false, + ).asSequence().toList() if (providers.size > 1) { logger.debug { "WebP reader providers: ${providers.map { it.javaClass.canonicalName }}" } - providers.firstOrNull { it.javaClass.canonicalName == webpNightMonkeys }?.let { nightMonkeys -> + providers.firstOrNull { it.javaClass.canonicalName == WEBP_NIGHT_MONKEYS }?.let { nightMonkeys -> (providers - nightMonkeys).forEach { logger.debug { "Deregister provider: ${it.javaClass.canonicalName}" } IIORegistry.getDefaultInstance().deregisterServiceProvider(it) @@ -57,29 +57,42 @@ class ImageConverter( private val supportsTransparency = listOf("png") - fun canConvertMediaType(from: String, to: String) = + fun canConvertMediaType( + from: String, + to: String, + ) = supportedReadMediaTypes.contains(from) && supportedWriteMediaTypes.contains(to) - fun convertImage(imageBytes: ByteArray, format: String): ByteArray = + fun convertImage( + imageBytes: ByteArray, + format: String, + ): ByteArray = ByteArrayOutputStream().use { baos -> val image = ImageIO.read(imageBytes.inputStream()) - val result = if (!supportsTransparency.contains(format) && containsAlphaChannel(image)) { - if (containsTransparency(image)) logger.info { "Image contains alpha channel but is not opaque, visual artifacts may appear" } - else logger.info { "Image contains alpha channel but is opaque, conversion should not generate any visual artifacts" } - BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB).also { - it.createGraphics().drawImage(image, 0, 0, Color.WHITE, null) + val result = + if (!supportsTransparency.contains(format) && containsAlphaChannel(image)) { + if (containsTransparency(image)) + logger.info { "Image contains alpha channel but is not opaque, visual artifacts may appear" } + else + logger.info { "Image contains alpha channel but is opaque, conversion should not generate any visual artifacts" } + BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB).also { + it.createGraphics().drawImage(image, 0, 0, Color.WHITE, null) + } + } else { + image } - } else { - image - } ImageIO.write(result, format, baos) baos.toByteArray() } - fun resizeImageToByteArray(imageBytes: ByteArray, format: ImageType, size: Int): ByteArray { + fun resizeImageToByteArray( + imageBytes: ByteArray, + format: ImageType, + size: Int, + ): ByteArray { val builder = resizeImageBuilder(imageBytes, format, size) ?: return imageBytes return ByteArrayOutputStream().use { @@ -88,20 +101,29 @@ class ImageConverter( } } - fun resizeImageToBufferedImage(imageBytes: ByteArray, format: ImageType, size: Int): BufferedImage { + fun resizeImageToBufferedImage( + imageBytes: ByteArray, + format: ImageType, + size: Int, + ): BufferedImage { val builder = resizeImageBuilder(imageBytes, format, size) ?: return ImageIO.read(imageBytes.inputStream()) return builder.asBufferedImage() } - private fun resizeImageBuilder(imageBytes: ByteArray, format: ImageType, size: Int): Thumbnails.Builder? { - val longestEdge = imageAnalyzer.getDimension(imageBytes.inputStream())?.let { - val mediaType = contentDetector.detectMediaType(imageBytes.inputStream()) - val longestEdge = max(it.height, it.width) - // don't resize if source and target format is the same, and source is smaller than desired - if (mediaType == format.mediaType && longestEdge <= size) return null - longestEdge - } + private fun resizeImageBuilder( + imageBytes: ByteArray, + format: ImageType, + size: Int, + ): Thumbnails.Builder? { + val longestEdge = + imageAnalyzer.getDimension(imageBytes.inputStream())?.let { + val mediaType = contentDetector.detectMediaType(imageBytes.inputStream()) + val longestEdge = max(it.height, it.width) + // don't resize if source and target format is the same, and source is smaller than desired + if (mediaType == format.mediaType && longestEdge <= size) return null + longestEdge + } // prevent upscaling val resizeTo = if (longestEdge != null) min(longestEdge, size) else size diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/JooqConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/JooqConfiguration.kt index f3e14be0c..7ed9ac5b1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/JooqConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/JooqConfiguration.kt @@ -19,7 +19,6 @@ import javax.sql.DataSource // as advised in https://docs.spring.io/spring-boot/docs/3.1.4/reference/htmlsingle/#howto.data-access.configure-jooq-with-multiple-datasources @Configuration class JooqConfiguration { - @Bean("dslContext") @Primary fun mainDslContext( @@ -37,12 +36,17 @@ class JooqConfiguration { ): DSLContext = createDslContext(dataSource, transactionProvider, executeListenerProviders) - private fun createDslContext(dataSource: DataSource, transactionProvider: ObjectProvider, executeListenerProviders: ObjectProvider) = DefaultDSLContext( - DefaultConfiguration().also { configuration -> - configuration.set(SQLDialect.SQLITE) - configuration.set(DataSourceConnectionProvider(TransactionAwareDataSourceProxy(dataSource))) - transactionProvider.ifAvailable { newTransactionProvider: TransactionProvider? -> configuration.set(newTransactionProvider) } - configuration.set(*executeListenerProviders.orderedStream().toList().toTypedArray()) - }, - ) + private fun createDslContext( + dataSource: DataSource, + transactionProvider: ObjectProvider, + executeListenerProviders: ObjectProvider, + ) = + DefaultDSLContext( + DefaultConfiguration().also { configuration -> + configuration.set(SQLDialect.SQLITE) + configuration.set(DataSourceConnectionProvider(TransactionAwareDataSourceProxy(dataSource))) + transactionProvider.ifAvailable { newTransactionProvider: TransactionProvider? -> configuration.set(newTransactionProvider) } + configuration.set(*executeListenerProviders.orderedStream().toList().toTypedArray()) + }, + ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/UnpagedSorted.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/UnpagedSorted.kt index fc2dadd28..bc9cbb153 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/UnpagedSorted.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/UnpagedSorted.kt @@ -6,7 +6,6 @@ import org.springframework.data.domain.Sort class UnpagedSorted( private val sort: Sort, ) : Pageable { - override fun getPageNumber(): Int { throw UnsupportedOperationException() } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt index c958ea71d..2d542526f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/Utils.kt @@ -27,7 +27,10 @@ fun Sort.Order.toSortField(sorts: Map>): SortField.sortByValues(values: List, asc: Boolean = true): Field { +fun Field.sortByValues( + values: List, + asc: Boolean = true, +): Field { var c = DSL.choose(this).`when`("dummy dsl", Int.MAX_VALUE) val multiplier = if (asc) 1 else -1 values.forEachIndexed { index, value -> c = c.`when`(value, index * multiplier) } @@ -42,9 +45,12 @@ fun Field.inOrNoCondition(list: Collection?): Condition = } fun Field.udfStripAccents() = - DSL.function(SqliteUdfDataSource.udfStripAccents, String::class.java, this) + DSL.function(SqliteUdfDataSource.UDF_STRIP_ACCENTS, String::class.java, this) -fun DSLContext.insertTempStrings(batchSize: Int, collection: Collection) { +fun DSLContext.insertTempStrings( + batchSize: Int, + collection: Collection, +) { this.deleteFrom(Tables.TEMP_STRING_LIST).execute() if (collection.isNotEmpty()) { collection.chunked(batchSize).forEach { chunk -> @@ -62,9 +68,12 @@ fun DSLContext.insertTempStrings(batchSize: Int, collection: Collection) fun DSLContext.selectTempStrings() = this.select(Tables.TEMP_STRING_LIST.STRING).from(Tables.TEMP_STRING_LIST) fun ContentRestrictions.toCondition(dsl: DSLContext): Condition { - val ageAllowed = if (ageRestriction?.restriction == AllowExclude.ALLOW_ONLY) { - Tables.SERIES_METADATA.AGE_RATING.isNotNull.and(Tables.SERIES_METADATA.AGE_RATING.lessOrEqual(ageRestriction.age)) - } else DSL.noCondition() + val ageAllowed = + if (ageRestriction?.restriction == AllowExclude.ALLOW_ONLY) { + Tables.SERIES_METADATA.AGE_RATING.isNotNull.and(Tables.SERIES_METADATA.AGE_RATING.lessOrEqual(ageRestriction.age)) + } else { + DSL.noCondition() + } val labelAllowed = if (labelsAllow.isNotEmpty()) @@ -73,12 +82,14 @@ fun ContentRestrictions.toCondition(dsl: DSLContext): Condition { .from(Tables.SERIES_METADATA_SHARING) .where(Tables.SERIES_METADATA_SHARING.LABEL.`in`(labelsAllow)), ) - else DSL.noCondition() + else + DSL.noCondition() val ageDenied = if (ageRestriction?.restriction == AllowExclude.EXCLUDE) Tables.SERIES_METADATA.AGE_RATING.isNull.or(Tables.SERIES_METADATA.AGE_RATING.lessThan(ageRestriction.age)) - else DSL.noCondition() + else + DSL.noCondition() val labelDenied = if (labelsExclude.isNotEmpty()) @@ -87,7 +98,8 @@ fun ContentRestrictions.toCondition(dsl: DSLContext): Condition { .from(Tables.SERIES_METADATA_SHARING) .where(Tables.SERIES_METADATA_SHARING.LABEL.`in`(labelsExclude)), ) - else DSL.noCondition() + else + DSL.noCondition() return ageAllowed.or(labelAllowed) .and(ageDenied.and(labelDenied)) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/AuthenticationActivityDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/AuthenticationActivityDao.kt index 25821602a..e1257ad48 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/AuthenticationActivityDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/AuthenticationActivityDao.kt @@ -22,25 +22,28 @@ import java.time.LocalDateTime class AuthenticationActivityDao( private val dsl: DSLContext, ) : AuthenticationActivityRepository { - private val aa = Tables.AUTHENTICATION_ACTIVITY - private val sorts = mapOf( - "dateTime" to aa.DATE_TIME, - "email" to aa.EMAIL, - "success" to aa.SUCCESS, - "ip" to aa.IP, - "error" to aa.ERROR, - "userId" to aa.USER_ID, - "userAgent" to aa.USER_AGENT, - ) + private val sorts = + mapOf( + "dateTime" to aa.DATE_TIME, + "email" to aa.EMAIL, + "success" to aa.SUCCESS, + "ip" to aa.IP, + "error" to aa.ERROR, + "userId" to aa.USER_ID, + "userAgent" to aa.USER_AGENT, + ) override fun findAll(pageable: Pageable): Page { val conditions: Condition = DSL.trueCondition() return findAll(conditions, pageable) } - override fun findAllByUser(user: KomgaUser, pageable: Pageable): Page { + override fun findAllByUser( + user: KomgaUser, + pageable: Pageable, + ): Page { val conditions = aa.USER_ID.eq(user.id).or(aa.EMAIL.eq(user.email)) return findAll(conditions, pageable) } @@ -54,23 +57,29 @@ class AuthenticationActivityDao( .fetchOne() ?.toDomain() - private fun findAll(conditions: Condition, pageable: Pageable): PageImpl { + private fun findAll( + conditions: Condition, + pageable: Pageable, + ): PageImpl { val count = dsl.fetchCount(aa, conditions) val orderBy = pageable.sort.toOrderBy(sorts) - val items = dsl.selectFrom(aa) - .where(conditions) - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(aa) - .map { it.toDomain() } + val items = + dsl.selectFrom(aa) + .where(conditions) + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(aa) + .map { it.toDomain() } val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDao.kt index 094431083..2f2683f04 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDao.kt @@ -35,16 +35,20 @@ class BookDao( private val d = Tables.BOOK_METADATA private val r = Tables.READ_PROGRESS - private val sorts = mapOf( - "createdDate" to b.CREATED_DATE, - "seriesId" to b.SERIES_ID, - "number" to b.NUMBER, - ) + private val sorts = + mapOf( + "createdDate" to b.CREATED_DATE, + "seriesId" to b.SERIES_ID, + "number" to b.NUMBER, + ) override fun findByIdOrNull(bookId: String): Book? = findByIdOrNull(dsl, bookId) - override fun findNotDeletedByLibraryIdAndUrlOrNull(libraryId: String, url: URL): Book? = + override fun findNotDeletedByLibraryIdAndUrlOrNull( + libraryId: String, + url: URL, + ): Book? = dsl.selectFrom(b) .where(b.LIBRARY_ID.eq(libraryId).and(b.URL.eq(url.toString()))) .and(b.DELETED_DATE.isNull) @@ -53,7 +57,10 @@ class BookDao( .firstOrNull() ?.toDomain() - private fun findByIdOrNull(dsl: DSLContext, bookId: String): Book? = + private fun findByIdOrNull( + dsl: DSLContext, + bookId: String, + ): Book? = dsl.selectFrom(b) .where(b.ID.eq(bookId)) .fetchOneInto(b) @@ -72,7 +79,10 @@ class BookDao( .map { it.toDomain() } @Transactional - override fun findAllNotDeletedByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): Collection { + override fun findAllNotDeletedByLibraryIdAndUrlNotIn( + libraryId: String, + urls: Collection, + ): Collection { dsl.insertTempStrings(batchSize, urls.map { it.toString() }) return dsl.selectFrom(b) @@ -103,33 +113,40 @@ class BookDao( .fetchInto(b) .map { it.toDomain() } - override fun findAll(bookSearch: BookSearch, pageable: Pageable): Page { + override fun findAll( + bookSearch: BookSearch, + pageable: Pageable, + ): Page { val conditions = bookSearch.toCondition() - val count = dsl.selectCount() - .from(b) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) - .where(conditions) - .fetchOne(0, Long::class.java) ?: 0 + val count = + dsl.selectCount() + .from(b) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) + .where(conditions) + .fetchOne(0, Long::class.java) ?: 0 val orderBy = pageable.sort.toOrderBy(sorts) - val items = dsl.select(*b.fields()) - .from(b) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) - .where(conditions) - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(b) - .map { it.toDomain() } + val items = + dsl.select(*b.fields()) + .from(b) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) + .where(conditions) + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(b) + .map { it.toDomain() } val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count.toInt(), 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count.toInt(), 20), pageSort), count, ) } @@ -164,7 +181,10 @@ class BookDao( .limit(1) .fetchOne(b.ID) - override fun findFirstUnreadIdInSeriesOrNull(seriesId: String, userId: String): String? = + override fun findFirstUnreadIdInSeriesOrNull( + seriesId: String, + userId: String, + ): String? = dsl.select(b.ID) .from(b) .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) @@ -193,7 +213,10 @@ class BookDao( .where(b.LIBRARY_ID.eq(libraryId)) .fetch(b.ID) - override fun findAllIds(bookSearch: BookSearch, sort: Sort): Collection { + override fun findAllIds( + bookSearch: BookSearch, + sort: Sort, + ): Collection { val conditions = bookSearch.toCondition() val orderBy = sort.toOrderBy(sorts) @@ -207,7 +230,10 @@ class BookDao( .fetch(b.ID) } - override fun findAllByLibraryIdAndMediaTypes(libraryId: String, mediaTypes: Collection): Collection = + override fun findAllByLibraryIdAndMediaTypes( + libraryId: String, + mediaTypes: Collection, + ): Collection = dsl.select(*b.fields()) .from(b) .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) @@ -216,7 +242,11 @@ class BookDao( .fetchInto(b) .map { it.toDomain() } - override fun findAllByLibraryIdAndMismatchedExtension(libraryId: String, mediaType: String, extension: String): Collection = + override fun findAllByLibraryIdAndMismatchedExtension( + libraryId: String, + mediaType: String, + extension: String, + ): Collection = dsl.select(*b.fields()) .from(b) .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDao.kt index 9b6130027..b1a75b9c9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDao.kt @@ -54,7 +54,6 @@ class BookDtoDao( @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, private val transactionTemplate: TransactionTemplate, ) : BookDtoRepository { - private val b = Tables.BOOK private val m = Tables.MEDIA private val d = Tables.BOOK_METADATA @@ -70,28 +69,34 @@ class BookDtoDao( private val countRead: AggregateFunction = DSL.sum(DSL.`when`(r.COMPLETED.isTrue, 1).otherwise(0)) private val countInProgress: AggregateFunction = DSL.sum(DSL.`when`(r.COMPLETED.isFalse, 1).otherwise(0)) - private val sorts = mapOf( - "name" to b.NAME.collate(SqliteUdfDataSource.collationUnicode3), - "created" to b.CREATED_DATE, - "createdDate" to b.CREATED_DATE, - "lastModified" to b.LAST_MODIFIED_DATE, - "lastModifiedDate" to b.LAST_MODIFIED_DATE, - "fileSize" to b.FILE_SIZE, - "size" to b.FILE_SIZE, - "fileHash" to b.FILE_HASH, - "url" to b.URL.noCase(), - "media.status" to m.STATUS.noCase(), - "media.comment" to m.COMMENT.noCase(), - "media.mediaType" to m.MEDIA_TYPE.noCase(), - "metadata.title" to d.TITLE.collate(SqliteUdfDataSource.collationUnicode3), - "metadata.numberSort" to d.NUMBER_SORT, - "metadata.releaseDate" to d.RELEASE_DATE, - "readProgress.lastModified" to r.LAST_MODIFIED_DATE, - "readProgress.readDate" to r.READ_DATE, - "readList.number" to rlb.NUMBER, - ) + private val sorts = + mapOf( + "name" to b.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3), + "created" to b.CREATED_DATE, + "createdDate" to b.CREATED_DATE, + "lastModified" to b.LAST_MODIFIED_DATE, + "lastModifiedDate" to b.LAST_MODIFIED_DATE, + "fileSize" to b.FILE_SIZE, + "size" to b.FILE_SIZE, + "fileHash" to b.FILE_HASH, + "url" to b.URL.noCase(), + "media.status" to m.STATUS.noCase(), + "media.comment" to m.COMMENT.noCase(), + "media.mediaType" to m.MEDIA_TYPE.noCase(), + "metadata.title" to d.TITLE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3), + "metadata.numberSort" to d.NUMBER_SORT, + "metadata.releaseDate" to d.RELEASE_DATE, + "readProgress.lastModified" to r.LAST_MODIFIED_DATE, + "readProgress.readDate" to r.READ_DATE, + "readList.number" to rlb.NUMBER, + ) - override fun findAll(search: BookSearchWithReadProgress, userId: String, pageable: Pageable, restrictions: ContentRestrictions): Page { + override fun findAll( + search: BookSearchWithReadProgress, + userId: String, + pageable: Pageable, + restrictions: ContentRestrictions, + ): Page { val conditions = search.toCondition().and(restrictions.toCondition(dsl)) return findAll(conditions, userId, pageable, false, null, search.searchTerm) @@ -120,67 +125,84 @@ class BookDtoDao( ): Page { val bookIds = luceneHelper.searchEntitiesIds(searchTerm, LuceneEntity.Book) - val searchCondition = when { - bookIds == null -> noCondition() - bookIds.isEmpty() -> falseCondition() - // use temp table in case there are many search results - else -> b.ID.`in`(dsl.selectTempStrings()) - } + val searchCondition = + when { + bookIds == null -> noCondition() + bookIds.isEmpty() -> falseCondition() + // use temp table in case there are many search results + else -> b.ID.`in`(dsl.selectTempStrings()) + } val orderBy = pageable.sort.mapNotNull { - if (it.property == "relevance" && !bookIds.isNullOrEmpty()) b.ID.sortByValues(bookIds, it.isAscending) - else it.toSortField(sorts) + if (it.property == "relevance" && !bookIds.isNullOrEmpty()) + b.ID.sortByValues(bookIds, it.isAscending) + else + it.toSortField(sorts) } - val (count, dtos) = transactionTemplate.execute { - // only insert if we know we are going to select on that condition - if (!bookIds.isNullOrEmpty()) dsl.insertTempStrings(batchSize, bookIds) + val (count, dtos) = + transactionTemplate.execute { + // only insert if we know we are going to select on that condition + if (!bookIds.isNullOrEmpty()) dsl.insertTempStrings(batchSize, bookIds) - val count = dsl.fetchCount( - dsl.select(b.ID) - .from(b) - .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .leftJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID)) - .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .apply { if (selectReadListNumber) leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) } - .where(conditions) - .and(searchCondition) - .groupBy(b.ID), - ) + val count = + dsl.fetchCount( + dsl.select(b.ID) + .from(b) + .leftJoin(m).on(b.ID.eq(m.BOOK_ID)) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) + .leftJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID)) + .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } + .apply { if (selectReadListNumber) leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) } + .where(conditions) + .and(searchCondition) + .groupBy(b.ID), + ) - val dtos = selectBase(userId, selectReadListNumber) - .where(conditions) - .and(searchCondition) - .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchAndMap() + val dtos = + selectBase(userId, selectReadListNumber) + .where(conditions) + .and(searchCondition) + .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchAndMap() - count to dtos - }!! + count to dtos + }!! val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( dtos, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findByIdOrNull(bookId: String, userId: String): BookDto? = + override fun findByIdOrNull( + bookId: String, + userId: String, + ): BookDto? = selectBase(userId) .where(b.ID.eq(bookId)) .fetchAndMap() .firstOrNull() - override fun findPreviousInSeriesOrNull(bookId: String, userId: String): BookDto? = + override fun findPreviousInSeriesOrNull( + bookId: String, + userId: String, + ): BookDto? = findSiblingSeries(bookId, userId, next = false) - override fun findNextInSeriesOrNull(bookId: String, userId: String): BookDto? = + override fun findNextInSeriesOrNull( + bookId: String, + userId: String, + ): BookDto? = findSiblingSeries(bookId, userId, next = true) override fun findPreviousInReadListOrNull( @@ -201,33 +223,40 @@ class BookDtoDao( ): BookDto? = findSiblingReadList(readList, bookId, userId, filterOnLibraryIds, restrictions, next = true) - override fun findAllOnDeck(userId: String, filterOnLibraryIds: Collection?, pageable: Pageable, restrictions: ContentRestrictions): Page { - val seriesIds = dsl.select(s.ID) - .from(s) - .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .leftJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID)) - .where(restrictions.toCondition(dsl)) - .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .groupBy(s.ID) - .having(countUnread.ge(inline(1.toBigDecimal()))) - .and(countRead.ge(inline(1.toBigDecimal()))) - .and(countInProgress.eq(inline(0.toBigDecimal()))) - .orderBy(DSL.max(r.LAST_MODIFIED_DATE).desc()) - .fetchInto(String::class.java) + override fun findAllOnDeck( + userId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + restrictions: ContentRestrictions, + ): Page { + val seriesIds = + dsl.select(s.ID) + .from(s) + .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) + .leftJoin(sd).on(b.SERIES_ID.eq(sd.SERIES_ID)) + .where(restrictions.toCondition(dsl)) + .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } + .groupBy(s.ID) + .having(countUnread.ge(inline(1.toBigDecimal()))) + .and(countRead.ge(inline(1.toBigDecimal()))) + .and(countInProgress.eq(inline(0.toBigDecimal()))) + .orderBy(DSL.max(r.LAST_MODIFIED_DATE).desc()) + .fetchInto(String::class.java) - val dtos = seriesIds - .drop(pageable.pageNumber * pageable.pageSize) - .take(pageable.pageSize) - .mapNotNull { seriesId -> - selectBase(userId) - .where(b.SERIES_ID.eq(seriesId)) - .and(r.COMPLETED.isNull) - .orderBy(d.NUMBER_SORT.asc()) - .limit(1) - .fetchAndMap() - .firstOrNull() - } + val dtos = + seriesIds + .drop(pageable.pageNumber * pageable.pageSize) + .take(pageable.pageSize) + .mapNotNull { seriesId -> + selectBase(userId) + .where(b.SERIES_ID.eq(seriesId)) + .and(r.COMPLETED.isNull) + .orderBy(d.NUMBER_SORT.asc()) + .limit(1) + .fetchAndMap() + .firstOrNull() + } return PageImpl( dtos, @@ -236,41 +265,53 @@ class BookDtoDao( ) } - override fun findAllDuplicates(userId: String, pageable: Pageable): Page { - val hashes = dsl.select(b.FILE_HASH, DSL.count(b.ID)) - .from(b) - .where(b.FILE_HASH.ne("")) - .groupBy(b.FILE_HASH, b.FILE_SIZE) - .having(DSL.count(b.ID).gt(1)) - .fetch() - .associate { it.value1() to it.value2() } + override fun findAllDuplicates( + userId: String, + pageable: Pageable, + ): Page { + val hashes = + dsl.select(b.FILE_HASH, DSL.count(b.ID)) + .from(b) + .where(b.FILE_HASH.ne("")) + .groupBy(b.FILE_HASH, b.FILE_SIZE) + .having(DSL.count(b.ID).gt(1)) + .fetch() + .associate { it.value1() to it.value2() } val count = hashes.values.sum() val orderBy = pageable.sort.toOrderBy(sorts) - val dtos = selectBase(userId) - .where(b.FILE_HASH.`in`(hashes.keys)) - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchAndMap() + val dtos = + selectBase(userId) + .where(b.FILE_HASH.`in`(hashes.keys)) + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchAndMap() val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( dtos, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId) - private fun findSiblingSeries(bookId: String, userId: String, next: Boolean): BookDto? { - val record = dsl.select(b.SERIES_ID, d.NUMBER_SORT) - .from(b) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .where(b.ID.eq(bookId)) - .fetchOne()!! + private fun findSiblingSeries( + bookId: String, + userId: String, + next: Boolean, + ): BookDto? { + val record = + dsl.select(b.SERIES_ID, d.NUMBER_SORT) + .from(b) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .where(b.ID.eq(bookId)) + .fetchOne()!! val seriesId = record.get(0, String::class.java) val numberSort = record.get(1, Float::class.java) @@ -292,13 +333,14 @@ class BookDtoDao( next: Boolean, ): BookDto? { if (readList.ordered) { - val numberSort = dsl.select(rlb.NUMBER) - .from(b) - .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) - .where(b.ID.eq(bookId)) - .and(rlb.READLIST_ID.eq(readList.id)) - .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .fetchOne(rlb.NUMBER) + val numberSort = + dsl.select(rlb.NUMBER) + .from(b) + .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) + .where(b.ID.eq(bookId)) + .and(rlb.READLIST_ID.eq(readList.id)) + .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } + .fetchOne(rlb.NUMBER) return selectBase(userId, true) .where(rlb.READLIST_ID.eq(readList.id)) @@ -312,16 +354,17 @@ class BookDtoDao( } else { // it is too complex to perform a seek by release date as it could be null and could also have multiple occurrences of the same value // instead we pull the whole list of ids, and perform the seek on the list - val bookIds = dsl.select(b.ID) - .from(b) - .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .apply { if (restrictions.isRestricted) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } - .where(rlb.READLIST_ID.eq(readList.id)) - .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } - .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(d.RELEASE_DATE) - .fetch(b.ID) + val bookIds = + dsl.select(b.ID) + .from(b) + .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .apply { if (restrictions.isRestricted) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } + .where(rlb.READLIST_ID.eq(readList.id)) + .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } + .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } + .orderBy(d.RELEASE_DATE) + .fetch(b.ID) val bookIndex = bookIds.indexOfFirst { it == bookId } if (bookIndex == -1) return null @@ -336,7 +379,10 @@ class BookDtoDao( } } - private fun selectBase(userId: String, selectReadListNumber: Boolean = false) = + private fun selectBase( + userId: String, + selectReadListNumber: Boolean = false, + ) = dsl.select( *b.fields(), *m.fields(), @@ -360,18 +406,21 @@ class BookDtoDao( lateinit var links: Map> transactionTemplate.executeWithoutResult { dsl.insertTempStrings(batchSize, bookIds) - authors = dsl.selectFrom(a) - .where(a.BOOK_ID.`in`(dsl.selectTempStrings())) - .filter { it.name != null } - .groupBy({ it.bookId }, { AuthorDto(it.name, it.role) }) + authors = + dsl.selectFrom(a) + .where(a.BOOK_ID.`in`(dsl.selectTempStrings())) + .filter { it.name != null } + .groupBy({ it.bookId }, { AuthorDto(it.name, it.role) }) - tags = dsl.selectFrom(bt) - .where(bt.BOOK_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.bookId }, { it.tag }) + tags = + dsl.selectFrom(bt) + .where(bt.BOOK_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.bookId }, { it.tag }) - links = dsl.selectFrom(bl) - .where(bl.BOOK_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.bookId }, { WebLinkDto(it.label, it.url) }) + links = + dsl.selectFrom(bl) + .where(bl.BOOK_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.bookId }, { WebLinkDto(it.label, it.url) }) } return records @@ -395,16 +444,17 @@ class BookDtoDao( if (deleted == true) c = c.and(b.DELETED_DATE.isNotNull) if (deleted == false) c = c.and(b.DELETED_DATE.isNull) if (releasedAfter != null) c = c.and(d.RELEASE_DATE.gt(releasedAfter)) - if (!tags.isNullOrEmpty()) c = c.and(b.ID.`in`(dsl.select(bt.BOOK_ID).from(bt).where(bt.TAG.collate(SqliteUdfDataSource.collationUnicode3).`in`(tags)))) + if (!tags.isNullOrEmpty()) c = c.and(b.ID.`in`(dsl.select(bt.BOOK_ID).from(bt).where(bt.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(tags)))) if (readStatus != null) { - val cr = readStatus.map { - when (it) { - ReadStatus.UNREAD -> r.COMPLETED.isNull - ReadStatus.READ -> r.COMPLETED.isTrue - ReadStatus.IN_PROGRESS -> r.COMPLETED.isFalse - } - }.reduce { acc, condition -> acc.or(condition) } + val cr = + readStatus.map { + when (it) { + ReadStatus.UNREAD -> r.COMPLETED.isNull + ReadStatus.READ -> r.COMPLETED.isTrue + ReadStatus.IN_PROGRESS -> r.COMPLETED.isFalse + } + }.reduce { acc, condition -> acc.or(condition) } c = c.and(cr) } @@ -420,7 +470,12 @@ class BookDtoDao( return c } - private fun BookRecord.toDto(media: MediaDto, metadata: BookMetadataDto, readProgress: ReadProgressDto?, seriesTitle: String) = + private fun BookRecord.toDto( + media: MediaDto, + metadata: BookMetadataDto, + readProgress: ReadProgressDto?, + seriesTitle: String, + ) = BookDto( id = id, seriesId = seriesId, @@ -450,7 +505,11 @@ class BookDtoDao( epubDivinaCompatible = epubDivinaCompatible, ) - private fun BookMetadataRecord.toDto(authors: List, tags: Set, links: List) = + private fun BookMetadataRecord.toDto( + authors: List, + tags: Set, + links: List, + ) = BookMetadataDto( title = title, titleLock = titleLock, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDao.kt index ebaa1c85d..682b9c9c4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDao.kt @@ -132,16 +132,17 @@ class BookMetadataAggregationDao( override fun count(): Long = dsl.fetchCount(d).toLong() - private fun BookMetadataAggregationRecord.toDomain(authors: List, tags: Set) = + private fun BookMetadataAggregationRecord.toDomain( + authors: List, + tags: Set, + ) = BookMetadataAggregation( authors = authors, tags = tags, releaseDate = releaseDate, summary = summary, summaryNumber = summaryNumber, - seriesId = seriesId, - createdDate = createdDate.toCurrentTimeZone(), lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), ) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDao.kt index fd600a9e7..4012d63ec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDao.kt @@ -23,7 +23,6 @@ class BookMetadataDao( private val dsl: DSLContext, @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, ) : BookMetadataRepository { - private val d = Tables.BOOK_METADATA private val a = Tables.BOOK_METADATA_AUTHOR private val bt = Tables.BOOK_METADATA_TAG @@ -40,7 +39,10 @@ class BookMetadataDao( override fun findAllByIds(bookIds: Collection): Collection = find(dsl, bookIds) - private fun find(dsl: DSLContext, bookIds: Collection) = + private fun find( + dsl: DSLContext, + bookIds: Collection, + ) = dsl.select(*groupFields) .from(d) .leftJoin(a).on(d.BOOK_ID.eq(a.BOOK_ID)) @@ -242,7 +244,11 @@ class BookMetadataDao( override fun count(): Long = dsl.fetchCount(d).toLong() - private fun BookMetadataRecord.toDomain(authors: List, tags: Set, links: List) = + private fun BookMetadataRecord.toDomain( + authors: List, + tags: Set, + links: List, + ) = BookMetadata( title = title, summary = summary, @@ -253,12 +259,9 @@ class BookMetadataDao( tags = tags, isbn = isbn, links = links, - bookId = bookId, - createdDate = createdDate.toCurrentTimeZone(), lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), - titleLock = titleLock, summaryLock = summaryLock, numberLock = numberLock, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDao.kt index c4fca5ac9..8177b431f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDao.kt @@ -11,7 +11,6 @@ import org.springframework.transaction.annotation.Transactional class HistoricalEventDao( private val dsl: DSLContext, ) : HistoricalEventRepository { - private val e = Tables.HISTORICAL_EVENT private val ep = Tables.HISTORICAL_EVENT_PROPERTIES diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDtoDao.kt index b13cb371d..af6601d55 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/HistoricalEventDtoDao.kt @@ -16,41 +16,44 @@ import org.springframework.stereotype.Component class HistoricalEventDtoDao( private val dsl: DSLContext, ) : HistoricalEventDtoRepository { - private val e = Tables.HISTORICAL_EVENT private val ep = Tables.HISTORICAL_EVENT_PROPERTIES - private val sorts = mapOf( - "type" to e.TYPE, - "bookId" to e.BOOK_ID, - "seriesId" to e.SERIES_ID, - "timestamp" to e.TIMESTAMP, - ) + private val sorts = + mapOf( + "type" to e.TYPE, + "bookId" to e.BOOK_ID, + "seriesId" to e.SERIES_ID, + "timestamp" to e.TIMESTAMP, + ) override fun findAll(pageable: Pageable): Page { val count = dsl.fetchCount(e) val orderBy = pageable.sort.toOrderBy(sorts) - val items = dsl.selectFrom(e) - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .map { er -> - val epr = dsl.selectFrom(ep).where(ep.ID.eq(er.id)).fetch() - HistoricalEventDto( - type = er.type, - timestamp = er.timestamp, - bookId = er.bookId, - seriesId = er.seriesId, - properties = epr.filterNot { it.key == null }.associate { it.key to it.value }, - ) - } + val items = + dsl.selectFrom(e) + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .map { er -> + val epr = dsl.selectFrom(ep).where(ep.ID.eq(er.id)).fetch() + HistoricalEventDto( + type = er.type, + timestamp = er.timestamp, + bookId = er.bookId, + seriesId = er.seriesId, + properties = epr.filterNot { it.key == null }.associate { it.key to it.value }, + ) + } val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDao.kt index 0b1b0d47c..ea103f1a1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDao.kt @@ -20,7 +20,6 @@ import java.time.ZoneId class KomgaUserDao( private val dsl: DSLContext, ) : KomgaUserRepository { - private val u = Tables.USER private val ul = Tables.USER_LIBRARY_SHARING private val us = Tables.USER_SHARING @@ -48,9 +47,10 @@ class KomgaUserDao( private fun ResultQuery.fetchAndMap() = this.fetchGroups({ it.into(u) }, { it.into(ul) }) .map { (ur, ulr) -> - val usr = dsl.selectFrom(us) - .where(us.USER_ID.eq(ur.id)) - .toList() + val usr = + dsl.selectFrom(us) + .where(us.USER_ID.eq(ur.id)) + .toList() KomgaUser( email = ur.email, password = ur.password, @@ -59,13 +59,16 @@ class KomgaUserDao( rolePageStreaming = ur.rolePageStreaming, sharedLibrariesIds = ulr.mapNotNull { it.libraryId }.toSet(), sharedAllLibraries = ur.sharedAllLibraries, - restrictions = ContentRestrictions( - ageRestriction = if (ur.ageRestriction != null && ur.ageRestrictionAllowOnly != null) - AgeRestriction(ur.ageRestriction, if (ur.ageRestrictionAllowOnly) AllowExclude.ALLOW_ONLY else AllowExclude.EXCLUDE) - else null, - labelsAllow = usr.filter { it.allow }.map { it.label }.toSet(), - labelsExclude = usr.filterNot { it.allow }.map { it.label }.toSet(), - ), + restrictions = + ContentRestrictions( + ageRestriction = + if (ur.ageRestriction != null && ur.ageRestrictionAllowOnly != null) + AgeRestriction(ur.ageRestriction, if (ur.ageRestrictionAllowOnly) AllowExclude.ALLOW_ONLY else AllowExclude.EXCLUDE) + else + null, + labelsAllow = usr.filter { it.allow }.map { it.label }.toSet(), + labelsExclude = usr.filterNot { it.allow }.map { it.label }.toSet(), + ), id = ur.id, createdDate = ur.createdDate.toCurrentTimeZone(), lastModifiedDate = ur.lastModifiedDate.toCurrentTimeZone(), @@ -131,7 +134,10 @@ class KomgaUserDao( insertSharingRestrictions(user) } - override fun saveAnnouncementIdsRead(user: KomgaUser, announcementIds: Set) { + override fun saveAnnouncementIdsRead( + user: KomgaUser, + announcementIds: Set, + ) { dsl.batchStore(announcementIds.map { AnnouncementsReadRecord(user.id, it) }).execute() } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDao.kt index 6427623ac..464d72702 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDao.kt @@ -18,7 +18,6 @@ import java.time.ZoneId class LibraryDao( private val dsl: DSLContext, ) : LibraryRepository { - private val l = Tables.LIBRARY private val ul = Tables.USER_LIBRARY_SHARING private val le = Tables.LIBRARY_EXCLUSIONS @@ -36,6 +35,7 @@ class LibraryDao( private fun findOne(libraryId: String) = selectBase() .where(l.ID.eq(libraryId)) + override fun findAll(): Collection = selectBase() .fetchAndMap() @@ -145,6 +145,7 @@ class LibraryDao( } override fun count(): Long = dsl.fetchCount(l).toLong() + fun findDirectoryExclusions(libraryId: String): Set = dsl.select(le.EXCLUSION) .from(le) @@ -193,7 +194,6 @@ class LibraryDao( hashPages = hashPages, analyzeDimensions = analyzeDimensions, oneshotsDirectory = oneshotsDirectory, - unavailableDate = unavailableDate, id = id, createdDate = createdDate.toCurrentTimeZone(), diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt index ff2a13d8d..97a8fe6c9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDao.kt @@ -34,24 +34,24 @@ class MediaDao( @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, private val mapper: ObjectMapper, ) : MediaRepository { - private val m = Tables.MEDIA private val p = Tables.MEDIA_PAGE private val f = Tables.MEDIA_FILE private val b = Tables.BOOK - private val groupFields = arrayOf( - m.BOOK_ID, - m.MEDIA_TYPE, - m.STATUS, - m.CREATED_DATE, - m.LAST_MODIFIED_DATE, - m.COMMENT, - m.PAGE_COUNT, - m.EXTENSION_CLASS, - m.EPUB_DIVINA_COMPATIBLE, - *p.fields(), - ) + private val groupFields = + arrayOf( + m.BOOK_ID, + m.MEDIA_TYPE, + m.STATUS, + m.CREATED_DATE, + m.LAST_MODIFIED_DATE, + m.COMMENT, + m.PAGE_COUNT, + m.EXTENSION_CLASS, + m.EPUB_DIVINA_COMPATIBLE, + *p.fields(), + ) override fun findById(bookId: String): Media = find(dsl, bookId)!! @@ -66,7 +66,11 @@ class MediaDao( .fetchOne() ?.map { deserializeExtension(it.get(m.EXTENSION_CLASS), it.get(m.EXTENSION_VALUE_BLOB)) } - override fun findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(libraryId: String, mediaTypes: Collection, pageHashing: Int): Collection { + override fun findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash( + libraryId: String, + mediaTypes: Collection, + pageHashing: Int, + ): Collection { val pagesCount = DSL.count(p.BOOK_ID) val hashedCount = DSL.sum(DSL.`when`(p.FILE_HASH.eq(""), 0).otherwise(1)).cast(Int::class.java) val neededHash = pageHashing * 2 @@ -99,7 +103,10 @@ class MediaDao( .fetch() .map { Pair(it[m.BOOK_ID], it[m.PAGE_COUNT]) } - private fun find(dsl: DSLContext, bookId: String): Media? = + private fun find( + dsl: DSLContext, + bookId: String, + ): Media? = dsl.select(*groupFields) .from(m) .leftJoin(p).on(m.BOOK_ID.eq(p.BOOK_ID)) @@ -110,9 +117,10 @@ class MediaDao( { it.into(m) }, { it.into(p) }, ).map { (mr, pr) -> - val files = dsl.selectFrom(f) - .where(f.BOOK_ID.eq(bookId)) - .fetchInto(f) + val files = + dsl.selectFrom(f) + .where(f.BOOK_ID.eq(bookId)) + .fetchInto(f) mr.toDomain(pr.filterNot { it.bookId == null }.map { it.toDomain() }, files.map { it.toDomain() }) }.firstOrNull() @@ -271,7 +279,10 @@ class MediaDao( override fun count(): Long = dsl.fetchCount(m).toLong() - private fun MediaRecord.toDomain(pages: List, files: List) = + private fun MediaRecord.toDomain( + pages: List, + files: List, + ) = Media( status = Media.Status.valueOf(status), mediaType = mediaType, @@ -286,7 +297,10 @@ class MediaDao( lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), ) - fun deserializeExtension(extensionClass: String?, extensionBlob: ByteArray?): MediaExtension? { + fun deserializeExtension( + extensionClass: String?, + extensionBlob: ByteArray?, + ): MediaExtension? { if (extensionClass == null || extensionBlob == null) return null return try { GZIPInputStream(extensionBlob.inputStream()).use { gz -> diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDao.kt index 3156acccb..db9714bc6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDao.kt @@ -26,28 +26,29 @@ import java.time.ZoneId class PageHashDao( private val dsl: DSLContext, ) : PageHashRepository { - private val p = Tables.MEDIA_PAGE private val b = Tables.BOOK private val ph = Tables.PAGE_HASH private val pht = Tables.PAGE_HASH_THUMBNAIL - private val sortsKnown = mapOf( - "hash" to ph.HASH, - "matchCount" to DSL.field("count"), - "deleteCount" to ph.DELETE_COUNT, - "deleteSize" to ph.SIZE * ph.DELETE_COUNT, - ) + private val sortsKnown = + mapOf( + "hash" to ph.HASH, + "matchCount" to DSL.field("count"), + "deleteCount" to ph.DELETE_COUNT, + "deleteSize" to ph.SIZE * ph.DELETE_COUNT, + ) - private val sortsUnknown = mapOf( - "hash" to p.FILE_HASH, - "fileSize" to p.FILE_SIZE, - "matchCount" to DSL.field("count"), - "totalSize" to DSL.field("totalSize"), - "url" to b.URL, - "bookId" to b.ID, - "pageNumber" to p.NUMBER, - ) + private val sortsUnknown = + mapOf( + "hash" to p.FILE_HASH, + "fileSize" to p.FILE_SIZE, + "matchCount" to DSL.field("count"), + "totalSize" to DSL.field("totalSize"), + "url" to b.URL, + "bookId" to b.ID, + "pageNumber" to p.NUMBER, + ) override fun findKnown(pageHash: String): PageHashKnown? = dsl.selectFrom(ph) @@ -55,103 +56,124 @@ class PageHashDao( .fetchOneInto(ph) ?.toDomain() - override fun findAllKnown(actions: List?, pageable: Pageable): Page { - val query = dsl.select(*ph.fields(), DSL.count(p.FILE_HASH).`as`("count")) - .from(ph) - .leftJoin(p).on(ph.HASH.eq(p.FILE_HASH)) - .apply { actions?.let { where(ph.ACTION.`in`(actions)) } } - .groupBy(*ph.fields()) + override fun findAllKnown( + actions: List?, + pageable: Pageable, + ): Page { + val query = + dsl.select(*ph.fields(), DSL.count(p.FILE_HASH).`as`("count")) + .from(ph) + .leftJoin(p).on(ph.HASH.eq(p.FILE_HASH)) + .apply { actions?.let { where(ph.ACTION.`in`(actions)) } } + .groupBy(*ph.fields()) val count = dsl.fetchCount(query) val orderBy = pageable.sort.toOrderBy(sortsKnown) - val items = query - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetch() - .map { it.into(ph).toDomain(it.get("count", Int::class.java)) } + val items = + query + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetch() + .map { it.into(ph).toDomain(it.get("count", Int::class.java)) } val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } override fun findAllUnknown(pageable: Pageable): Page { val bookCount = DSL.count(p.BOOK_ID) - val query = dsl.select( - p.FILE_HASH, - p.FILE_SIZE, - bookCount.`as`("count"), - (bookCount * p.FILE_SIZE).`as`("totalSize"), - ) - .from(p) - .where(p.FILE_HASH.ne("")) - .and( - DSL.notExists( - dsl.selectOne() - .from(ph) - .where(ph.HASH.eq(p.FILE_HASH)), - ), + val query = + dsl.select( + p.FILE_HASH, + p.FILE_SIZE, + bookCount.`as`("count"), + (bookCount * p.FILE_SIZE).`as`("totalSize"), ) - .groupBy(p.FILE_HASH) - .having(DSL.count(p.BOOK_ID).gt(1)) - - val count = dsl.fetchCount(query) - - val orderBy = pageable.sort.toOrderBy(sortsUnknown) - val items = query - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetch { - PageHashUnknown(it.value1(), it.value2(), it.value3()) - } - - val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() - return PageImpl( - items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), - count.toLong(), - ) - } - - override fun findMatchesByHash(pageHash: String, pageable: Pageable): Page { - val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER, p.FILE_NAME, p.FILE_SIZE, p.MEDIA_TYPE) - .from(p) - .leftJoin(b).on(p.BOOK_ID.eq(b.ID)) - .where(p.FILE_HASH.eq(pageHash)) - - val count = dsl.fetchCount(query) - - val orderBy = pageable.sort.toOrderBy(sortsUnknown) - val items = query - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetch { - PageHashMatch( - bookId = it.value1(), - url = URL(it.value2()), - pageNumber = it.value3() + 1, - fileName = it.value4(), - fileSize = it.value5(), - mediaType = it.value6(), + .from(p) + .where(p.FILE_HASH.ne("")) + .and( + DSL.notExists( + dsl.selectOne() + .from(ph) + .where(ph.HASH.eq(p.FILE_HASH)), + ), ) - } + .groupBy(p.FILE_HASH) + .having(DSL.count(p.BOOK_ID).gt(1)) + + val count = dsl.fetchCount(query) + + val orderBy = pageable.sort.toOrderBy(sortsUnknown) + val items = + query + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetch { + PageHashUnknown(it.value1(), it.value2(), it.value3()) + } val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findMatchesByKnownHashAction(actions: List?, libraryId: String?): Map> = + override fun findMatchesByHash( + pageHash: String, + pageable: Pageable, + ): Page { + val query = + dsl.select(p.BOOK_ID, b.URL, p.NUMBER, p.FILE_NAME, p.FILE_SIZE, p.MEDIA_TYPE) + .from(p) + .leftJoin(b).on(p.BOOK_ID.eq(b.ID)) + .where(p.FILE_HASH.eq(pageHash)) + + val count = dsl.fetchCount(query) + + val orderBy = pageable.sort.toOrderBy(sortsUnknown) + val items = + query + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetch { + PageHashMatch( + bookId = it.value1(), + url = URL(it.value2()), + pageNumber = it.value3() + 1, + fileName = it.value4(), + fileSize = it.value5(), + mediaType = it.value6(), + ) + } + + val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() + return PageImpl( + items, + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), + count.toLong(), + ) + } + + override fun findMatchesByKnownHashAction( + actions: List?, + libraryId: String?, + ): Map> = dsl.select(p.BOOK_ID, p.FILE_NAME, p.NUMBER, p.FILE_HASH, p.MEDIA_TYPE, p.FILE_SIZE) .from(p) .innerJoin(ph).on(p.FILE_HASH.eq(ph.HASH)) @@ -159,13 +181,14 @@ class PageHashDao( .where(ph.ACTION.`in`(actions)) .apply { libraryId?.let { and(b.LIBRARY_ID.eq(it)) } } .fetch { - it.value1() to BookPageNumbered( - fileName = it.value2(), - pageNumber = it.value3() + 1, - fileHash = it.value4(), - mediaType = it.value5(), - fileSize = it.value6(), - ) + it.value1() to + BookPageNumbered( + fileName = it.value2(), + pageNumber = it.value3() + 1, + fileHash = it.value4(), + mediaType = it.value5(), + fileSize = it.value6(), + ) }.groupingBy { it.first } .fold(emptyList()) { acc, (_, new) -> acc + new } @@ -176,7 +199,10 @@ class PageHashDao( .fetchOne()?.value1() @Transactional - override fun insert(pageHash: PageHashKnown, thumbnail: ByteArray?) { + override fun insert( + pageHash: PageHashKnown, + thumbnail: ByteArray?, + ) { dsl.insertInto(ph) .set(ph.HASH, pageHash.hash) .set(ph.SIZE, pageHash.size) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDao.kt index 4edc5bf9c..c81e102f8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDao.kt @@ -36,19 +36,23 @@ class ReadListDao( private val luceneHelper: LuceneHelper, @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, ) : ReadListRepository { - private val rl = Tables.READLIST private val rlb = Tables.READLIST_BOOK private val b = Tables.BOOK private val sd = Tables.SERIES_METADATA - private val sorts = mapOf( - "name" to rl.NAME.collate(SqliteUdfDataSource.collationUnicode3), - "createdDate" to rl.CREATED_DATE, - "lastModifiedDate" to rl.LAST_MODIFIED_DATE, - ) + private val sorts = + mapOf( + "name" to rl.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3), + "createdDate" to rl.CREATED_DATE, + "lastModifiedDate" to rl.LAST_MODIFIED_DATE, + ) - override fun findByIdOrNull(readListId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions): ReadList? = + override fun findByIdOrNull( + readListId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions, + ): ReadList? = selectBase(restrictions.isRestricted) .where(rl.ID.eq(readListId)) .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } @@ -56,17 +60,25 @@ class ReadListDao( .fetchAndMap(filterOnLibraryIds, restrictions) .firstOrNull() - override fun findAll(belongsToLibraryIds: Collection?, filterOnLibraryIds: Collection?, search: String?, pageable: Pageable, restrictions: ContentRestrictions): Page { + override fun findAll( + belongsToLibraryIds: Collection?, + filterOnLibraryIds: Collection?, + search: String?, + pageable: Pageable, + restrictions: ContentRestrictions, + ): Page { val readListIds = luceneHelper.searchEntitiesIds(search, LuceneEntity.ReadList) val searchCondition = rl.ID.inOrNoCondition(readListIds) - val conditions = searchCondition - .and(b.LIBRARY_ID.inOrNoCondition(belongsToLibraryIds)) - .and(b.LIBRARY_ID.inOrNoCondition(filterOnLibraryIds)) - .and(restrictions.toCondition(dsl)) + val conditions = + searchCondition + .and(b.LIBRARY_ID.inOrNoCondition(belongsToLibraryIds)) + .and(b.LIBRARY_ID.inOrNoCondition(filterOnLibraryIds)) + .and(restrictions.toCondition(dsl)) val queryIds = - if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted) null + if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted) + null else dsl.selectDistinct(rl.ID) .from(rl) @@ -76,38 +88,50 @@ class ReadListDao( .where(conditions) val count = - if (queryIds != null) dsl.fetchCount(queryIds) - else dsl.fetchCount(rl, searchCondition) + if (queryIds != null) + dsl.fetchCount(queryIds) + else + dsl.fetchCount(rl, searchCondition) val orderBy = pageable.sort.mapNotNull { - if (it.property == "relevance" && !readListIds.isNullOrEmpty()) rl.ID.sortByValues(readListIds, it.isAscending) - else it.toSortField(sorts) + if (it.property == "relevance" && !readListIds.isNullOrEmpty()) + rl.ID.sortByValues(readListIds, it.isAscending) + else + it.toSortField(sorts) } - val items = selectBase(restrictions.isRestricted) - .where(conditions) - .apply { if (queryIds != null) and(rl.ID.`in`(queryIds)) } - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchAndMap(filterOnLibraryIds, restrictions) + val items = + selectBase(restrictions.isRestricted) + .where(conditions) + .apply { if (queryIds != null) and(rl.ID.`in`(queryIds)) } + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchAndMap(filterOnLibraryIds, restrictions) val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findAllContainingBookId(containsBookId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions): Collection { - val queryIds = dsl.select(rl.ID) - .from(rl) - .leftJoin(rlb).on(rl.ID.eq(rlb.READLIST_ID)) - .apply { if (restrictions.isRestricted) leftJoin(b).on(rlb.BOOK_ID.eq(b.ID)).leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } - .where(rlb.BOOK_ID.eq(containsBookId)) - .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } + override fun findAllContainingBookId( + containsBookId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions, + ): Collection { + val queryIds = + dsl.select(rl.ID) + .from(rl) + .leftJoin(rlb).on(rl.ID.eq(rlb.READLIST_ID)) + .apply { if (restrictions.isRestricted) leftJoin(b).on(rlb.BOOK_ID.eq(b.ID)).leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } + .where(rlb.BOOK_ID.eq(containsBookId)) + .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } return selectBase(restrictions.isRestricted) .where(rl.ID.`in`(queryIds)) @@ -141,20 +165,24 @@ class ReadListDao( .leftJoin(b).on(rlb.BOOK_ID.eq(b.ID)) .apply { if (joinOnSeriesMetadata) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } - private fun ResultQuery.fetchAndMap(filterOnLibraryIds: Collection?, restrictions: ContentRestrictions = ContentRestrictions()): List = + private fun ResultQuery.fetchAndMap( + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions = ContentRestrictions(), + ): List = fetchInto(rl) .map { rr -> - val bookIds = dsl.select(*rlb.fields()) - .from(rlb) - .leftJoin(b).on(rlb.BOOK_ID.eq(b.ID)) - .apply { if (restrictions.isRestricted) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } - .where(rlb.READLIST_ID.eq(rr.id)) - .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } - .orderBy(rlb.NUMBER.asc()) - .fetchInto(rlb) - .mapNotNull { it.number to it.bookId } - .toMap().toSortedMap() + val bookIds = + dsl.select(*rlb.fields()) + .from(rlb) + .leftJoin(b).on(rlb.BOOK_ID.eq(b.ID)) + .apply { if (restrictions.isRestricted) leftJoin(sd).on(sd.SERIES_ID.eq(b.SERIES_ID)) } + .where(rlb.READLIST_ID.eq(rr.id)) + .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } + .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } + .orderBy(rlb.NUMBER.asc()) + .fetchInto(rlb) + .mapNotNull { it.number to it.bookId } + .toMap().toSortedMap() rr.toDomain(bookIds) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListRequestDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListRequestDao.kt index 5b5aec0c6..75a712bef 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListRequestDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListRequestDao.kt @@ -31,30 +31,31 @@ class ReadListRequestDao( val indexField = "index" val numberField = "number" val requestsTable = values(*requestsAsRows.toTypedArray()).`as`("request", indexField, seriesField, numberField) - val matchedRequests = dsl.select( - requestsTable.field(indexField, Int::class.java), - sd.SERIES_ID, - sd.TITLE, - bd.BOOK_ID, - bd.NUMBER, - bd.TITLE, - bma.RELEASE_DATE, - ) - .from(requestsTable) - .innerJoin(sd).on(requestsTable.field(seriesField, String::class.java)?.eq(sd.TITLE.noCase())) - .leftJoin(bma).on(sd.SERIES_ID.eq(bma.SERIES_ID)) - .innerJoin(b).on(sd.SERIES_ID.eq(b.SERIES_ID)) - .innerJoin(bd).on( - b.ID.eq(bd.BOOK_ID) - .and(ltrim(bd.NUMBER, value("0")).eq(ltrim(requestsTable.field(numberField, String::class.java), value("0")).noCase())), - ).fetchGroups(requestsTable.field(indexField, Int::class.java)) - .mapValues { (_, records) -> - // use the requests index to match results - records.groupBy( - { ReadListRequestBookMatchSeries(it.get(1, String::class.java), it.get(2, String::class.java), it.get(6, LocalDate::class.java)) }, - { ReadListRequestBookMatchBook(it.get(3, String::class.java), it.get(4, String::class.java), it.get(5, String::class.java)) }, - ) - } + val matchedRequests = + dsl.select( + requestsTable.field(indexField, Int::class.java), + sd.SERIES_ID, + sd.TITLE, + bd.BOOK_ID, + bd.NUMBER, + bd.TITLE, + bma.RELEASE_DATE, + ) + .from(requestsTable) + .innerJoin(sd).on(requestsTable.field(seriesField, String::class.java)?.eq(sd.TITLE.noCase())) + .leftJoin(bma).on(sd.SERIES_ID.eq(bma.SERIES_ID)) + .innerJoin(b).on(sd.SERIES_ID.eq(b.SERIES_ID)) + .innerJoin(bd).on( + b.ID.eq(bd.BOOK_ID) + .and(ltrim(bd.NUMBER, value("0")).eq(ltrim(requestsTable.field(numberField, String::class.java), value("0")).noCase())), + ).fetchGroups(requestsTable.field(indexField, Int::class.java)) + .mapValues { (_, records) -> + // use the requests index to match results + records.groupBy( + { ReadListRequestBookMatchSeries(it.get(1, String::class.java), it.get(2, String::class.java), it.get(6, LocalDate::class.java)) }, + { ReadListRequestBookMatchBook(it.get(3, String::class.java), it.get(4, String::class.java), it.get(5, String::class.java)) }, + ) + } return requests.mapIndexed { i, request -> ReadListRequestBookMatches( diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDao.kt index 817ea8df8..85499b27c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDao.kt @@ -27,7 +27,6 @@ class ReadProgressDao( @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, private val mapper: ObjectMapper, ) : ReadProgressRepository { - private val r = Tables.READ_PROGRESS private val rs = Tables.READ_PROGRESS_SERIES private val b = Tables.BOOK @@ -37,7 +36,10 @@ class ReadProgressDao( .fetchInto(r) .map { it.toDomain() } - override fun findByBookIdAndUserIdOrNull(bookId: String, userId: String): ReadProgress? = + override fun findByBookIdAndUserIdOrNull( + bookId: String, + userId: String, + ): ReadProgress? = dsl.selectFrom(r) .where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))) .fetchOneInto(r) @@ -55,7 +57,10 @@ class ReadProgressDao( .fetchInto(r) .map { it.toDomain() } - override fun findAllByBookIdsAndUserId(bookIds: Collection, userId: String): Collection = + override fun findAllByBookIdsAndUserId( + bookIds: Collection, + userId: String, + ): Collection = dsl.selectFrom(r) .where(r.BOOK_ID.`in`(bookIds).and(r.USER_ID.eq(userId))) .fetchInto(r) @@ -113,7 +118,10 @@ class ReadProgressDao( .set(r.LOCATOR, locator?.let { mapper.serializeJsonGz(it) }) @Transactional - override fun delete(bookId: String, userId: String) { + override fun delete( + bookId: String, + userId: String, + ) { dsl.deleteFrom(r).where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))).execute() aggregateSeriesProgress(listOf(bookId), userId) } @@ -146,7 +154,10 @@ class ReadProgressDao( } @Transactional - override fun deleteByBookIdsAndUserId(bookIds: Collection, userId: String) { + override fun deleteByBookIdsAndUserId( + bookIds: Collection, + userId: String, + ) { dsl.insertTempStrings(batchSize, bookIds) dsl.deleteFrom(r).where(r.BOOK_ID.`in`(dsl.selectTempStrings())).and(r.USER_ID.eq(userId)).execute() @@ -159,12 +170,16 @@ class ReadProgressDao( dsl.deleteFrom(rs).execute() } - private fun aggregateSeriesProgress(bookIds: Collection, userId: String? = null) { + private fun aggregateSeriesProgress( + bookIds: Collection, + userId: String? = null, + ) { dsl.insertTempStrings(batchSize, bookIds) - val seriesIdsQuery = dsl.select(b.SERIES_ID) - .from(b) - .where(b.ID.`in`(dsl.selectTempStrings())) + val seriesIdsQuery = + dsl.select(b.SERIES_ID) + .from(b) + .where(b.ID.`in`(dsl.selectTempStrings())) dsl.deleteFrom(rs) .where(rs.SERIES_ID.`in`(seriesIdsQuery)) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDtoDao.kt index 743acc407..242b2718b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadProgressDtoDao.kt @@ -17,7 +17,6 @@ import java.math.BigDecimal class ReadProgressDtoDao( private val dsl: DSLContext, ) : ReadProgressDtoRepository { - private val rlb = Tables.READLIST_BOOK private val b = Tables.BOOK private val d = Tables.BOOK_METADATA @@ -27,81 +26,99 @@ class ReadProgressDtoDao( private val countRead: AggregateFunction = DSL.sum(DSL.`when`(r.COMPLETED.isTrue, 1).otherwise(0)) private val countInProgress: AggregateFunction = DSL.sum(DSL.`when`(r.COMPLETED.isFalse, 1).otherwise(0)) - override fun findProgressV2BySeries(seriesId: String, userId: String): TachiyomiReadProgressV2Dto { - val numberSortReadProgress = dsl.select( - d.NUMBER_SORT, - r.COMPLETED, - ) - .from(b) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .where(b.SERIES_ID.eq(seriesId)) - .orderBy(d.NUMBER_SORT) - .fetch() - .toList() + override fun findProgressV2BySeries( + seriesId: String, + userId: String, + ): TachiyomiReadProgressV2Dto { + val numberSortReadProgress = + dsl.select( + d.NUMBER_SORT, + r.COMPLETED, + ) + .from(b) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .where(b.SERIES_ID.eq(seriesId)) + .orderBy(d.NUMBER_SORT) + .fetch() + .toList() - val maxNumberSort = dsl.select(DSL.max(d.NUMBER_SORT)) - .from(b) - .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) - .where(b.SERIES_ID.eq(seriesId)) - .fetchOne(DSL.max(d.NUMBER_SORT)) ?: 0F + val maxNumberSort = + dsl.select(DSL.max(d.NUMBER_SORT)) + .from(b) + .leftJoin(d).on(b.ID.eq(d.BOOK_ID)) + .where(b.SERIES_ID.eq(seriesId)) + .fetchOne(DSL.max(d.NUMBER_SORT)) ?: 0F val booksCount = getSeriesBooksCount(seriesId, userId) return booksCountToDtoV2(booksCount, numberSortReadProgress.lastRead() ?: 0F, maxNumberSort) } - private fun getSeriesBooksCount(seriesId: String, userId: String) = dsl - .select(countUnread.`as`(BOOKS_UNREAD_COUNT)) - .select(countRead.`as`(BOOKS_READ_COUNT)) - .select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT)) - .from(b) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .where(b.SERIES_ID.eq(seriesId)) - .fetch() - .first() - .map { - BooksCount( - unreadCount = it.get(BOOKS_UNREAD_COUNT, Int::class.java), - readCount = it.get(BOOKS_READ_COUNT, Int::class.java), - inProgressCount = it.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java), - ) - } - - override fun findProgressByReadList(readListId: String, userId: String): TachiyomiReadProgressDto { - val indexedReadProgress = dsl.select( - rowNumber().over().orderBy(rlb.NUMBER), - r.COMPLETED, - ) - .from(b) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) - .where(rlb.READLIST_ID.eq(readListId)) - .orderBy(rlb.NUMBER) - .fetch() - .toList() - - val booksCountRecord = dsl + private fun getSeriesBooksCount( + seriesId: String, + userId: String, + ) = + dsl .select(countUnread.`as`(BOOKS_UNREAD_COUNT)) .select(countRead.`as`(BOOKS_READ_COUNT)) .select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT)) .from(b) .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) - .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) - .where(rlb.READLIST_ID.eq(readListId)) + .where(b.SERIES_ID.eq(seriesId)) .fetch() .first() + .map { + BooksCount( + unreadCount = it.get(BOOKS_UNREAD_COUNT, Int::class.java), + readCount = it.get(BOOKS_READ_COUNT, Int::class.java), + inProgressCount = it.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java), + ) + } - val booksCount = BooksCount( - unreadCount = booksCountRecord.get(BOOKS_UNREAD_COUNT, Int::class.java), - readCount = booksCountRecord.get(BOOKS_READ_COUNT, Int::class.java), - inProgressCount = booksCountRecord.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java), - ) + override fun findProgressByReadList( + readListId: String, + userId: String, + ): TachiyomiReadProgressDto { + val indexedReadProgress = + dsl.select( + rowNumber().over().orderBy(rlb.NUMBER), + r.COMPLETED, + ) + .from(b) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) + .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) + .where(rlb.READLIST_ID.eq(readListId)) + .orderBy(rlb.NUMBER) + .fetch() + .toList() + + val booksCountRecord = + dsl + .select(countUnread.`as`(BOOKS_UNREAD_COUNT)) + .select(countRead.`as`(BOOKS_READ_COUNT)) + .select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT)) + .from(b) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId)) + .leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) + .where(rlb.READLIST_ID.eq(readListId)) + .fetch() + .first() + + val booksCount = + BooksCount( + unreadCount = booksCountRecord.get(BOOKS_UNREAD_COUNT, Int::class.java), + readCount = booksCountRecord.get(BOOKS_READ_COUNT, Int::class.java), + inProgressCount = booksCountRecord.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java), + ) return booksCountToDto(booksCount, indexedReadProgress.lastRead() ?: 0) } - private fun booksCountToDto(booksCount: BooksCount, lastReadContinuousIndex: Int): TachiyomiReadProgressDto = + private fun booksCountToDto( + booksCount: BooksCount, + lastReadContinuousIndex: Int, + ): TachiyomiReadProgressDto = TachiyomiReadProgressDto( booksCount = booksCount.totalCount, booksUnreadCount = booksCount.unreadCount, @@ -110,7 +127,11 @@ class ReadProgressDtoDao( lastReadContinuousIndex = lastReadContinuousIndex, ) - private fun booksCountToDtoV2(booksCount: BooksCount, lastReadContinuousNumberSort: Float, maxNumberSort: Float): TachiyomiReadProgressV2Dto = + private fun booksCountToDtoV2( + booksCount: BooksCount, + lastReadContinuousNumberSort: Float, + maxNumberSort: Float, + ): TachiyomiReadProgressV2Dto = TachiyomiReadProgressV2Dto( booksCount = booksCount.totalCount, booksUnreadCount = booksCount.unreadCount, diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReferentialDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReferentialDao.kt index 6ee3e9be0..bcd651445 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReferentialDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ReferentialDao.kt @@ -23,7 +23,6 @@ import java.time.LocalDate class ReferentialDao( private val dsl: DSLContext, ) : ReferentialRepository { - private val a = Tables.BOOK_METADATA_AUTHOR private val sd = Tables.SERIES_METADATA private val bma = Tables.BOOK_METADATA_AGGREGATION @@ -38,28 +37,39 @@ class ReferentialDao( private val rb = Tables.READLIST_BOOK private val sl = Tables.SERIES_METADATA_SHARING - override fun findAllAuthorsByName(search: String, filterOnLibraryIds: Collection?): List = + override fun findAllAuthorsByName( + search: String, + filterOnLibraryIds: Collection?, + ): List = dsl.selectDistinct(a.NAME, a.ROLE) .from(a) .apply { filterOnLibraryIds?.let { leftJoin(b).on(a.BOOK_ID.eq(b.ID)) } } .where(a.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(a.NAME.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(a.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchInto(a) .map { it.toDomain() } - override fun findAllAuthorsByNameAndLibrary(search: String, libraryId: String, filterOnLibraryIds: Collection?): List = + override fun findAllAuthorsByNameAndLibrary( + search: String, + libraryId: String, + filterOnLibraryIds: Collection?, + ): List = dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) .from(bmaa) .leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) .where(bmaa.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) .and(s.LIBRARY_ID.eq(libraryId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchInto(bmaa) .map { it.toDomain() } - override fun findAllAuthorsByNameAndCollection(search: String, collectionId: String, filterOnLibraryIds: Collection?): List = + override fun findAllAuthorsByNameAndCollection( + search: String, + collectionId: String, + filterOnLibraryIds: Collection?, + ): List = dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) .from(bmaa) .leftJoin(cs).on(bmaa.SERIES_ID.eq(cs.SERIES_ID)) @@ -67,38 +77,71 @@ class ReferentialDao( .where(bmaa.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) .and(cs.COLLECTION_ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchInto(bmaa) .map { it.toDomain() } - override fun findAllAuthorsByNameAndSeries(search: String, seriesId: String, filterOnLibraryIds: Collection?): List = + override fun findAllAuthorsByNameAndSeries( + search: String, + seriesId: String, + filterOnLibraryIds: Collection?, + ): List = dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) .from(bmaa) .apply { filterOnLibraryIds?.let { leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) } } .where(bmaa.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) .and(bmaa.SERIES_ID.eq(seriesId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(bmaa.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchInto(bmaa) .map { it.toDomain() } - override fun findAllAuthorsByName(search: String?, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + override fun findAllAuthorsByName( + search: String?, + role: String?, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { return findAuthorsByName(search, role, filterOnLibraryIds, pageable, null) } - override fun findAllAuthorsByNameAndLibrary(search: String?, role: String?, libraryId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + override fun findAllAuthorsByNameAndLibrary( + search: String?, + role: String?, + libraryId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.LIBRARY, libraryId)) } - override fun findAllAuthorsByNameAndCollection(search: String?, role: String?, collectionId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + override fun findAllAuthorsByNameAndCollection( + search: String?, + role: String?, + collectionId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.COLLECTION, collectionId)) } - override fun findAllAuthorsByNameAndSeries(search: String?, role: String?, seriesId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + override fun findAllAuthorsByNameAndSeries( + search: String?, + role: String?, + seriesId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.SERIES, seriesId)) } - override fun findAllAuthorsByNameAndReadList(search: String?, role: String?, readListId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + override fun findAllAuthorsByNameAndReadList( + search: String?, + role: String?, + readListId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.READLIST, readListId)) } @@ -114,56 +157,69 @@ class ReferentialDao( val id: String, ) - private fun findAuthorsByName(search: String?, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable, filterBy: FilterBy?): Page { - val query = dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) - .from(bmaa) - .apply { if (filterOnLibraryIds != null || filterBy?.type == FilterByType.LIBRARY) leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) } - .apply { if (filterBy?.type == FilterByType.COLLECTION) leftJoin(cs).on(bmaa.SERIES_ID.eq(cs.SERIES_ID)) } - .apply { - if (filterBy?.type == FilterByType.READLIST) - leftJoin(b).on(bmaa.SERIES_ID.eq(b.SERIES_ID)) - .leftJoin(rb).on(b.ID.eq(rb.BOOK_ID)) - } - .where(noCondition()) - .apply { search?.let { and(bmaa.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) } } - .apply { role?.let { and(bmaa.ROLE.eq(role)) } } - .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .apply { - filterBy?.let { - when (it.type) { - FilterByType.LIBRARY -> and(s.LIBRARY_ID.eq(it.id)) - FilterByType.COLLECTION -> and(cs.COLLECTION_ID.eq(it.id)) - FilterByType.SERIES -> and(bmaa.SERIES_ID.eq(it.id)) - FilterByType.READLIST -> and(rb.READLIST_ID.eq(it.id)) + private fun findAuthorsByName( + search: String?, + role: String?, + filterOnLibraryIds: Collection?, + pageable: Pageable, + filterBy: FilterBy?, + ): Page { + val query = + dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) + .from(bmaa) + .apply { if (filterOnLibraryIds != null || filterBy?.type == FilterByType.LIBRARY) leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) } + .apply { if (filterBy?.type == FilterByType.COLLECTION) leftJoin(cs).on(bmaa.SERIES_ID.eq(cs.SERIES_ID)) } + .apply { + if (filterBy?.type == FilterByType.READLIST) + leftJoin(b).on(bmaa.SERIES_ID.eq(b.SERIES_ID)) + .leftJoin(rb).on(b.ID.eq(rb.BOOK_ID)) + } + .where(noCondition()) + .apply { search?.let { and(bmaa.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) } } + .apply { role?.let { and(bmaa.ROLE.eq(role)) } } + .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } + .apply { + filterBy?.let { + when (it.type) { + FilterByType.LIBRARY -> and(s.LIBRARY_ID.eq(it.id)) + FilterByType.COLLECTION -> and(cs.COLLECTION_ID.eq(it.id)) + FilterByType.SERIES -> and(bmaa.SERIES_ID.eq(it.id)) + FilterByType.READLIST -> and(rb.READLIST_ID.eq(it.id)) + } } } - } val count = dsl.fetchCount(query) - val sort = bmaa.NAME.collate(SqliteUdfDataSource.collationUnicode3) + val sort = bmaa.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3) - val items = query - .orderBy(sort) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(a) - .map { it.toDomain() } + val items = + query + .orderBy(sort) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(a) + .map { it.toDomain() } val pageSort = Sort.by("relevance") return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findAllAuthorsNamesByName(search: String, filterOnLibraryIds: Collection?): List = + override fun findAllAuthorsNamesByName( + search: String, + filterOnLibraryIds: Collection?, + ): List = dsl.selectDistinct(a.NAME) .from(a) .apply { filterOnLibraryIds?.let { leftJoin(b).on(a.BOOK_ID.eq(b.ID)) } } .where(a.NAME.udfStripAccents().containsIgnoreCase(search.stripAccents())) .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(a.NAME.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(a.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetch(a.NAME) override fun findAllAuthorsRoles(filterOnLibraryIds: Collection?): List = @@ -187,26 +243,32 @@ class ReferentialDao( .where(s.LIBRARY_ID.`in`(it)) } } - .orderBy(g.GENRE.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(g.GENRE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(g.GENRE) - override fun findAllGenresByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllGenresByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(g.GENRE) .from(g) .leftJoin(s).on(g.SERIES_ID.eq(s.ID)) .where(s.LIBRARY_ID.eq(libraryId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(g.GENRE.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(g.GENRE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(g.GENRE) - override fun findAllGenresByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllGenresByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(g.GENRE) .from(g) .leftJoin(cs).on(g.SERIES_ID.eq(cs.SERIES_ID)) .apply { filterOnLibraryIds?.let { leftJoin(s).on(g.SERIES_ID.eq(s.ID)) } } .where(cs.COLLECTION_ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(g.GENRE.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(g.GENRE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(g.GENRE) override fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection?): Set = @@ -222,7 +284,10 @@ class ReferentialDao( .sortedBy { it.stripAccents().lowercase() } .toSet() - override fun findAllSeriesAndBookTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesAndBookTagsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(bt.TAG.`as`("tag")) .from(bt) .leftJoin(b).on(bt.BOOK_ID.eq(b.ID)) @@ -239,7 +304,10 @@ class ReferentialDao( .sortedBy { it.stripAccents().lowercase() } .toSet() - override fun findAllSeriesAndBookTagsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesAndBookTagsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(bmat.TAG.`as`("tag")) .from(bmat) .leftJoin(s).on(bmat.SERIES_ID.eq(s.ID)) @@ -267,45 +335,57 @@ class ReferentialDao( .where(s.LIBRARY_ID.`in`(it)) } } - .orderBy(st.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(st.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(st.TAG) - override fun findAllSeriesTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesTagsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(st.TAG) .from(st) .leftJoin(s).on(st.SERIES_ID.eq(s.ID)) .where(s.LIBRARY_ID.eq(libraryId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(st.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(st.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(st.TAG) - override fun findAllBookTagsBySeries(seriesId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllBookTagsBySeries( + seriesId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(bt.TAG) .from(bt) .leftJoin(b).on(bt.BOOK_ID.eq(b.ID)) .where(b.SERIES_ID.eq(seriesId)) .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(bt.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(bt.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(bt.TAG) - override fun findAllBookTagsByReadList(readListId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllBookTagsByReadList( + readListId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(bt.TAG) .from(bt) .leftJoin(b).on(bt.BOOK_ID.eq(b.ID)) .leftJoin(rb).on(bt.BOOK_ID.eq(rb.BOOK_ID)) .where(rb.READLIST_ID.eq(readListId)) .apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } } - .orderBy(bt.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(bt.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(bt.TAG) - override fun findAllSeriesTagsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesTagsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.select(st.TAG) .from(st) .leftJoin(cs).on(st.SERIES_ID.eq(cs.SERIES_ID)) .apply { filterOnLibraryIds?.let { leftJoin(s).on(st.SERIES_ID.eq(s.ID)) } } .where(cs.COLLECTION_ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(st.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(st.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(st.TAG) override fun findAllBookTags(filterOnLibraryIds: Collection?): Set = @@ -317,7 +397,7 @@ class ReferentialDao( .where(b.LIBRARY_ID.`in`(it)) } } - .orderBy(st.TAG.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(st.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(st.TAG) override fun findAllLanguages(filterOnLibraryIds: Collection?): Set = @@ -329,7 +409,10 @@ class ReferentialDao( .orderBy(sd.LANGUAGE) .fetchSet(sd.LANGUAGE) - override fun findAllLanguagesByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllLanguagesByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.LANGUAGE) .from(sd) .leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) @@ -339,7 +422,10 @@ class ReferentialDao( .orderBy(sd.LANGUAGE) .fetchSet(sd.LANGUAGE) - override fun findAllLanguagesByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllLanguagesByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.LANGUAGE) .from(sd) .leftJoin(cs).on(sd.SERIES_ID.eq(cs.SERIES_ID)) @@ -356,44 +442,57 @@ class ReferentialDao( .apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } } .where(sd.PUBLISHER.ne("")) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sd.PUBLISHER) - override fun findAllPublishers(filterOnLibraryIds: Collection?, pageable: Pageable): Page { - val query = dsl.selectDistinct(sd.PUBLISHER) - .from(sd) - .apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } } - .where(sd.PUBLISHER.ne("")) - .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } + override fun findAllPublishers( + filterOnLibraryIds: Collection?, + pageable: Pageable, + ): Page { + val query = + dsl.selectDistinct(sd.PUBLISHER) + .from(sd) + .apply { filterOnLibraryIds?.let { leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) } } + .where(sd.PUBLISHER.ne("")) + .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } val count = dsl.fetchCount(query) - val sort = sd.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3) + val sort = sd.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3) - val items = query - .orderBy(sort) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetch(sd.PUBLISHER) + val items = + query + .orderBy(sort) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetch(sd.PUBLISHER) val pageSort = Sort.by("name") return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findAllPublishersByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllPublishersByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.PUBLISHER) .from(sd) .leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) .where(sd.PUBLISHER.ne("")) .and(s.LIBRARY_ID.eq(libraryId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sd.PUBLISHER) - override fun findAllPublishersByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllPublishersByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.PUBLISHER) .from(sd) .leftJoin(cs).on(sd.SERIES_ID.eq(cs.SERIES_ID)) @@ -401,7 +500,7 @@ class ReferentialDao( .where(sd.PUBLISHER.ne("")) .and(cs.COLLECTION_ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sd.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sd.PUBLISHER) override fun findAllAgeRatings(filterOnLibraryIds: Collection?): Set = @@ -416,7 +515,10 @@ class ReferentialDao( .orderBy(sd.AGE_RATING) .fetchSet(sd.AGE_RATING) - override fun findAllAgeRatingsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllAgeRatingsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.AGE_RATING) .from(sd) .leftJoin(s).on(sd.SERIES_ID.eq(s.ID)) @@ -425,7 +527,10 @@ class ReferentialDao( .orderBy(sd.AGE_RATING) .fetchSet(sd.AGE_RATING) - override fun findAllAgeRatingsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllAgeRatingsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sd.AGE_RATING) .from(sd) .leftJoin(cs).on(sd.SERIES_ID.eq(cs.SERIES_ID)) @@ -444,7 +549,10 @@ class ReferentialDao( .orderBy(bma.RELEASE_DATE.desc()) .fetchSet(bma.RELEASE_DATE) - override fun findAllSeriesReleaseDatesByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesReleaseDatesByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(bma.RELEASE_DATE) .from(bma) .leftJoin(s).on(bma.SERIES_ID.eq(s.ID)) @@ -454,7 +562,10 @@ class ReferentialDao( .orderBy(bma.RELEASE_DATE.desc()) .fetchSet(bma.RELEASE_DATE) - override fun findAllSeriesReleaseDatesByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSeriesReleaseDatesByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(bma.RELEASE_DATE) .from(bma) .leftJoin(cs).on(bma.SERIES_ID.eq(cs.SERIES_ID)) @@ -474,26 +585,32 @@ class ReferentialDao( .where(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sl.LABEL.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sl.LABEL.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sl.LABEL) - override fun findAllSharingLabelsByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSharingLabelsByLibrary( + libraryId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sl.LABEL) .from(sl) .leftJoin(s).on(sl.SERIES_ID.eq(s.ID)) .where(s.LIBRARY_ID.eq(libraryId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sl.LABEL.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sl.LABEL.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sl.LABEL) - override fun findAllSharingLabelsByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set = + override fun findAllSharingLabelsByCollection( + collectionId: String, + filterOnLibraryIds: Collection?, + ): Set = dsl.selectDistinct(sl.LABEL) .from(sl) .leftJoin(cs).on(sl.SERIES_ID.eq(cs.SERIES_ID)) .apply { filterOnLibraryIds?.let { leftJoin(s).on(sl.SERIES_ID.eq(s.ID)) } } .where(cs.COLLECTION_ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .orderBy(sl.LABEL.collate(SqliteUdfDataSource.collationUnicode3)) + .orderBy(sl.LABEL.collate(SqliteUdfDataSource.COLLATION_UNICODE_3)) .fetchSet(sl.LABEL) private fun BookMetadataAuthorRecord.toDomain(): Author = diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDao.kt index d3bd3c0e0..3a076c5fd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDao.kt @@ -35,17 +35,21 @@ class SeriesCollectionDao( private val luceneHelper: LuceneHelper, @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, ) : SeriesCollectionRepository { - private val c = Tables.COLLECTION private val cs = Tables.COLLECTION_SERIES private val s = Tables.SERIES private val sd = Tables.SERIES_METADATA - private val sorts = mapOf( - "name" to c.NAME.collate(SqliteUdfDataSource.collationUnicode3), - ) + private val sorts = + mapOf( + "name" to c.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3), + ) - override fun findByIdOrNull(collectionId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions): SeriesCollection? = + override fun findByIdOrNull( + collectionId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions, + ): SeriesCollection? = selectBase(restrictions.isRestricted) .where(c.ID.eq(collectionId)) .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } @@ -53,17 +57,25 @@ class SeriesCollectionDao( .fetchAndMap(filterOnLibraryIds, restrictions) .firstOrNull() - override fun findAll(belongsToLibraryIds: Collection?, filterOnLibraryIds: Collection?, search: String?, pageable: Pageable, restrictions: ContentRestrictions): Page { + override fun findAll( + belongsToLibraryIds: Collection?, + filterOnLibraryIds: Collection?, + search: String?, + pageable: Pageable, + restrictions: ContentRestrictions, + ): Page { val collectionIds = luceneHelper.searchEntitiesIds(search, LuceneEntity.Collection) val searchCondition = c.ID.inOrNoCondition(collectionIds) - val conditions = searchCondition - .and(s.LIBRARY_ID.inOrNoCondition(belongsToLibraryIds)) - .and(s.LIBRARY_ID.inOrNoCondition(filterOnLibraryIds)) - .and(restrictions.toCondition(dsl)) + val conditions = + searchCondition + .and(s.LIBRARY_ID.inOrNoCondition(belongsToLibraryIds)) + .and(s.LIBRARY_ID.inOrNoCondition(filterOnLibraryIds)) + .and(restrictions.toCondition(dsl)) val queryIds = - if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted) null + if (belongsToLibraryIds == null && filterOnLibraryIds == null && !restrictions.isRestricted) + null else dsl.selectDistinct(c.ID) .from(c) @@ -73,38 +85,50 @@ class SeriesCollectionDao( .where(conditions) val count = - if (queryIds != null) dsl.fetchCount(queryIds) - else dsl.fetchCount(c, searchCondition) + if (queryIds != null) + dsl.fetchCount(queryIds) + else + dsl.fetchCount(c, searchCondition) val orderBy = pageable.sort.mapNotNull { - if (it.property == "relevance" && !collectionIds.isNullOrEmpty()) c.ID.sortByValues(collectionIds, it.isAscending) - else it.toSortField(sorts) + if (it.property == "relevance" && !collectionIds.isNullOrEmpty()) + c.ID.sortByValues(collectionIds, it.isAscending) + else + it.toSortField(sorts) } - val items = selectBase(restrictions.isRestricted) - .where(conditions) - .apply { if (queryIds != null) and(c.ID.`in`(queryIds)) } - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchAndMap(filterOnLibraryIds, restrictions) + val items = + selectBase(restrictions.isRestricted) + .where(conditions) + .apply { if (queryIds != null) and(c.ID.`in`(queryIds)) } + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchAndMap(filterOnLibraryIds, restrictions) val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } - override fun findAllContainingSeriesId(containsSeriesId: String, filterOnLibraryIds: Collection?, restrictions: ContentRestrictions): Collection { - val queryIds = dsl.select(c.ID) - .from(c) - .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) - .apply { if (restrictions.isRestricted) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) } - .where(cs.SERIES_ID.eq(containsSeriesId)) - .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } + override fun findAllContainingSeriesId( + containsSeriesId: String, + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions, + ): Collection { + val queryIds = + dsl.select(c.ID) + .from(c) + .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) + .apply { if (restrictions.isRestricted) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) } + .where(cs.SERIES_ID.eq(containsSeriesId)) + .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } return selectBase(restrictions.isRestricted) .where(c.ID.`in`(queryIds)) @@ -138,19 +162,23 @@ class SeriesCollectionDao( .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) .apply { if (joinOnSeriesMetadata) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) } - private fun ResultQuery.fetchAndMap(filterOnLibraryIds: Collection?, restrictions: ContentRestrictions = ContentRestrictions()): List = + private fun ResultQuery.fetchAndMap( + filterOnLibraryIds: Collection?, + restrictions: ContentRestrictions = ContentRestrictions(), + ): List = fetchInto(c) .map { cr -> - val seriesIds = dsl.select(*cs.fields()) - .from(cs) - .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) - .apply { if (restrictions.isRestricted) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) } - .where(cs.COLLECTION_ID.eq(cr.id)) - .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } - .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } - .orderBy(cs.NUMBER.asc()) - .fetchInto(cs) - .mapNotNull { it.seriesId } + val seriesIds = + dsl.select(*cs.fields()) + .from(cs) + .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) + .apply { if (restrictions.isRestricted) leftJoin(sd).on(cs.SERIES_ID.eq(sd.SERIES_ID)) } + .where(cs.COLLECTION_ID.eq(cr.id)) + .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } + .apply { if (restrictions.isRestricted) and(restrictions.toCondition(dsl)) } + .orderBy(cs.NUMBER.asc()) + .fetchInto(cs) + .mapNotNull { it.seriesId } cr.toDomain(seriesIds) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDao.kt index 32ecb7668..04dfe0054 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDao.kt @@ -47,7 +47,10 @@ class SeriesDao( .map { it.toDomain() } @Transactional - override fun findAllNotDeletedByLibraryIdAndUrlNotIn(libraryId: String, urls: Collection): List { + override fun findAllNotDeletedByLibraryIdAndUrlNotIn( + libraryId: String, + urls: Collection, + ): List { dsl.insertTempStrings(batchSize, urls.map { it.toString() }) return dsl.selectFrom(s) @@ -58,7 +61,10 @@ class SeriesDao( .map { it.toDomain() } } - override fun findNotDeletedByLibraryIdAndUrlOrNull(libraryId: String, url: URL): Series? = + override fun findNotDeletedByLibraryIdAndUrlOrNull( + libraryId: String, + url: URL, + ): Series? = dsl.selectFrom(s) .where(s.LIBRARY_ID.eq(libraryId).and(s.URL.eq(url.toString()))) .and(s.DELETED_DATE.isNull) @@ -111,7 +117,10 @@ class SeriesDao( .execute() } - override fun update(series: Series, updateModifiedTime: Boolean) { + override fun update( + series: Series, + updateModifiedTime: Boolean, + ) { dsl.update(s) .set(s.NAME, series.name) .set(s.URL, series.url.toString()) @@ -156,7 +165,7 @@ class SeriesDao( searchTerm?.let { c = c.and(d.TITLE.containsIgnoreCase(it)) } searchRegex?.let { c = c.and((it.second.toColumn()).likeRegex(it.first)) } if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus)) - if (!publishers.isNullOrEmpty()) c = c.and(d.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3).`in`(publishers)) + if (!publishers.isNullOrEmpty()) c = c.and(d.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(publishers)) if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull) if (deleted == false) c = c.and(s.DELETED_DATE.isNull) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDao.kt index 25cb8a65e..39c27bc58 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDao.kt @@ -61,7 +61,6 @@ class SeriesDtoDao( @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, private val transactionTemplate: TransactionTemplate, ) : SeriesDtoRepository { - private val s = Tables.SERIES private val d = Tables.SERIES_METADATA private val rs = Tables.READ_PROGRESS_SERIES @@ -75,26 +74,33 @@ class SeriesDtoDao( private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR private val bmat = Tables.BOOK_METADATA_AGGREGATION_TAG - private val groupFields = arrayOf( - *s.fields(), - *d.fields(), - *bma.fields(), - *rs.fields(), - ) + private val groupFields = + arrayOf( + *s.fields(), + *d.fields(), + *bma.fields(), + *rs.fields(), + ) - private val sorts = mapOf( - "metadata.titleSort" to d.TITLE_SORT.noCase(), - "createdDate" to s.CREATED_DATE, - "created" to s.CREATED_DATE, - "lastModifiedDate" to s.LAST_MODIFIED_DATE, - "lastModified" to s.LAST_MODIFIED_DATE, - "booksMetadata.releaseDate" to bma.RELEASE_DATE, - "collection.number" to cs.NUMBER, - "name" to s.NAME.collate(SqliteUdfDataSource.collationUnicode3), - "booksCount" to s.BOOK_COUNT, - ) + private val sorts = + mapOf( + "metadata.titleSort" to d.TITLE_SORT.noCase(), + "createdDate" to s.CREATED_DATE, + "created" to s.CREATED_DATE, + "lastModifiedDate" to s.LAST_MODIFIED_DATE, + "lastModified" to s.LAST_MODIFIED_DATE, + "booksMetadata.releaseDate" to bma.RELEASE_DATE, + "collection.number" to cs.NUMBER, + "name" to s.NAME.collate(SqliteUdfDataSource.COLLATION_UNICODE_3), + "booksCount" to s.BOOK_COUNT, + ) - override fun findAll(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable, restrictions: ContentRestrictions): Page { + override fun findAll( + search: SeriesSearchWithReadProgress, + userId: String, + pageable: Pageable, + restrictions: ContentRestrictions, + ): Page { val conditions = search.toCondition().and(restrictions.toCondition(dsl)) return findAll(conditions, userId, pageable, search.toJoinConditions(), search.searchTerm) @@ -119,14 +125,19 @@ class SeriesDtoDao( restrictions: ContentRestrictions, pageable: Pageable, ): Page { - val conditions = search.toCondition() - .and(restrictions.toCondition(dsl)) - .and(s.CREATED_DATE.notEqual(s.LAST_MODIFIED_DATE)) + val conditions = + search.toCondition() + .and(restrictions.toCondition(dsl)) + .and(s.CREATED_DATE.notEqual(s.LAST_MODIFIED_DATE)) return findAll(conditions, userId, pageable, search.toJoinConditions(), search.searchTerm) } - override fun countByFirstCharacter(search: SeriesSearchWithReadProgress, userId: String, restrictions: ContentRestrictions): List { + override fun countByFirstCharacter( + search: SeriesSearchWithReadProgress, + userId: String, + restrictions: ContentRestrictions, + ): List { val conditions = search.toCondition().and(restrictions.toCondition(dsl)) val joinConditions = search.toJoinConditions() val seriesIds = luceneHelper.searchEntitiesIds(search.searchTerm, LuceneEntity.Series) @@ -155,7 +166,10 @@ class SeriesDtoDao( } } - override fun findByIdOrNull(seriesId: String, userId: String): SeriesDto? = + override fun findByIdOrNull( + seriesId: String, + userId: String, + ): SeriesDto? = selectBase(userId) .where(s.ID.eq(seriesId)) .groupBy(*groupFields) @@ -192,42 +206,48 @@ class SeriesDtoDao( val seriesIds = luceneHelper.searchEntitiesIds(searchTerm, LuceneEntity.Series) val searchCondition = s.ID.inOrNoCondition(seriesIds) - val count = dsl.select(countDistinct(s.ID)) - .from(s) - .leftJoin(d).on(s.ID.eq(d.SERIES_ID)) - .leftJoin(bma).on(s.ID.eq(bma.SERIES_ID)) - .leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId)) - .apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) } - .apply { - if (joinConditions.tag) - leftJoin(st).on(s.ID.eq(st.SERIES_ID)) - .leftJoin(bmat).on(s.ID.eq(bmat.SERIES_ID)) - } - .apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) } - .apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) } - .apply { if (joinConditions.sharingLabel) leftJoin(sl).on(s.ID.eq(sl.SERIES_ID)) } - .where(conditions) - .and(searchCondition) - .fetchOne(countDistinct(s.ID)) ?: 0 + val count = + dsl.select(countDistinct(s.ID)) + .from(s) + .leftJoin(d).on(s.ID.eq(d.SERIES_ID)) + .leftJoin(bma).on(s.ID.eq(bma.SERIES_ID)) + .leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId)) + .apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) } + .apply { + if (joinConditions.tag) + leftJoin(st).on(s.ID.eq(st.SERIES_ID)) + .leftJoin(bmat).on(s.ID.eq(bmat.SERIES_ID)) + } + .apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) } + .apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) } + .apply { if (joinConditions.sharingLabel) leftJoin(sl).on(s.ID.eq(sl.SERIES_ID)) } + .where(conditions) + .and(searchCondition) + .fetchOne(countDistinct(s.ID)) ?: 0 val orderBy = pageable.sort.mapNotNull { - if (it.property == "relevance" && !seriesIds.isNullOrEmpty()) s.ID.sortByValues(seriesIds, it.isAscending) - else it.toSortField(sorts) + if (it.property == "relevance" && !seriesIds.isNullOrEmpty()) + s.ID.sortByValues(seriesIds, it.isAscending) + else + it.toSortField(sorts) } - val dtos = selectBase(userId, joinConditions) - .where(conditions) - .and(searchCondition) - .orderBy(orderBy) - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchAndMap() + val dtos = + selectBase(userId, joinConditions) + .where(conditions) + .and(searchCondition) + .orderBy(orderBy) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchAndMap() val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted() return PageImpl( dtos, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) - else PageRequest.of(0, maxOf(count, 20), pageSort), + if (pageable.isPaged) + PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else + PageRequest.of(0, maxOf(count, 20), pageSort), count.toLong(), ) } @@ -247,34 +267,41 @@ class SeriesDtoDao( lateinit var aggregatedTags: Map> transactionTemplate.executeWithoutResult { dsl.insertTempStrings(batchSize, seriesIds) - genres = dsl.selectFrom(g) - .where(g.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { it.genre }) + genres = + dsl.selectFrom(g) + .where(g.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { it.genre }) - tags = dsl.selectFrom(st) - .where(st.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { it.tag }) + tags = + dsl.selectFrom(st) + .where(st.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { it.tag }) - sharingLabels = dsl.selectFrom(sl) - .where(sl.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { it.label }) + sharingLabels = + dsl.selectFrom(sl) + .where(sl.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { it.label }) - links = dsl.selectFrom(slk) - .where(slk.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { WebLinkDto(it.label, it.url) }) + links = + dsl.selectFrom(slk) + .where(slk.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { WebLinkDto(it.label, it.url) }) - alternateTitles = dsl.selectFrom(sat) - .where(sat.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { AlternateTitleDto(it.label, it.title) }) + alternateTitles = + dsl.selectFrom(sat) + .where(sat.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { AlternateTitleDto(it.label, it.title) }) - aggregatedAuthors = dsl.selectFrom(bmaa) - .where(bmaa.SERIES_ID.`in`(dsl.selectTempStrings())) - .filter { it.name != null } - .groupBy({ it.seriesId }, { AuthorDto(it.name, it.role) }) + aggregatedAuthors = + dsl.selectFrom(bmaa) + .where(bmaa.SERIES_ID.`in`(dsl.selectTempStrings())) + .filter { it.name != null } + .groupBy({ it.seriesId }, { AuthorDto(it.name, it.role) }) - aggregatedTags = dsl.selectFrom(bmat) - .where(bmat.SERIES_ID.`in`(dsl.selectTempStrings())) - .groupBy({ it.seriesId }, { it.tag }) + aggregatedTags = + dsl.selectFrom(bmat) + .where(bmat.SERIES_ID.`in`(dsl.selectTempStrings())) + .groupBy({ it.seriesId }, { it.tag }) } return records @@ -305,15 +332,15 @@ class SeriesDtoDao( if (!collectionIds.isNullOrEmpty()) c = c.and(cs.COLLECTION_ID.`in`(collectionIds)) searchRegex?.let { c = c.and((it.second.toColumn()).likeRegex(it.first)) } if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus)) - if (!publishers.isNullOrEmpty()) c = c.and(d.PUBLISHER.collate(SqliteUdfDataSource.collationUnicode3).`in`(publishers)) + if (!publishers.isNullOrEmpty()) c = c.and(d.PUBLISHER.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(publishers)) if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull) if (deleted == false) c = c.and(s.DELETED_DATE.isNull) if (complete == false) c = c.and(d.TOTAL_BOOK_COUNT.isNotNull.and(d.TOTAL_BOOK_COUNT.ne(s.BOOK_COUNT))) if (complete == true) c = c.and(d.TOTAL_BOOK_COUNT.isNotNull.and(d.TOTAL_BOOK_COUNT.eq(s.BOOK_COUNT))) if (oneshot != null) c = c.and(s.ONESHOT.eq(oneshot)) - if (!languages.isNullOrEmpty()) c = c.and(d.LANGUAGE.collate(SqliteUdfDataSource.collationUnicode3).`in`(languages)) - if (!genres.isNullOrEmpty()) c = c.and(g.GENRE.collate(SqliteUdfDataSource.collationUnicode3).`in`(genres)) - if (!tags.isNullOrEmpty()) c = c.and(st.TAG.collate(SqliteUdfDataSource.collationUnicode3).`in`(tags).or(bmat.TAG.collate(SqliteUdfDataSource.collationUnicode3).`in`(tags))) + if (!languages.isNullOrEmpty()) c = c.and(d.LANGUAGE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(languages)) + if (!genres.isNullOrEmpty()) c = c.and(g.GENRE.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(genres)) + if (!tags.isNullOrEmpty()) c = c.and(st.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(tags).or(bmat.TAG.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(tags))) if (!ageRatings.isNullOrEmpty()) { val c1 = if (ageRatings.contains(null)) d.AGE_RATING.isNull else DSL.noCondition() val c2 = if (ageRatings.filterNotNull().isNotEmpty()) d.AGE_RATING.`in`(ageRatings.filterNotNull()) else DSL.noCondition() @@ -328,15 +355,16 @@ class SeriesDtoDao( } c = c.and(ca) } - if (!sharingLabels.isNullOrEmpty()) c = c.and(sl.LABEL.collate(SqliteUdfDataSource.collationUnicode3).`in`(sharingLabels)) + if (!sharingLabels.isNullOrEmpty()) c = c.and(sl.LABEL.collate(SqliteUdfDataSource.COLLATION_UNICODE_3).`in`(sharingLabels)) if (!readStatus.isNullOrEmpty()) { - val cr = readStatus.map { - when (it) { - ReadStatus.UNREAD -> rs.READ_COUNT.isNull - ReadStatus.READ -> rs.READ_COUNT.eq(s.BOOK_COUNT) - ReadStatus.IN_PROGRESS -> rs.READ_COUNT.ne(s.BOOK_COUNT) - } - }.reduce { acc, condition -> acc.or(condition) } + val cr = + readStatus.map { + when (it) { + ReadStatus.UNREAD -> rs.READ_COUNT.isNull + ReadStatus.READ -> rs.READ_COUNT.eq(s.BOOK_COUNT) + ReadStatus.IN_PROGRESS -> rs.READ_COUNT.ne(s.BOOK_COUNT) + } + }.reduce { acc, condition -> acc.or(condition) } c = c.and(cr) } @@ -394,7 +422,13 @@ class SeriesDtoDao( oneshot = oneshot, ) - private fun SeriesMetadataRecord.toDto(genres: Set, tags: Set, sharingLabels: Set, links: List, alternateTitles: List) = + private fun SeriesMetadataRecord.toDto( + genres: Set, + tags: Set, + sharingLabels: Set, + links: List, + alternateTitles: List, + ) = SeriesMetadataDto( status = status, statusLock = statusLock, @@ -428,14 +462,16 @@ class SeriesDtoDao( alternateTitlesLock = alternateTitlesLock, ) - private fun BookMetadataAggregationRecord.toDto(authors: List, tags: Set) = + private fun BookMetadataAggregationRecord.toDto( + authors: List, + tags: Set, + ) = BookMetadataAggregationDto( authors = authors, tags = tags, releaseDate = releaseDate, summary = summary, summaryNumber = summaryNumber, - created = createdDate, lastModified = lastModifiedDate, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDao.kt index e7687b724..d336700bb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDao.kt @@ -22,7 +22,6 @@ class SeriesMetadataDao( private val dsl: DSLContext, @Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, ) : SeriesMetadataRepository { - private val d = Tables.SERIES_METADATA private val g = Tables.SERIES_METADATA_GENRE private val st = Tables.SERIES_METADATA_TAG @@ -265,15 +264,22 @@ class SeriesMetadataDao( override fun count(): Long = dsl.fetchCount(d).toLong() - private fun SeriesMetadataRecord.toDomain(genres: Set, tags: Set, sharingLabels: Set, links: List, alternateTitles: List) = + private fun SeriesMetadataRecord.toDomain( + genres: Set, + tags: Set, + sharingLabels: Set, + links: List, + alternateTitles: List, + ) = SeriesMetadata( status = SeriesMetadata.Status.valueOf(status), title = title, titleSort = titleSort, summary = summary, - readingDirection = readingDirection?.let { - SeriesMetadata.ReadingDirection.valueOf(readingDirection) - }, + readingDirection = + readingDirection?.let { + SeriesMetadata.ReadingDirection.valueOf(readingDirection) + }, publisher = publisher, ageRating = ageRating, language = language, @@ -283,7 +289,6 @@ class SeriesMetadataDao( sharingLabels = sharingLabels, links = links, alternateTitles = alternateTitles, - statusLock = statusLock, titleLock = titleLock, titleSortLock = titleSortLock, @@ -298,9 +303,7 @@ class SeriesMetadataDao( sharingLabelsLock = sharingLabelsLock, linksLock = linksLock, alternateTitlesLock = alternateTitlesLock, - seriesId = seriesId, - createdDate = createdDate.toCurrentTimeZone(), lastModifiedDate = lastModifiedDate.toCurrentTimeZone(), ) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ServerSettingsDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ServerSettingsDao.kt index 38e0f64d0..e6138b3f8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ServerSettingsDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ServerSettingsDao.kt @@ -8,16 +8,21 @@ import org.springframework.stereotype.Component class ServerSettingsDao( private val dsl: DSLContext, ) { - private val s = Tables.SERVER_SETTINGS - fun getSettingByKey(key: String, clazz: Class): T? = + fun getSettingByKey( + key: String, + clazz: Class, + ): T? = dsl.select(s.VALUE) .from(s) .where(s.KEY.eq(key)) .fetchOneInto(clazz) - fun saveSetting(key: String, value: String) { + fun saveSetting( + key: String, + value: String, + ) { dsl.insertInto(s) .values(key, value) .onDuplicateKeyUpdate() @@ -25,11 +30,17 @@ class ServerSettingsDao( .execute() } - fun saveSetting(key: String, value: Boolean) { + fun saveSetting( + key: String, + value: Boolean, + ) { saveSetting(key, value.toString()) } - fun saveSetting(key: String, value: Int) { + fun saveSetting( + key: String, + value: Int, + ) { saveSetting(key, value.toString()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SidecarDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SidecarDao.kt index 4e30b2164..0746c35f0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SidecarDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/SidecarDao.kt @@ -25,7 +25,10 @@ class SidecarDao( override fun findAll(): Collection = dsl.selectFrom(sc).fetch().map { it.toDomain() } - override fun save(libraryId: String, sidecar: Sidecar) { + override fun save( + libraryId: String, + sidecar: Sidecar, + ) { dsl.insertInto(sc) .values( sidecar.url.toString(), @@ -41,7 +44,10 @@ class SidecarDao( } @Transactional - override fun deleteByLibraryIdAndUrls(libraryId: String, urls: Collection) { + override fun deleteByLibraryIdAndUrls( + libraryId: String, + urls: Collection, + ) { dsl.insertTempStrings(batchSize, urls.map { it.toString() }) dsl.deleteFrom(sc) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailBookDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailBookDao.kt index 9d782a6c0..44269edec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailBookDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailBookDao.kt @@ -29,7 +29,10 @@ class ThumbnailBookDao( .fetchInto(tb) .map { it.toDomain() } - override fun findAllByBookIdAndType(bookId: String, type: ThumbnailBook.Type): Collection = + override fun findAllByBookIdAndType( + bookId: String, + type: ThumbnailBook.Type, + ): Collection = dsl.selectFrom(tb) .where(tb.BOOK_ID.eq(bookId)) .and(tb.TYPE.eq(type.toString())) @@ -52,22 +55,27 @@ class ThumbnailBookDao( .firstOrNull() override fun findAllWithoutMetadata(pageable: Pageable): Page { - val query = dsl.selectFrom(tb) - .where(tb.FILE_SIZE.eq(0)) - .or(tb.MEDIA_TYPE.eq("")) - .or(tb.WIDTH.eq(0)) - .or(tb.HEIGHT.eq(0)) + val query = + dsl.selectFrom(tb) + .where(tb.FILE_SIZE.eq(0)) + .or(tb.MEDIA_TYPE.eq("")) + .or(tb.WIDTH.eq(0)) + .or(tb.HEIGHT.eq(0)) val count = dsl.fetchCount(query) - val items = query - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(tb) - .map { it.toDomain() } + val items = + query + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(tb) + .map { it.toDomain() } return PageImpl(items, pageable, count.toLong()) } - override fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan(type: ThumbnailBook.Type, size: Int): Collection = + override fun findAllBookIdsByThumbnailTypeAndDimensionSmallerThan( + type: ThumbnailBook.Type, + size: Int, + ): Collection = dsl.select(tb.BOOK_ID) .from(tb) .where(tb.TYPE.eq(type.toString())) @@ -149,7 +157,10 @@ class ThumbnailBookDao( dsl.deleteFrom(tb).where(tb.BOOK_ID.`in`(dsl.selectTempStrings())).execute() } - override fun deleteByBookIdAndType(bookId: String, type: ThumbnailBook.Type) { + override fun deleteByBookIdAndType( + bookId: String, + type: ThumbnailBook.Type, + ) { dsl.deleteFrom(tb) .where(tb.BOOK_ID.eq(bookId)) .and(tb.TYPE.eq(type.toString())) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailReadListDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailReadListDao.kt index 33a6599ad..f603d8af8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailReadListDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailReadListDao.kt @@ -40,17 +40,19 @@ class ThumbnailReadListDao( .firstOrNull() override fun findAllWithoutMetadata(pageable: Pageable): Page { - val query = dsl.selectFrom(tr) - .where(tr.FILE_SIZE.eq(0)) - .or(tr.MEDIA_TYPE.eq("")) - .or(tr.WIDTH.eq(0)) - .or(tr.HEIGHT.eq(0)) + val query = + dsl.selectFrom(tr) + .where(tr.FILE_SIZE.eq(0)) + .or(tr.MEDIA_TYPE.eq("")) + .or(tr.WIDTH.eq(0)) + .or(tr.HEIGHT.eq(0)) val count = query.count() - val items = query - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(tr) - .map { it.toDomain() } + val items = + query + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(tr) + .map { it.toDomain() } return PageImpl(items, pageable, count.toLong()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesCollectionDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesCollectionDao.kt index 064eba8ca..734039844 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesCollectionDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesCollectionDao.kt @@ -40,17 +40,19 @@ class ThumbnailSeriesCollectionDao( .map { it.toDomain() } override fun findAllWithoutMetadata(pageable: Pageable): Page { - val query = dsl.selectFrom(tc) - .where(tc.FILE_SIZE.eq(0)) - .or(tc.MEDIA_TYPE.eq("")) - .or(tc.WIDTH.eq(0)) - .or(tc.HEIGHT.eq(0)) + val query = + dsl.selectFrom(tc) + .where(tc.FILE_SIZE.eq(0)) + .or(tc.MEDIA_TYPE.eq("")) + .or(tc.WIDTH.eq(0)) + .or(tc.HEIGHT.eq(0)) val count = query.count() - val items = query - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(tc) - .map { it.toDomain() } + val items = + query + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(tc) + .map { it.toDomain() } return PageImpl(items, pageable, count.toLong()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesDao.kt index 0c072723b..60df5a03a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/main/ThumbnailSeriesDao.kt @@ -35,7 +35,10 @@ class ThumbnailSeriesDao( .fetchInto(ts) .map { it.toDomain() } - override fun findAllBySeriesIdIdAndType(seriesId: String, type: ThumbnailSeries.Type): Collection = + override fun findAllBySeriesIdIdAndType( + seriesId: String, + type: ThumbnailSeries.Type, + ): Collection = dsl.selectFrom(ts) .where(ts.SERIES_ID.eq(seriesId)) .and(ts.TYPE.eq(type.toString())) @@ -52,17 +55,19 @@ class ThumbnailSeriesDao( .firstOrNull() override fun findAllWithoutMetadata(pageable: Pageable): Page { - val query = dsl.selectFrom(ts) - .where(ts.FILE_SIZE.eq(0)) - .or(ts.MEDIA_TYPE.eq("")) - .or(ts.WIDTH.eq(0)) - .or(ts.HEIGHT.eq(0)) + val query = + dsl.selectFrom(ts) + .where(ts.FILE_SIZE.eq(0)) + .or(ts.MEDIA_TYPE.eq("")) + .or(ts.WIDTH.eq(0)) + .or(ts.HEIGHT.eq(0)) val count = query.count() - val items = query - .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } - .fetchInto(ts) - .map { it.toDomain() } + val items = + query + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(ts) + .map { it.toDomain() } return PageImpl(items, pageable, count.toLong()) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDao.kt index 2c275ece2..55a69f70e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDao.kt @@ -47,19 +47,20 @@ class TasksDao( @Transactional override fun takeFirst(owner: String): Task? { - val task = selectBase() - .where(tasksAvailableCondition) - .orderBy(t.PRIORITY.desc(), t.LAST_MODIFIED_DATE) - .limit(1) - .fetchOne() - ?.let { - try { - objectMapper.readValue(it.value2(), Class.forName(it.value1())) as Task - } catch (e: Exception) { - logger.error(e) { "Could not deserialize object of type: ${it.value1()}" } - null - } - } ?: return null + val task = + selectBase() + .where(tasksAvailableCondition) + .orderBy(t.PRIORITY.desc(), t.LAST_MODIFIED_DATE) + .limit(1) + .fetchOne() + ?.let { + try { + objectMapper.readValue(it.value2(), Class.forName(it.value1())) as Task + } catch (e: Exception) { + logger.error(e) { "Could not deserialize object of type: ${it.value1()}" } + null + } + } ?: return null dsl.update(t) .set(t.OWNER, owner) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ContentDetector.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ContentDetector.kt index c37adf14b..597ee6b39 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ContentDetector.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/ContentDetector.kt @@ -15,11 +15,11 @@ private val logger = KotlinLogging.logger {} class ContentDetector( private val tika: TikaConfig, ) { - fun detectMediaType(path: Path): String { - val metadata = Metadata().also { - it[Metadata.TIKA_MIME_FILE] = path.name - } + val metadata = + Metadata().also { + it[Metadata.TIKA_MIME_FILE] = path.name + } return TikaInputStream.get(path).use { val mediaType = tika.detector.detect(it, metadata) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/TikaConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/TikaConfiguration.kt index 847aefa71..a31e208b4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/TikaConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/TikaConfiguration.kt @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Configuration @Configuration class TikaConfiguration { - @Bean fun tika() = TikaConfig() } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/DivinaExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/DivinaExtractor.kt index a33ce57ee..e987017c0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/DivinaExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/DivinaExtractor.kt @@ -8,7 +8,13 @@ interface DivinaExtractor { fun mediaTypes(): List @Throws(MediaUnsupportedException::class) - fun getEntries(path: Path, analyzeDimensions: Boolean): List + fun getEntries( + path: Path, + analyzeDimensions: Boolean, + ): List - fun getEntryStream(path: Path, entryName: String): ByteArray + fun getEntryStream( + path: Path, + entryName: String, + ): ByteArray } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/Rar5Extractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/Rar5Extractor.kt index 410f2c9fb..c81503e7b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/Rar5Extractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/Rar5Extractor.kt @@ -37,22 +37,25 @@ class Rar5Extractor( private val contentDetector: ContentDetector, private val imageAnalyzer: ImageAnalyzer, ) : DivinaExtractor { - private val natSortComparator: Comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() override fun mediaTypes(): List = listOf(MediaType.RAR_5.type) - override fun getEntries(path: Path, analyzeDimensions: Boolean): List = + override fun getEntries( + path: Path, + analyzeDimensions: Boolean, + ): List = Archive(path).use { rar -> generateSequence { rar.nextEntry } .map { entry -> try { val buffer = rar.inputStream.use { it.readBytes() } val mediaType = buffer.inputStream().use { contentDetector.detectMediaType(it) } - val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType)) - buffer.inputStream().use { imageAnalyzer.getDimension(it) } - else - null + val dimension = + if (analyzeDimensions && contentDetector.isImage(mediaType)) + buffer.inputStream().use { imageAnalyzer.getDimension(it) } + else + null val fileSize = entry.size MediaContainerEntry(name = entry.name, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } catch (e: Exception) { @@ -64,6 +67,9 @@ class Rar5Extractor( .toList() } - override fun getEntryStream(path: Path, entryName: String): ByteArray = + override fun getEntryStream( + path: Path, + entryName: String, + ): ByteArray = Archive.getInputStream(path, entryName).use { it?.readBytes() ?: ByteArray(0) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractor.kt index 9ce1f6bb6..5279f0949 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractor.kt @@ -18,12 +18,14 @@ class RarExtractor( private val contentDetector: ContentDetector, private val imageAnalyzer: ImageAnalyzer, ) : DivinaExtractor { - private val natSortComparator: Comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() override fun mediaTypes(): List = listOf(MediaType.RAR_GENERIC.type, MediaType.RAR_4.type) - override fun getEntries(path: Path, analyzeDimensions: Boolean): List = + override fun getEntries( + path: Path, + analyzeDimensions: Boolean, + ): List = Archive(path.toFile()).use { rar -> if (rar.isPasswordProtected) throw MediaUnsupportedException("Encrypted RAR archives are not supported", "ERR_1002") if (rar.mainHeader.isSolid) throw MediaUnsupportedException("Solid RAR archives are not supported", "ERR_1003") @@ -34,10 +36,11 @@ class RarExtractor( try { val buffer = rar.getInputStream(entry).use { it.readBytes() } val mediaType = buffer.inputStream().use { contentDetector.detectMediaType(it) } - val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType)) - buffer.inputStream().use { imageAnalyzer.getDimension(it) } - else - null + val dimension = + if (analyzeDimensions && contentDetector.isImage(mediaType)) + buffer.inputStream().use { imageAnalyzer.getDimension(it) } + else + null val fileSize = entry.fullUnpackSize MediaContainerEntry(name = entry.fileName, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } catch (e: Exception) { @@ -48,7 +51,10 @@ class RarExtractor( .sortedWith(compareBy(natSortComparator) { it.name }) } - override fun getEntryStream(path: Path, entryName: String): ByteArray = + override fun getEntryStream( + path: Path, + entryName: String, + ): ByteArray = Archive(path.toFile()).use { rar -> val header = rar.fileHeaders.find { it.fileName == entryName } rar.getInputStream(header).use { it.readBytes() } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractor.kt index 36540bc44..a9ab1b540 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractor.kt @@ -18,12 +18,14 @@ class ZipExtractor( private val contentDetector: ContentDetector, private val imageAnalyzer: ImageAnalyzer, ) : DivinaExtractor { - private val natSortComparator: Comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() override fun mediaTypes(): List = listOf(MediaType.ZIP.type) - override fun getEntries(path: Path, analyzeDimensions: Boolean): List = + override fun getEntries( + path: Path, + analyzeDimensions: Boolean, + ): List = ZipFile(path.toFile()).use { zip -> zip.entries.toList() .filter { !it.isDirectory } @@ -31,10 +33,11 @@ class ZipExtractor( try { zip.getInputStream(entry).buffered().use { stream -> val mediaType = contentDetector.detectMediaType(stream) - val dimension = if (analyzeDimensions && contentDetector.isImage(mediaType)) - imageAnalyzer.getDimension(stream) - else - null + val dimension = + if (analyzeDimensions && contentDetector.isImage(mediaType)) + imageAnalyzer.getDimension(stream) + else + null val fileSize = if (entry.size == ArchiveEntry.SIZE_UNKNOWN) null else entry.size MediaContainerEntry(name = entry.name, mediaType = mediaType, dimension = dimension, fileSize = fileSize) } @@ -46,7 +49,10 @@ class ZipExtractor( .sortedWith(compareBy(natSortComparator) { it.name }) } - override fun getEntryStream(path: Path, entryName: String): ByteArray = + override fun getEntryStream( + path: Path, + entryName: String, + ): ByteArray = ZipFile(path.toFile()).use { zip -> zip.getInputStream(zip.getEntry(entryName)).use { it.readBytes() } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/EpubExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/EpubExtractor.kt index 33a71dc52..83c38d244 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/EpubExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/EpubExtractor.kt @@ -28,11 +28,13 @@ class EpubExtractor( private val imageAnalyzer: ImageAnalyzer, @Value("#{@komgaProperties.epubDivinaLetterCountThreshold}") private val letterCountThreshold: Int, ) { - /** * Retrieves a specific entry by name from the zip archive */ - fun getEntryStream(path: Path, entryName: String): ByteArray = + fun getEntryStream( + path: Path, + entryName: String, + ): ByteArray = ZipFile(path.toFile()).use { zip -> zip.getEntry(entryName)?.let { entry -> zip.getInputStream(entry).use { it.readBytes() } } ?: throw EntryNotFoundException("Entry does not exist: $entryName") @@ -64,10 +66,15 @@ class EpubExtractor( zip.getInputStream(zip.getEntry(coverPath)).readAllBytes(), mediaType, ) - } else null + } else { + null + } } - fun getManifest(path: Path, analyzeDimensions: Boolean): EpubManifest = + fun getManifest( + path: Path, + analyzeDimensions: Boolean, + ): EpubManifest = path.epub { epub -> val (resources, missingResources) = getResources(epub).partition { it.fileSize != null } val isFixedLayout = isFixedLayout(epub) @@ -88,21 +95,23 @@ class EpubExtractor( private fun getResources(epub: EpubPackage): List { val spine = epub.opfDoc.select("spine > itemref").map { it.attr("idref") }.mapNotNull { epub.manifest[it] } - val pages = spine.map { page -> - MediaFile( - normalizeHref(epub.opfDir, page.href), - page.mediaType, - MediaFile.SubType.EPUB_PAGE, - ) - } + val pages = + spine.map { page -> + MediaFile( + normalizeHref(epub.opfDir, page.href), + page.mediaType, + MediaFile.SubType.EPUB_PAGE, + ) + } - val assets = epub.manifest.values.filterNot { spine.contains(it) }.map { - MediaFile( - normalizeHref(epub.opfDir, it.href), - it.mediaType, - MediaFile.SubType.EPUB_ASSET, - ) - } + val assets = + epub.manifest.values.filterNot { spine.contains(it) }.map { + MediaFile( + normalizeHref(epub.opfDir, it.href), + it.mediaType, + MediaFile.SubType.EPUB_ASSET, + ) + } val zipEntries = epub.zip.entries.toList() return (pages + assets).map { resource -> @@ -110,43 +119,54 @@ class EpubExtractor( } } - private fun getDivinaPages(epub: EpubPackage, isFixedLayout: Boolean, pageCount: Int, analyzeDimensions: Boolean): List { + private fun getDivinaPages( + epub: EpubPackage, + isFixedLayout: Boolean, + pageCount: Int, + analyzeDimensions: Boolean, + ): List { if (!isFixedLayout) return emptyList() try { - val pagesWithImages = epub.opfDoc.select("spine > itemref") - .map { it.attr("idref") } - .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } - .map { pagePath -> - val doc = epub.zip.getInputStream(epub.zip.getEntry(pagePath)).use { Jsoup.parse(it, null, "") } + val pagesWithImages = + epub.opfDoc.select("spine > itemref") + .map { it.attr("idref") } + .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } + .map { pagePath -> + val doc = epub.zip.getInputStream(epub.zip.getEntry(pagePath)).use { Jsoup.parse(it, null, "") } - // if a page has text over the threshold then the book is not divina compatible - if (doc.body().text().length > letterCountThreshold) return emptyList() + // if a page has text over the threshold then the book is not divina compatible + if (doc.body().text().length > letterCountThreshold) return emptyList() - val img = doc.getElementsByTag("img") - .map { it.attr("src") } // get the src, which can be a relative path + val img = + doc.getElementsByTag("img") + .map { it.attr("src") } // get the src, which can be a relative path - val svg = doc.select("svg > image[xlink:href]") - .map { it.attr("xlink:href") } // get the source, which can be a relative path + val svg = + doc.select("svg > image[xlink:href]") + .map { it.attr("xlink:href") } // get the source, which can be a relative path - (img + svg).map { (Path(pagePath).parent ?: Path("")).resolve(it).normalize().invariantSeparatorsPathString } // resolve it against the page folder - } + (img + svg).map { (Path(pagePath).parent ?: Path("")).resolve(it).normalize().invariantSeparatorsPathString } // resolve it against the page folder + } if (pagesWithImages.size != pageCount) return emptyList() val imagesPath = pagesWithImages.flatten() if (imagesPath.size != pageCount) return emptyList() - val divinaPages = imagesPath.mapNotNull { imagePath -> - val mediaType = epub.manifest.values.firstOrNull { normalizeHref(epub.opfDir, it.href) == imagePath }?.mediaType ?: return@mapNotNull null - val zipEntry = epub.zip.getEntry(imagePath) - if (!contentDetector.isImage(mediaType)) return@mapNotNull null + val divinaPages = + imagesPath.mapNotNull { imagePath -> + val mediaType = epub.manifest.values.firstOrNull { normalizeHref(epub.opfDir, it.href) == imagePath }?.mediaType ?: return@mapNotNull null + val zipEntry = epub.zip.getEntry(imagePath) + if (!contentDetector.isImage(mediaType)) return@mapNotNull null - val dimension = - if (analyzeDimensions) epub.zip.getInputStream(zipEntry).use { imageAnalyzer.getDimension(it) } - else null - val fileSize = if (zipEntry.size == ArchiveEntry.SIZE_UNKNOWN) null else zipEntry.size - BookPage(fileName = imagePath, mediaType = mediaType, dimension = dimension, fileSize = fileSize) - } + val dimension = + if (analyzeDimensions) + epub.zip.getInputStream(zipEntry).use { imageAnalyzer.getDimension(it) } + else + null + val fileSize = if (zipEntry.size == ArchiveEntry.SIZE_UNKNOWN) null else zipEntry.size + BookPage(fileName = imagePath, mediaType = mediaType, dimension = dimension, fileSize = fileSize) + } if (divinaPages.size != pageCount) return emptyList() return divinaPages @@ -157,9 +177,10 @@ class EpubExtractor( } private fun computePageCount(epub: EpubPackage): Int { - val spine = epub.opfDoc.select("spine > itemref") - .map { it.attr("idref") } - .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } + val spine = + epub.opfDoc.select("spine > itemref") + .map { it.attr("idref") } + .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } return epub.zip.entries.toList().filter { it.name in spine }.sumOf { ceil(it.compressedSize / 1024.0).toInt() } } @@ -168,30 +189,34 @@ class EpubExtractor( epub.opfDoc.selectFirst("metadata > *|meta[property=rendition:layout]")?.text() == "pre-paginated" || epub.opfDoc.selectFirst("metadata > *|meta[name=fixed-layout]")?.attr("content") == "true" - private fun computePositions(resources: List, isFixedLayout: Boolean): List { + private fun computePositions( + resources: List, + isFixedLayout: Boolean, + ): List { val readingOrder = resources.filter { it.subType == MediaFile.SubType.EPUB_PAGE } var startPosition = 1 - val positions = if (isFixedLayout) { - readingOrder.map { - R2Locator( - href = it.fileName, - type = it.mediaType ?: "application/octet-stream", - locations = R2Locator.Location(progression = 0F, position = startPosition++), - ) - } - } else { - readingOrder.flatMap { file -> - val positionCount = maxOf(1, ceil((file.fileSize ?: 0) / 1024.0).roundToInt()) - (0 until positionCount).map { p -> + val positions = + if (isFixedLayout) { + readingOrder.map { R2Locator( - href = file.fileName, - type = file.mediaType ?: "application/octet-stream", - locations = R2Locator.Location(progression = p.toFloat() / positionCount, position = startPosition++), + href = it.fileName, + type = it.mediaType ?: "application/octet-stream", + locations = R2Locator.Location(progression = 0F, position = startPosition++), ) } + } else { + readingOrder.flatMap { file -> + val positionCount = maxOf(1, ceil((file.fileSize ?: 0) / 1024.0).roundToInt()) + (0 until positionCount).map { p -> + R2Locator( + href = file.fileName, + type = file.mediaType ?: "application/octet-stream", + locations = R2Locator.Location(progression = p.toFloat() / positionCount, position = startPosition++), + ) + } + } } - } return positions.map { locator -> val totalProgression = locator.locations?.position?.let { it.toFloat() / positions.size } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Nav.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Nav.kt index 1a7420121..e2b42c56e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Nav.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Nav.kt @@ -13,15 +13,22 @@ fun EpubPackage.getNavResource(): ResourceContent? = zip.getInputStream(zip.getEntry(href)).use { ResourceContent(Path(href), it.readBytes().decodeToString()) } } -fun processNav(document: ResourceContent, navElement: Epub3Nav): List { +fun processNav( + document: ResourceContent, + navElement: Epub3Nav, +): List { val doc = Jsoup.parse(document.content) - val nav = doc.select("nav") - // Jsoup selectors cannot find an attribute with namespace - .firstOrNull { it.attributes().any { attr -> attr.key.endsWith("type") && attr.value == navElement.value } } + val nav = + doc.select("nav") + // Jsoup selectors cannot find an attribute with namespace + .firstOrNull { it.attributes().any { attr -> attr.key.endsWith("type") && attr.value == navElement.value } } return nav?.select(":root > ol > li")?.toList()?.mapNotNull { navLiElementToTocEntry(it, document.path.parent) } ?: emptyList() } -private fun navLiElementToTocEntry(element: Element, navDir: Path?): EpubTocEntry? { +private fun navLiElementToTocEntry( + element: Element, + navDir: Path?, +): EpubTocEntry? { val title = element.selectFirst(":root > a, span")?.text() val href = element.selectFirst(":root > a")?.attr("href")?.let { UriUtils.decode(it, Charsets.UTF_8) } val children = element.select(":root > ol > li").mapNotNull { navLiElementToTocEntry(it, navDir) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Ncx.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Ncx.kt index aee3f250d..8fbb7025c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Ncx.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Ncx.kt @@ -15,13 +15,20 @@ fun EpubPackage.getNcxResource(): ResourceContent? = zip.getInputStream(zip.getEntry(href)).use { ResourceContent(Path(href), it.readBytes().decodeToString()) } } -fun processNcx(document: ResourceContent, navType: Epub2Nav): List = +fun processNcx( + document: ResourceContent, + navType: Epub2Nav, +): List = Jsoup.parse(document.content) .select("${navType.level1} > ${navType.level2}") .toList() .mapNotNull { ncxElementToTocEntry(navType, it, document.path.parent) } -private fun ncxElementToTocEntry(navType: Epub2Nav, element: Element, ncxDir: Path?): EpubTocEntry? { +private fun ncxElementToTocEntry( + navType: Epub2Nav, + element: Element, + ncxDir: Path?, +): EpubTocEntry? { val title = element.selectFirst("navLabel > text")?.text() val href = element.selectFirst("content")?.attr("src")?.let { UriUtils.decode(it, Charsets.UTF_8) } val children = element.select(":root > ${navType.level2}").toList().mapNotNull { ncxElementToTocEntry(navType, it, ncxDir) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Opf.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Opf.kt index e217887be..234fd5f9b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Opf.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/Opf.kt @@ -7,22 +7,30 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.invariantSeparatorsPathString -fun Document.getManifest() = select("manifest > item").associate { - it.attr("id") to ManifestItem( - it.attr("id"), - it.attr("href"), - it.attr("media-type"), - it.attr("properties").split(" ").toSet(), - ) -} +fun Document.getManifest() = + select("manifest > item").associate { + it.attr("id") to + ManifestItem( + it.attr("id"), + it.attr("href"), + it.attr("media-type"), + it.attr("properties").split(" ").toSet(), + ) + } -fun normalizeHref(opfDir: Path?, href: String) = (opfDir?.resolve(href)?.normalize() ?: Paths.get(href)).invariantSeparatorsPathString +fun normalizeHref( + opfDir: Path?, + href: String, +) = (opfDir?.resolve(href)?.normalize() ?: Paths.get(href)).invariantSeparatorsPathString /** * Process an OPF document and extracts TOC entries * from the section. */ -fun processOpfGuide(opf: Document, opfDir: Path?): List { +fun processOpfGuide( + opf: Document, + opfDir: Path?, +): List { val guide = opf.selectFirst("guide") ?: return emptyList() return guide.select("reference").map { ref -> EpubTocEntry( diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/pdf/PdfExtractor.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/pdf/PdfExtractor.kt index ffe24e5d0..316bcd03f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/pdf/PdfExtractor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/mediacontainer/pdf/PdfExtractor.kt @@ -27,7 +27,10 @@ class PdfExtractor( @Qualifier("pdfResolution") private val resolution: Float, ) { - fun getPages(path: Path, analyzeDimensions: Boolean): List = + fun getPages( + path: Path, + analyzeDimensions: Boolean, + ): List = Loader.loadPDF(path.toFile()).use { pdf -> (0 until pdf.numberOfPages).map { index -> val page = pdf.getPage(index) @@ -36,31 +39,42 @@ class PdfExtractor( } } - fun getPageContentAsImage(path: Path, pageNumber: Int): TypedBytes { + fun getPageContentAsImage( + path: Path, + pageNumber: Int, + ): TypedBytes { Loader.loadPDF(path.toFile()).use { pdf -> val page = pdf.getPage(pageNumber - 1) val image = PDFRenderer(pdf).renderImage(pageNumber - 1, page.getScale(), RGB) - val bytes = ByteArrayOutputStream().use { out -> - ImageIO.write(image, imageType.imageIOFormat, out) - out.toByteArray() - } + val bytes = + ByteArrayOutputStream().use { out -> + ImageIO.write(image, imageType.imageIOFormat, out) + out.toByteArray() + } return TypedBytes(bytes, imageType.mediaType) } } - fun getPageContentAsPdf(path: Path, pageNumber: Int): TypedBytes { + fun getPageContentAsPdf( + path: Path, + pageNumber: Int, + ): TypedBytes { Loader.loadPDF(path.toFile()).use { pdf -> - val bytes = ByteArrayOutputStream().use { out -> - PageExtractor(pdf, pageNumber, pageNumber).extract().save(out) - out.toByteArray() - } + val bytes = + ByteArrayOutputStream().use { out -> + PageExtractor(pdf, pageNumber, pageNumber).extract().save(out) + out.toByteArray() + } return TypedBytes(bytes, MediaType.PDF.type) } } private fun PDPage.getScale() = getScale(cropBox.width, cropBox.height) - private fun getScale(width: Float, height: Float) = resolution / minOf(width, height) + private fun getScale( + width: Float, + height: Float, + ) = resolution / minOf(width, height) fun scaleDimension(dimension: Dimension): Dimension { val scale = getScale(dimension.width.toFloat(), dimension.height.toFloat()) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/BookMetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/BookMetadataProvider.kt index efa93c1d6..97f75f5f8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/BookMetadataProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/BookMetadataProvider.kt @@ -6,5 +6,6 @@ import org.gotson.komga.domain.model.BookWithMedia interface BookMetadataProvider : MetadataProvider { val capabilities: Set + fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/MetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/MetadataProvider.kt index 89aa9069f..898ba6c9c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/MetadataProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/MetadataProvider.kt @@ -4,5 +4,8 @@ import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.model.MetadataPatchTarget interface MetadataProvider { - fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean + fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataFromBookProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataFromBookProvider.kt index 8e8ac482a..e75e682a3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataFromBookProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/SeriesMetadataFromBookProvider.kt @@ -5,5 +5,9 @@ import org.gotson.komga.domain.model.SeriesMetadataPatch interface SeriesMetadataFromBookProvider : MetadataProvider { val supportsAppendVolume: Boolean - fun getSeriesMetadataFromBook(book: BookWithMedia, appendVolumeToTitle: Boolean): SeriesMetadataPatch? + + fun getSeriesMetadataFromBook( + book: BookWithMedia, + appendVolumeToTitle: Boolean, + ): SeriesMetadataPatch? } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnBarcodeProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnBarcodeProvider.kt index 96d7f8ac4..dfd4d15a6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnBarcodeProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnBarcodeProvider.kt @@ -30,20 +30,21 @@ class IsbnBarcodeProvider( private val bookAnalyzer: BookAnalyzer, private val validator: ISBNValidator, ) : BookMetadataProvider { - - private val hints = mapOf( - DecodeHintType.POSSIBLE_FORMATS to EnumSet.of(BarcodeFormat.EAN_13), - DecodeHintType.TRY_HARDER to true, - ) + private val hints = + mapOf( + DecodeHintType.POSSIBLE_FORMATS to EnumSet.of(BarcodeFormat.EAN_13), + DecodeHintType.TRY_HARDER to true, + ) override val capabilities = setOf(BookMetadataPatchCapability.ISBN) override fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? { if (book.media.profile == MediaProfile.EPUB) return null - val pagesToTry = (1..book.media.pageCount).toList().let { - (it.takeLast(PAGES_LAST).reversed() + it.take(PAGES_FIRST)).distinct() - } + val pagesToTry = + (1..book.media.pageCount).toList().let { + (it.takeLast(PAGES_LAST).reversed() + it.take(PAGES_FIRST)).distinct() + } for (p in pagesToTry) { try { @@ -53,11 +54,12 @@ class IsbnBarcodeProvider( val source = RGBLuminanceSource(image.width, image.height, pixels) val bitmap = BinaryBitmap(HybridBinarizer(source)) - val result = try { - MultiFormatReader().decode(bitmap, hints) - } catch (e: Exception) { - null - } + val result = + try { + MultiFormatReader().decode(bitmap, hints) + } catch (e: Exception) { + null + } if (result == null || result.text == null) { logger.debug { "Book page $p does not contain a barcode: $book" } @@ -78,7 +80,10 @@ class IsbnBarcodeProvider( return null } - override fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean = + override fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean = when (target) { MetadataPatchTarget.BOOK -> library.importBarcodeIsbn else -> false diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnConfiguration.kt index c52251866..2fbc89f0b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/barcode/IsbnConfiguration.kt @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Configuration @Configuration class IsbnConfiguration { - @Bean fun isbnValidator() = ISBNValidator(true) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProvider.kt index d9289771f..5e0abc26b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProvider.kt @@ -34,23 +34,24 @@ class ComicInfoProvider( private val bookAnalyzer: BookAnalyzer, private val isbnValidator: ISBNValidator, ) : BookMetadataProvider, SeriesMetadataFromBookProvider { - - override val capabilities = setOf( - BookMetadataPatchCapability.TITLE, - BookMetadataPatchCapability.SUMMARY, - BookMetadataPatchCapability.NUMBER, - BookMetadataPatchCapability.NUMBER_SORT, - BookMetadataPatchCapability.RELEASE_DATE, - BookMetadataPatchCapability.AUTHORS, - BookMetadataPatchCapability.READ_LISTS, - BookMetadataPatchCapability.LINKS, - ) + override val capabilities = + setOf( + BookMetadataPatchCapability.TITLE, + BookMetadataPatchCapability.SUMMARY, + BookMetadataPatchCapability.NUMBER, + BookMetadataPatchCapability.NUMBER_SORT, + BookMetadataPatchCapability.RELEASE_DATE, + BookMetadataPatchCapability.AUTHORS, + BookMetadataPatchCapability.READ_LISTS, + BookMetadataPatchCapability.LINKS, + ) 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) - } + val releaseDate = + comicInfo.year?.let { + LocalDate.of(comicInfo.year!!, comicInfo.month ?: 1, comicInfo.day ?: 1) + } val authors = mutableListOf() comicInfo.writer?.splitWithRole("writer")?.let { authors += it } @@ -88,15 +89,16 @@ class ComicInfoProvider( } } - val link = comicInfo.web?.let { - try { - val uri = URI(it) - listOf(WebLink(uri.host, uri)) - } catch (e: Exception) { - logger.error(e) { "Could not parse Web element as valid URI: $it" } - null + val link = + comicInfo.web?.let { + try { + val uri = URI(it) + listOf(WebLink(uri.host, uri)) + } catch (e: Exception) { + logger.error(e) { "Could not parse Web element as valid URI: $it" } + null + } } - } val tags = comicInfo.tags?.split(',')?.mapNotNull { it.trim().lowercase().ifBlank { null } } @@ -120,13 +122,17 @@ class ComicInfoProvider( override val supportsAppendVolume = true - override fun getSeriesMetadataFromBook(book: BookWithMedia, appendVolumeToTitle: Boolean): SeriesMetadataPatch? { + override fun getSeriesMetadataFromBook( + book: BookWithMedia, + appendVolumeToTitle: Boolean, + ): 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 - else -> null - } + val readingDirection = + when (comicInfo.manga) { + Manga.NO -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT + Manga.YES_AND_RIGHT_TO_LEFT -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT + else -> null + } val genres = comicInfo.genre?.split(',')?.mapNotNull { it.trim().ifBlank { null } } val series = if (appendVolumeToTitle) computeSeriesFromSeriesAndVolume(comicInfo.series, comicInfo.volume) else comicInfo.series @@ -148,7 +154,10 @@ class ComicInfoProvider( return null } - override fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean = + override fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean = when (target) { MetadataPatchTarget.BOOK -> library.importComicInfoBook MetadataPatchTarget.SERIES -> library.importComicInfoSeries @@ -177,7 +186,10 @@ class ComicInfoProvider( } } -fun computeSeriesFromSeriesAndVolume(series: String?, volume: Int?): String? = +fun computeSeriesFromSeriesAndVolume( + series: String?, + volume: Int?, +): String? = series?.ifBlank { null }?.let { s -> s + (volume?.let { if (it != 1) " ($it)" else "" } ?: "") } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProvider.kt index c3625dd62..1d331ed3b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProvider.kt @@ -17,22 +17,24 @@ class ReadListProvider( ) { @Throws(ComicRackListException::class) fun importFromCbl(cbl: ByteArray): ReadListRequest { - val readingList = try { - mapper.readValue(cbl, ReadingList::class.java) - } catch (e: Exception) { - logger.error(e) { "Error while trying to parse ComicRack ReadingList" } - throw ComicRackListException("Error while trying to parse ComicRack ReadingList", "ERR_1015") - } + val readingList = + try { + mapper.readValue(cbl, ReadingList::class.java) + } catch (e: Exception) { + logger.error(e) { "Error while trying to parse ComicRack ReadingList" } + throw ComicRackListException("Error while trying to parse ComicRack ReadingList", "ERR_1015") + } logger.debug { "Trying to convert ComicRack ReadingList to ReadListRequest: $readingList" } if (readingList.name.isNullOrBlank()) throw ComicRackListException("ReadingList has no Name element", "ERR_1030") if (readingList.books.isEmpty()) throw ComicRackListException("ReadingList does not contain any Book element", "ERR_1029") - val books = readingList.books.map { - if (it.series.isNullOrBlank() || it.number == null) throw ComicRackListException("Book is missing series or number: $it", "ERR_1031") - val series = setOfNotNull(computeSeriesFromSeriesAndVolume(it.series, it.volume), it.series?.ifBlank { null }) - ReadListRequestBook(series, it.number!!.trim()) - } + val books = + readingList.books.map { + if (it.series.isNullOrBlank() || it.number == null) throw ComicRackListException("Book is missing series or number: $it", "ERR_1031") + val series = setOfNotNull(computeSeriesFromSeriesAndVolume(it.series, it.volume), it.series?.ifBlank { null }) + ReadListRequestBook(series, it.number!!.trim()) + } return ReadListRequest(name = readingList.name!!, books = books) .also { logger.debug { "Converted request: $it" } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfo.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfo.kt index bc962fd18..0d47dccbe 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfo.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfo.kt @@ -6,7 +6,6 @@ import jakarta.xml.bind.annotation.XmlSchemaType @JsonIgnoreProperties(ignoreUnknown = true) class ComicInfo { - @JsonProperty(value = "Title") var title: String? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingList.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingList.kt index 7b917a747..84a04f26f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingList.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingList.kt @@ -9,7 +9,6 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty @JsonIgnoreProperties(ignoreUnknown = true) class ReadingList { - @JsonProperty(value = "Name") var name: String? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt index 14b3f417d..e42935f12 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProvider.kt @@ -26,24 +26,25 @@ import java.time.format.DateTimeFormatter class EpubMetadataProvider( private val isbnValidator: ISBNValidator, ) : BookMetadataProvider, SeriesMetadataFromBookProvider { + private val relators = + mapOf( + "aut" to "writer", + "clr" to "colorist", + "cov" to "cover", + "edt" to "editor", + "art" to "penciller", + "ill" to "penciller", + "trl" to "translator", + ) - private val relators = mapOf( - "aut" to "writer", - "clr" to "colorist", - "cov" to "cover", - "edt" to "editor", - "art" to "penciller", - "ill" to "penciller", - "trl" to "translator", - ) - - override val capabilities = setOf( - BookMetadataPatchCapability.TITLE, - BookMetadataPatchCapability.SUMMARY, - BookMetadataPatchCapability.RELEASE_DATE, - BookMetadataPatchCapability.AUTHORS, - BookMetadataPatchCapability.ISBN, - ) + override val capabilities = + setOf( + BookMetadataPatchCapability.TITLE, + BookMetadataPatchCapability.SUMMARY, + BookMetadataPatchCapability.RELEASE_DATE, + BookMetadataPatchCapability.AUTHORS, + BookMetadataPatchCapability.ISBN, + ) override fun getBookMetadataFromBook(book: BookWithMedia): BookMetadataPatch? { if (book.media.mediaType != MediaType.EPUB.type) return null @@ -54,23 +55,27 @@ class EpubMetadataProvider( val description = opf.selectFirst("metadata > dc|description")?.text()?.let { Jsoup.clean(it, Safelist.none()) }?.ifBlank { null } val date = opf.selectFirst("metadata > dc|date")?.text()?.let { parseDate(it) } - val authorRoles = opf.select("metadata > *|meta[property=role][scheme=marc:relators]") - .associate { it.attr("refines").removePrefix("#") to it.text() } - val authors = opf.select("metadata > dc|creator") - .mapNotNull { el -> - val name = el.text().trim() - if (name.isBlank()) null - else { - val opfRole = el.attr("opf:role").ifBlank { null } - val id = el.attr("id").ifBlank { null } - val refineRole = authorRoles[id]?.ifBlank { null } - Author(name, relators[opfRole ?: refineRole] ?: "writer") - } - }.ifEmpty { null } + val authorRoles = + opf.select("metadata > *|meta[property=role][scheme=marc:relators]") + .associate { it.attr("refines").removePrefix("#") to it.text() } + val authors = + opf.select("metadata > dc|creator") + .mapNotNull { el -> + val name = el.text().trim() + if (name.isBlank()) { + null + } else { + val opfRole = el.attr("opf:role").ifBlank { null } + val id = el.attr("id").ifBlank { null } + val refineRole = authorRoles[id]?.ifBlank { null } + Author(name, relators[opfRole ?: refineRole] ?: "writer") + } + }.ifEmpty { null } - val isbn = opf.select("metadata > dc|identifier") - .map { it.text().lowercase().removePrefix("isbn:") } - .firstNotNullOfOrNull { isbnValidator.validate(it) } + val isbn = + opf.select("metadata > dc|identifier") + .map { it.text().lowercase().removePrefix("isbn:") } + .firstNotNullOfOrNull { isbnValidator.validate(it) } return BookMetadataPatch( title = title, @@ -85,7 +90,10 @@ class EpubMetadataProvider( override val supportsAppendVolume = false - override fun getSeriesMetadataFromBook(book: BookWithMedia, appendVolumeToTitle: Boolean): SeriesMetadataPatch? { + override fun getSeriesMetadataFromBook( + book: BookWithMedia, + appendVolumeToTitle: Boolean, + ): SeriesMetadataPatch? { if (book.media.mediaType != MediaType.EPUB.type) return null getPackageFile(book.book.path)?.let { packageFile -> val opf = Jsoup.parse(packageFile, "", Parser.xmlParser()) @@ -93,18 +101,20 @@ class EpubMetadataProvider( val series = opf.selectFirst("metadata > *|meta[property=belongs-to-collection]")?.text()?.ifBlank { null } val publisher = opf.selectFirst("metadata > dc|publisher")?.text()?.ifBlank { null } val language = opf.selectFirst("metadata > dc|language")?.text()?.ifBlank { null } - val genres = opf.select("metadata > dc|subject") - .mapNotNull { it?.text()?.trim()?.ifBlank { null } } - .toSet() - .ifEmpty { null } + val genres = + opf.select("metadata > dc|subject") + .mapNotNull { it?.text()?.trim()?.ifBlank { null } } + .toSet() + .ifEmpty { null } - val direction = opf.getElementsByTag("spine").first()?.attr("page-progression-direction")?.let { - when (it) { - "rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT - "ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT - else -> null + val direction = + opf.getElementsByTag("spine").first()?.attr("page-progression-direction")?.let { + when (it) { + "rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT + "ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT + else -> null + } } - } return SeriesMetadataPatch( title = series, @@ -123,7 +133,10 @@ class EpubMetadataProvider( return null } - override fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean = + override fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean = when (target) { MetadataPatchTarget.BOOK -> library.importEpubBook MetadataPatchTarget.SERIES -> library.importEpubSeries diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProvider.kt index 0f1997e45..d6003bf8f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProvider.kt @@ -27,7 +27,6 @@ class LocalArtworkProvider( private val contentDetector: ContentDetector, private val imageAnalyzer: ImageAnalyzer, ) : SidecarSeriesConsumer, SidecarBookConsumer { - val supportedExtensions = listOf("png", "jpeg", "jpg", "tbn", "webp") val supportedSeriesFiles = listOf("cover", "default", "folder", "poster", "series") @@ -93,7 +92,10 @@ class LocalArtworkProvider( override fun getSidecarBookPrefilter(): List = supportedExtensions.map { ext -> ".*(-\\d+)?\\.$ext".toRegex(RegexOption.IGNORE_CASE) } - override fun isSidecarBookMatch(basename: String, sidecar: String): Boolean = + override fun isSidecarBookMatch( + basename: String, + sidecar: String, + ): Boolean = "${Regex.escape(basename)}(-\\d+)?".toRegex(RegexOption.IGNORE_CASE).matches(FilenameUtils.getBaseName(sidecar)) override fun getSidecarSeriesType(): Sidecar.Type = Sidecar.Type.ARTWORK diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProvider.kt index 64b92b811..f1fe632fc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProvider.kt @@ -24,7 +24,6 @@ private const val SERIES_JSON = "series.json" class MylarSeriesProvider( private val mapper: ObjectMapper, ) : SeriesMetadataProvider, SidecarSeriesConsumer { - override fun getSeriesMetadata(series: Series): SeriesMetadataPatch? { if (series.oneshot) { logger.debug { "Disabled for oneshot series, skipping" } @@ -39,16 +38,20 @@ class MylarSeriesProvider( } val metadata = mapper.readValue(seriesJsonPath.toFile(), MylarSeries::class.java).metadata - val title = if (metadata.volume == null || metadata.volume == 1) metadata.name - else "${metadata.name} (${metadata.year})" + val title = + if (metadata.volume == null || metadata.volume == 1) + metadata.name + else + "${metadata.name} (${metadata.year})" return SeriesMetadataPatch( title = title, titleSort = title.stripAccents(), - status = when (metadata.status) { - Status.Ended -> SeriesMetadata.Status.ENDED - Status.Continuing -> SeriesMetadata.Status.ONGOING - }, + status = + when (metadata.status) { + Status.Ended -> SeriesMetadata.Status.ENDED + Status.Continuing -> SeriesMetadata.Status.ONGOING + }, summary = metadata.descriptionFormatted ?: metadata.descriptionText, readingDirection = null, publisher = metadata.publisher, @@ -64,7 +67,10 @@ class MylarSeriesProvider( } } - override fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean = + override fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean = when (target) { MetadataPatchTarget.SERIES -> library.importMylarSeries else -> false diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/AgeRating.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/AgeRating.kt index d4b6ce0c6..eba4801f9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/AgeRating.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/AgeRating.kt @@ -2,7 +2,10 @@ package org.gotson.komga.infrastructure.metadata.mylar.dto import com.fasterxml.jackson.annotation.JsonValue -enum class AgeRating(@get:JsonValue val value: String, val ageRating: Int? = null) { +enum class AgeRating( + @get:JsonValue val value: String, + val ageRating: Int? = null, +) { ALL("All", 0), NINE("9+", 9), TWELVE("12+", 12), diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/MylarMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/MylarMetadata.kt index 435842a20..f8e420bfd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/MylarMetadata.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/MylarMetadata.kt @@ -7,40 +7,26 @@ import com.fasterxml.jackson.annotation.JsonProperty @JsonIgnoreProperties(ignoreUnknown = true) data class MylarMetadata( val type: String, - val publisher: String, - val imprint: String?, - val name: String, - @field:JsonAlias("cid") val comicid: String, - val year: Int, - @field:JsonProperty("description_text") val descriptionText: String?, - @field:JsonProperty("description_formatted") val descriptionFormatted: String?, - val volume: Int?, - @field:JsonProperty("booktype") val bookType: String, - @field:JsonProperty("age_rating") val ageRating: AgeRating?, - @field:JsonProperty("ComicImage") val comicImage: String, - @field:JsonProperty("total_issues") val totalIssues: Int, - @field:JsonProperty("publication_run") val publicationRun: String, - val status: Status, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/Status.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/Status.kt index f5d0323b8..e40fa09a9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/Status.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/Status.kt @@ -1,5 +1,6 @@ package org.gotson.komga.infrastructure.metadata.mylar.dto enum class Status { - Ended, Continuing + Ended, + Continuing, } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/oneshot/OneShotSeriesProvider.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/oneshot/OneShotSeriesProvider.kt index edc483501..eaf53f9c9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/oneshot/OneShotSeriesProvider.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/metadata/oneshot/OneShotSeriesProvider.kt @@ -33,6 +33,9 @@ class OneShotSeriesProvider( ) } - override fun shouldLibraryHandlePatch(library: Library, target: MetadataPatchTarget): Boolean = + override fun shouldLibraryHandlePatch( + library: Library, + target: MetadataPatchTarget, + ): Boolean = target == MetadataPatchTarget.SERIES } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneConfiguration.kt index c92d869b9..94ca9885d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneConfiguration.kt @@ -14,7 +14,6 @@ import java.nio.file.Paths class LuceneConfiguration( private val komgaProperties: KomgaProperties, ) { - @Bean fun indexAnalyzer() = with(komgaProperties.lucene.indexAnalyzer) { diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneHelper.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneHelper.kt index c468fae04..7bc0f46c8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneHelper.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneHelper.kt @@ -44,10 +44,11 @@ class LuceneHelper( fun indexExists(): Boolean = DirectoryReader.indexExists(directory) fun setIndexVersion(version: Int) { - val doc = Document().apply { - add(StringField("index_version", version.toString(), Field.Store.YES)) - add(StringField("type", "index_version", Field.Store.NO)) - } + val doc = + Document().apply { + add(StringField("index_version", version.toString(), Field.Store.YES)) + add(StringField("type", "index_version", Field.Store.NO)) + } updateDocument(Term("type", "index_version"), doc) logger.info { "Lucene index version: ${getIndexVersion()}" } } @@ -58,19 +59,24 @@ class LuceneHelper( return topDocs.scoreDocs.map { searcher.storedFields().document(it.doc)["index_version"] }.firstOrNull()?.toIntOrNull() ?: 1 } - fun searchEntitiesIds(searchTerm: String?, entity: LuceneEntity): List? { + fun searchEntitiesIds( + searchTerm: String?, + entity: LuceneEntity, + ): List? { return if (!searchTerm.isNullOrBlank()) { try { - val fieldsQuery = MultiFieldQueryParser(entity.defaultFields, searchAnalyzer).apply { - defaultOperator = QueryParser.Operator.AND - }.parse("$searchTerm *:*") + val fieldsQuery = + MultiFieldQueryParser(entity.defaultFields, searchAnalyzer).apply { + defaultOperator = QueryParser.Operator.AND + }.parse("$searchTerm *:*") val typeQuery = TermQuery(Term(LuceneEntity.TYPE, entity.type)) - val booleanQuery = BooleanQuery.Builder() - .add(fieldsQuery, BooleanClause.Occur.MUST) - .add(typeQuery, BooleanClause.Occur.MUST) - .build() + val booleanQuery = + BooleanQuery.Builder() + .add(fieldsQuery, BooleanClause.Occur.MUST) + .add(typeQuery, BooleanClause.Occur.MUST) + .build() val searcher = searcherManager.acquire() val topDocs = searcher.search(booleanQuery, Int.MAX_VALUE) @@ -81,7 +87,9 @@ class LuceneHelper( logger.error(e) { "Error fetching entities from index" } emptyList() } - } else null + } else { + null + } } fun upgradeIndex() { @@ -99,7 +107,10 @@ class LuceneHelper( commitAndMaybeRefresh() } - fun updateDocument(term: Term, doc: Document) { + fun updateDocument( + term: Term, + doc: Document, + ) { indexWriter.updateDocument(term, doc) commitAndMaybeRefresh() } @@ -112,10 +123,11 @@ class LuceneHelper( @Volatile private var commitFuture: ScheduledFuture<*>? = null - private val commitRunnable = Runnable { - indexWriter.commit() - searcherManager.maybeRefresh() - } + private val commitRunnable = + Runnable { + indexWriter.commit() + searcherManager.maybeRefresh() + } private fun commitAndMaybeRefresh() { if (commitFuture == null || commitFuture!!.isDone) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/MultiLingualAnalyzer.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/MultiLingualAnalyzer.kt index 7dc43c5e4..592403ffe 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/MultiLingualAnalyzer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/MultiLingualAnalyzer.kt @@ -20,7 +20,10 @@ open class MultiLingualAnalyzer : Analyzer() { return TokenStreamComponents(source, filter) } - override fun normalize(fieldName: String?, `in`: TokenStream): TokenStream { + override fun normalize( + fieldName: String?, + `in`: TokenStream, + ): TokenStream { var filter: TokenStream = CJKWidthFilter(`in`) filter = LowerCaseFilter(filter) filter = ASCIIFoldingFilter(filter) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycle.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycle.kt index acc80acc9..ac85a03ec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycle.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycle.kt @@ -33,7 +33,6 @@ class SearchIndexLifecycle( private val seriesDtoRepository: SeriesDtoRepository, private val luceneHelper: LuceneHelper, ) { - fun upgradeIndex() { luceneHelper.upgradeIndex() luceneHelper.setIndexVersion(INDEX_VERSION) @@ -56,7 +55,11 @@ class SearchIndexLifecycle( luceneHelper.setIndexVersion(INDEX_VERSION) } - private fun rebuildIndex(entity: LuceneEntity, provider: (Pageable) -> Page, toDoc: (T) -> Document?) { + private fun rebuildIndex( + entity: LuceneEntity, + provider: (Pageable) -> Page, + toDoc: (T) -> Document?, + ) { logger.info { "Rebuilding index for ${entity.name}" } val count = provider(Pageable.ofSize(1)).totalElements @@ -69,8 +72,9 @@ class SearchIndexLifecycle( (0 until pages).forEach { page -> logger.info { "Processing page ${page + 1} of $pages ($batchSize elements)" } - val entityDocs = provider(PageRequest.of(page, batchSize)).content - .mapNotNull { toDoc(it) } + val entityDocs = + provider(PageRequest.of(page, batchSize)).content + .mapNotNull { toDoc(it) } luceneHelper.addDocuments(entityDocs) } }.also { duration -> @@ -104,17 +108,26 @@ class SearchIndexLifecycle( private fun BookDto.bookToDocument(): Document = if (this.oneshot) { seriesDtoRepository.findByIdOrNull(seriesId, "unused")!!.oneshotDocument(toDocument()) - } else this.toDocument() + } else { + this.toDocument() + } private fun addEntity(doc: Document) { luceneHelper.addDocument(doc) } - private fun updateEntity(entity: LuceneEntity, entityId: String, newDoc: Document) { + private fun updateEntity( + entity: LuceneEntity, + entityId: String, + newDoc: Document, + ) { luceneHelper.updateDocument(Term(entity.id, entityId), newDoc) } - private fun deleteEntity(entity: LuceneEntity, entityId: String) { + private fun deleteEntity( + entity: LuceneEntity, + entityId: String, + ) { luceneHelper.deleteDocuments(Term(entity.id, entityId)) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/CorsConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/CorsConfiguration.kt index b10852b7b..485ac04a8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/CorsConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/CorsConfiguration.kt @@ -19,10 +19,12 @@ import java.util.Collections @Configuration class CorsConfiguration { - @Bean @Conditional(CorsAllowedOriginsPresent::class) - fun corsConfigurationSource(sessionHeaderName: String, komgaProperties: KomgaProperties): UrlBasedCorsConfigurationSource = + fun corsConfigurationSource( + sessionHeaderName: String, + komgaProperties: KomgaProperties, + ): UrlBasedCorsConfigurationSource = UrlBasedCorsConfigurationSource().apply { registerCorsConfiguration( "/**", @@ -37,11 +39,15 @@ class CorsConfiguration { } class CorsAllowedOriginsPresent : SpringBootCondition() { - override fun getMatchOutcome(context: ConditionContext, metadata: AnnotatedTypeMetadata): ConditionOutcome { - val defined = Binder.get(context.environment) - .bind(ConfigurationPropertyName.of("komga.cors.allowed-origins"), Bindable.of(List::class.java)) - .orElse(Collections.emptyList()) - .isNotEmpty() + override fun getMatchOutcome( + context: ConditionContext, + metadata: AnnotatedTypeMetadata, + ): ConditionOutcome { + val defined = + Binder.get(context.environment) + .bind(ConfigurationPropertyName.of("komga.cors.allowed-origins"), Bindable.of(List::class.java)) + .orElse(Collections.emptyList()) + .isNotEmpty() return ConditionOutcome(defined, "Cors allowed-origins present") } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaPrincipal.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaPrincipal.kt index 0599b2b0a..9de1d84a8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaPrincipal.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaPrincipal.kt @@ -14,7 +14,6 @@ class KomgaPrincipal( val oAuth2User: OAuth2User? = null, val oidcUser: OidcUser? = null, ) : UserDetails, OAuth2User, OidcUser { - override fun getAuthorities(): MutableCollection = user.roles .map { SimpleGrantedAuthority("ROLE_$it") } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaUserDetailsService.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaUserDetailsService.kt index 03d47ab80..51330f0c7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaUserDetailsService.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/KomgaUserDetailsService.kt @@ -10,7 +10,6 @@ import org.springframework.stereotype.Component class KomgaUserDetailsService( private val userRepository: KomgaUserRepository, ) : UserDetailsService { - override fun loadUserByUsername(username: String): UserDetails = userRepository.findByEmailIgnoreCaseOrNull(username)?.let { KomgaPrincipal(it) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/LoginListener.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/LoginListener.kt index fde5fb37c..635bf5758 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/LoginListener.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/LoginListener.kt @@ -22,24 +22,25 @@ class LoginListener( private val authenticationActivityRepository: AuthenticationActivityRepository, private val userRepository: KomgaUserRepository, ) { - @EventListener fun onSuccess(event: AuthenticationSuccessEvent) { val user = (event.authentication.principal as KomgaPrincipal).user - val source = when (event.source) { - is OAuth2LoginAuthenticationToken -> "OAuth2:${(event.source as OAuth2LoginAuthenticationToken).clientRegistration.clientName}" - is UsernamePasswordAuthenticationToken -> "Password" - is RememberMeAuthenticationToken -> "RememberMe" - else -> null - } - val activity = AuthenticationActivity( - userId = user.id, - email = user.email, - ip = event.getIp(), - userAgent = event.getUserAgent(), - success = true, - source = source, - ) + val source = + when (event.source) { + is OAuth2LoginAuthenticationToken -> "OAuth2:${(event.source as OAuth2LoginAuthenticationToken).clientRegistration.clientName}" + is UsernamePasswordAuthenticationToken -> "Password" + is RememberMeAuthenticationToken -> "RememberMe" + else -> null + } + val activity = + AuthenticationActivity( + userId = user.id, + email = user.email, + ip = event.getIp(), + userAgent = event.getUserAgent(), + success = true, + source = source, + ) logger.info { activity } authenticationActivityRepository.insert(activity) @@ -48,21 +49,23 @@ class LoginListener( @EventListener fun onFailure(event: AbstractAuthenticationFailureEvent) { val user = event.authentication?.principal?.toString().orEmpty() - val source = when (event.source) { - is OAuth2LoginAuthenticationToken -> "OAuth2:${(event.source as OAuth2LoginAuthenticationToken).clientRegistration.clientName}" - is UsernamePasswordAuthenticationToken -> "Password" - is RememberMeAuthenticationToken -> "RememberMe" - else -> null - } - val activity = AuthenticationActivity( - userId = userRepository.findByEmailIgnoreCaseOrNull(user)?.id, - email = user, - ip = event.getIp(), - userAgent = event.getUserAgent(), - success = false, - source = source, - error = event.exception.message, - ) + val source = + when (event.source) { + is OAuth2LoginAuthenticationToken -> "OAuth2:${(event.source as OAuth2LoginAuthenticationToken).clientRegistration.clientName}" + is UsernamePasswordAuthenticationToken -> "Password" + is RememberMeAuthenticationToken -> "RememberMe" + else -> null + } + val activity = + AuthenticationActivity( + userId = userRepository.findByEmailIgnoreCaseOrNull(user)?.id, + email = user, + ip = event.getIp(), + userAgent = event.getUserAgent(), + success = false, + source = source, + error = event.exception.message, + ) logger.info { activity } authenticationActivityRepository.insert(activity) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/SecurityConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/SecurityConfiguration.kt index 024ad4fec..c5c515dde 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/SecurityConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/SecurityConfiguration.kt @@ -43,7 +43,6 @@ class SecurityConfiguration( private val sessionRegistry: SessionRegistry, clientRegistrationRepository: InMemoryClientRegistrationRepository?, ) { - private val oauth2Enabled = clientRegistrationRepository != null @Bean @@ -115,10 +114,11 @@ class SecurityConfiguration( oauth2.loginPage("/login") .defaultSuccessUrl("/?server_redirect=Y", true) .failureHandler { request, response, exception -> - val errorMessage = when (exception) { - is OAuth2AuthenticationException -> exception.error.errorCode - else -> exception.message - } + val errorMessage = + when (exception) { + is OAuth2AuthenticationException -> exception.error.errorCode + else -> exception.message + } val url = "/login?server_redirect=Y&error=$errorMessage" SimpleUrlAuthenticationFailureHandler(url).onAuthenticationFailure(request, response, exception) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/GithubOAuth2UserService.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/GithubOAuth2UserService.kt index 0cef8e979..548523325 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/GithubOAuth2UserService.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/GithubOAuth2UserService.kt @@ -28,25 +28,27 @@ class GithubOAuth2UserService : DefaultOAuth2UserService() { oAuth2User.getAttribute("email") == null ) { try { - val email = RestTemplate().exchange( - RequestEntity( - HttpHeaders().apply { setBearerAuth(userRequest.accessToken.tokenValue) }, - HttpMethod.GET, - UriComponentsBuilder.fromUriString("${userRequest.clientRegistration.providerDetails.userInfoEndpoint.uri}/emails").build().toUri(), - ), - parameterizedResponseType, - ) - .body?.let { emails -> - emails - .filter { it["verified"] == true } - .filter { it["primary"] == true } - .firstNotNullOfOrNull { it["email"].toString() } - } - oAuth2User = DefaultOAuth2User( - oAuth2User.authorities, - oAuth2User.attributes.toMutableMap().apply { put("email", email) }, - userRequest.clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName, - ) + val email = + RestTemplate().exchange( + RequestEntity( + HttpHeaders().apply { setBearerAuth(userRequest.accessToken.tokenValue) }, + HttpMethod.GET, + UriComponentsBuilder.fromUriString("${userRequest.clientRegistration.providerDetails.userInfoEndpoint.uri}/emails").build().toUri(), + ), + parameterizedResponseType, + ) + .body?.let { emails -> + emails + .filter { it["verified"] == true } + .filter { it["primary"] == true } + .firstNotNullOfOrNull { it["email"].toString() } + } + oAuth2User = + DefaultOAuth2User( + oAuth2User.authorities, + oAuth2User.attributes.toMutableMap().apply { put("email", email) }, + userRequest.clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName, + ) } catch (e: Exception) { logger.warn { "Could not retrieve emails" } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/KomgaOAuth2UserServiceConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/KomgaOAuth2UserServiceConfiguration.kt index af758065a..32a984407 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/KomgaOAuth2UserServiceConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/oauth2/KomgaOAuth2UserServiceConfiguration.kt @@ -26,25 +26,27 @@ class KomgaOAuth2UserServiceConfiguration( private val userLifecycle: KomgaUserLifecycle, private val komgaProperties: KomgaProperties, ) { - @Bean fun oauth2UserService(): OAuth2UserService { val defaultDelegate = DefaultOAuth2UserService() val githubDelegate = GithubOAuth2UserService() return OAuth2UserService { userRequest: OAuth2UserRequest -> - val delegate = when (userRequest.clientRegistration.registrationId.lowercase()) { - "github" -> githubDelegate - else -> defaultDelegate - } + val delegate = + when (userRequest.clientRegistration.registrationId.lowercase()) { + "github" -> githubDelegate + else -> defaultDelegate + } val oAuth2User = delegate.loadUser(userRequest) - val email = oAuth2User.getAttribute("email") - ?: throw OAuth2AuthenticationException("ERR_1024") + val email = + oAuth2User.getAttribute("email") + ?: throw OAuth2AuthenticationException("ERR_1024") - val existingUser = userRepository.findByEmailIgnoreCaseOrNull(email) - ?: tryCreateNewUser(email) + val existingUser = + userRepository.findByEmailIgnoreCaseOrNull(email) + ?: tryCreateNewUser(email) KomgaPrincipal(existingUser, oAuth2User = oAuth2User) } @@ -60,8 +62,9 @@ class KomgaOAuth2UserServiceConfiguration( if (komgaProperties.oidcEmailVerification && oidcUser.emailVerified == null) throw OAuth2AuthenticationException("ERR_1027") if (komgaProperties.oidcEmailVerification && oidcUser.emailVerified == false) throw OAuth2AuthenticationException("ERR_1026") - val existingUser = userRepository.findByEmailIgnoreCaseOrNull(oidcUser.email) - ?: tryCreateNewUser(oidcUser.email) + val existingUser = + userRepository.findByEmailIgnoreCaseOrNull(oidcUser.email) + ?: tryCreateNewUser(oidcUser.email) KomgaPrincipal(existingUser, oidcUser) } @@ -71,5 +74,7 @@ class KomgaOAuth2UserServiceConfiguration( if (komgaProperties.oauth2AccountCreation) { logger.info { "Creating new user from OAuth2 login: $email" } userLifecycle.createUser(KomgaUser(email, RandomStringUtils.randomAlphanumeric(12), roleAdmin = false)) - } else throw OAuth2AuthenticationException("ERR_1025") + } else { + throw OAuth2AuthenticationException("ERR_1025") + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionConfiguration.kt index a554d32a2..af69d08d4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionConfiguration.kt @@ -16,7 +16,6 @@ import org.springframework.session.web.http.HttpSessionIdResolver @EnableCaffeineHttpSession @Configuration class SessionConfiguration { - @Bean fun sessionCookieName() = "SESSION" @@ -30,7 +29,10 @@ class SessionConfiguration { } @Bean - fun httpSessionIdResolver(sessionHeaderName: String, cookieSerializer: CookieSerializer): HttpSessionIdResolver = + fun httpSessionIdResolver( + sessionHeaderName: String, + cookieSerializer: CookieSerializer, + ): HttpSessionIdResolver = SmartHttpSessionIdResolver(sessionHeaderName, cookieSerializer) @Bean diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionListener.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionListener.kt index 572defbd8..d3c8227a4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionListener.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SessionListener.kt @@ -9,7 +9,6 @@ private val logger = KotlinLogging.logger {} @Component class SessionListener { - @EventListener fun sessionEventLogging(event: AbstractSessionEvent) { logger.debug { "${event.javaClass.simpleName}: ${event.sessionId}" } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SmartHttpSessionIdResolver.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SmartHttpSessionIdResolver.kt index b80ea1b97..838909492 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SmartHttpSessionIdResolver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/security/session/SmartHttpSessionIdResolver.kt @@ -17,11 +17,18 @@ class SmartHttpSessionIdResolver( override fun resolveSessionIds(request: HttpServletRequest): List = request.getResolver().resolveSessionIds(request) - override fun setSessionId(request: HttpServletRequest, response: HttpServletResponse, sessionId: String) { + override fun setSessionId( + request: HttpServletRequest, + response: HttpServletResponse, + sessionId: String, + ) { request.getResolver().setSessionId(request, response, sessionId) } - override fun expireSession(request: HttpServletRequest, response: HttpServletResponse) { + override fun expireSession( + request: HttpServletRequest, + response: HttpServletResponse, + ) { request.getResolver().expireSession(request, response) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarBookConsumer.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarBookConsumer.kt index 00597f338..02b02ea38 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarBookConsumer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarBookConsumer.kt @@ -4,6 +4,11 @@ import org.gotson.komga.domain.model.Sidecar interface SidecarBookConsumer { fun getSidecarBookType(): Sidecar.Type + fun getSidecarBookPrefilter(): List - fun isSidecarBookMatch(basename: String, sidecar: String): Boolean + + fun isSidecarBookMatch( + basename: String, + sidecar: String, + ): Boolean } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarSeriesConsumer.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarSeriesConsumer.kt index a41950bf9..a62a672dc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarSeriesConsumer.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/sidecar/SidecarSeriesConsumer.kt @@ -4,5 +4,6 @@ import org.gotson.komga.domain.model.Sidecar interface SidecarSeriesConsumer { fun getSidecarSeriesType(): Sidecar.Type + fun getSidecarSeriesFilenames(): List } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/swagger/SwaggerConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/swagger/SwaggerConfiguration.kt index cd3f770be..7f98a3200 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/swagger/SwaggerConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/swagger/SwaggerConfiguration.kt @@ -11,7 +11,6 @@ import org.springframework.context.annotation.Configuration @Configuration class SwaggerConfiguration { - @Bean fun openApi(): OpenAPI = OpenAPI() @@ -21,9 +20,9 @@ class SwaggerConfiguration { .version("v1.0") .description( """ - Komga offers 2 APIs: REST and OPDS. + Komga offers 2 APIs: REST and OPDS. - Both APIs are secured using HTTP Basic Authentication. + Both APIs are secured using HTTP Basic Authentication. """.trimIndent(), ) .license(License().name("MIT").url("https://github.com/gotson/komga/blob/master/LICENSE")), diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/transaction/TransactionConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/transaction/TransactionConfiguration.kt index 51ef3e271..bd5ccd5a2 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/transaction/TransactionConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/transaction/TransactionConfiguration.kt @@ -7,7 +7,6 @@ import org.springframework.transaction.support.TransactionTemplate @Configuration class TransactionConfiguration { - @Bean fun transactionTemplate(transactionManager: PlatformTransactionManager) = TransactionTemplate(transactionManager) diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/BCP47.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/BCP47.kt index 9fb15d8ed..9ef14ed91 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/BCP47.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/BCP47.kt @@ -16,8 +16,10 @@ annotation class BCP47( ) class BCP47Validator : ConstraintValidator { - - override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { + override fun isValid( + value: String?, + context: ConstraintValidatorContext?, + ): Boolean { if (value == null) return false return BCP47TagValidator.isValid(value) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/Blank.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/Blank.kt index 305343cd0..0655c5a18 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/Blank.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/Blank.kt @@ -15,8 +15,10 @@ annotation class Blank( ) class BlankValidator : ConstraintValidator { - - override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { + override fun isValid( + value: String?, + context: ConstraintValidatorContext?, + ): Boolean { if (value == null) return false return value.isBlank() } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/AuthorsHandlerMethodArgumentResolver.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/AuthorsHandlerMethodArgumentResolver.kt index 3134edba8..4533226a4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/AuthorsHandlerMethodArgumentResolver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/AuthorsHandlerMethodArgumentResolver.kt @@ -25,7 +25,10 @@ class AuthorsHandlerMethodArgumentResolver : HandlerMethodArgumentResolver { return parseParameterIntoAuthors(param.toList()) } - private fun parseParameterIntoAuthors(source: List, delimiter: String = ","): List = + private fun parseParameterIntoAuthors( + source: List, + delimiter: String = ",", + ): List = source .filter { it.contains(delimiter) } .map { Author(name = it.substringBeforeLast(delimiter), role = it.substringAfterLast(delimiter)) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsFilterConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsFilterConfiguration.kt index e009965d7..8c2cbe25c 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsFilterConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsFilterConfiguration.kt @@ -22,7 +22,11 @@ class BracketParamsFilterConfiguration { } inner class BracketParamsFilter : Filter { - override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) { + override fun doFilter( + request: ServletRequest?, + response: ServletResponse?, + chain: FilterChain?, + ) { chain?.doFilter(BracketParamsRequestWrapper(request as HttpServletRequest), response) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsRequestWrapper.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsRequestWrapper.kt index 46890308d..040d5a037 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsRequestWrapper.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/BracketParamsRequestWrapper.kt @@ -9,8 +9,10 @@ class BracketParamsRequestWrapper(request: HttpServletRequest) : HttpServletRequ override fun getParameter(name: String): String? { val nameWithoutSuffix = name.removeSuffix("[]") val values = listOfNotNull(super.getParameter(nameWithoutSuffix), super.getParameter("$nameWithoutSuffix[]")) - return if (values.isEmpty()) null - else values.joinToString(",") + return if (values.isEmpty()) + null + else + values.joinToString(",") } override fun getParameterValues(name: String): Array? { @@ -18,8 +20,10 @@ class BracketParamsRequestWrapper(request: HttpServletRequest) : HttpServletRequ val regular = super.getParameterValues(nameWithoutSuffix) val suffix = super.getParameterValues("$nameWithoutSuffix[]") val values = listOfNotNull(regular, suffix) - return if (values.isEmpty()) null - else values.reduce { acc, strings -> acc + strings } + return if (values.isEmpty()) + null + else + values.reduce { acc, strings -> acc + strings } } override fun getParameterNames(): Enumeration = diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/DelimitedPairHandlerMethodArgumentResolver.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/DelimitedPairHandlerMethodArgumentResolver.kt index 3c62c3e46..f57c8d492 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/DelimitedPairHandlerMethodArgumentResolver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/DelimitedPairHandlerMethodArgumentResolver.kt @@ -25,7 +25,12 @@ class DelimitedPairHandlerMethodArgumentResolver : HandlerMethodArgumentResolver return parseParameterIntoPairs(param.first()) } - private fun parseParameterIntoPairs(source: String, delimiter: String = ","): Pair? = - if (!source.contains(delimiter)) null - else Pair(source.substringBeforeLast(delimiter), source.substringAfterLast(delimiter)) + private fun parseParameterIntoPairs( + source: String, + delimiter: String = ",", + ): Pair? = + if (!source.contains(delimiter)) + null + else + Pair(source.substringBeforeLast(delimiter), source.substringAfterLast(delimiter)) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/EtagFilterConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/EtagFilterConfiguration.kt index 69db547c2..02e7c2581 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/EtagFilterConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/EtagFilterConfiguration.kt @@ -10,13 +10,13 @@ import org.springframework.web.util.pattern.PathPatternParser @Configuration class EtagFilterConfiguration { - - private val excludePatterns = listOf( - PathPatternParser.defaultInstance.parse("/api/v1/books/*/file/**"), - PathPatternParser.defaultInstance.parse("/opds/v1.2/books/*/file/**"), - PathPatternParser.defaultInstance.parse("/api/v1/readlists/*/file/**"), - PathPatternParser.defaultInstance.parse("/api/v1/series/*/file/**"), - ) + private val excludePatterns = + listOf( + PathPatternParser.defaultInstance.parse("/api/v1/books/*/file/**"), + PathPatternParser.defaultInstance.parse("/opds/v1.2/books/*/file/**"), + PathPatternParser.defaultInstance.parse("/api/v1/readlists/*/file/**"), + PathPatternParser.defaultInstance.parse("/api/v1/series/*/file/**"), + ) @Bean fun shallowEtagHeaderFilter(): FilterRegistrationBean = diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt index 47fca0ac1..2276a20dc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/Utils.kt @@ -18,11 +18,12 @@ fun filePathToUrl(filePath: String): URL = fun ResponseEntity.BodyBuilder.setCachePrivate() = this.cacheControl(cachePrivate) -val cachePrivate = CacheControl - .maxAge(0, TimeUnit.SECONDS) - .noTransform() - .cachePrivate() - .mustRevalidate() +val cachePrivate = + CacheControl + .maxAge(0, TimeUnit.SECONDS) + .noTransform() + .cachePrivate() + .mustRevalidate() fun getMediaTypeOrDefault(mediaTypeString: String?): MediaType { mediaTypeString?.let { diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/WebServerConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/WebServerConfiguration.kt index 8762e0814..805bbf168 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/WebServerConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/web/WebServerConfiguration.kt @@ -14,12 +14,16 @@ class WebServerConfiguration( ) : WebServerFactoryCustomizer { override fun customize(factory: ConfigurableServletWebServerFactory) { settingsProvider.serverPort?.let { - if (it > 1) factory.setPort(it) - else logger.warn { "Ignoring invalid server port: $it" } + if (it > 1) + factory.setPort(it) + else + logger.warn { "Ignoring invalid server port: $it" } } settingsProvider.serverContextPath?.let { - if (it.startsWith("/") && !it.endsWith("/")) factory.setContextPath(it) - else logger.warn { "Ignoring invalid server context path: $it" } + if (it.startsWith("/") && !it.endsWith("/")) + factory.setContextPath(it) + else + logger.warn { "Ignoring invalid server context path: $it" } } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/MappingJackson2XmlHttpMessageConverterConfiguration.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/MappingJackson2XmlHttpMessageConverterConfiguration.kt index 9d53f781f..b82822ef6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/MappingJackson2XmlHttpMessageConverterConfiguration.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/MappingJackson2XmlHttpMessageConverterConfiguration.kt @@ -8,7 +8,6 @@ import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConve @Configuration class MappingJackson2XmlHttpMessageConverterConfiguration { - @Bean fun mappingJackson2XmlHttpMessageConverter(builder: Jackson2ObjectMapperBuilder) = MappingJackson2XmlHttpMessageConverter( diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/NamespaceXmlFactory.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/NamespaceXmlFactory.kt index ce0ea2a73..d306a31ae 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/NamespaceXmlFactory.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/xml/NamespaceXmlFactory.kt @@ -15,11 +15,16 @@ class NamespaceXmlFactory( private val defaultNamespace: String? = null, private val prefixToNamespace: Map = emptyMap(), ) : XmlFactory() { - - override fun _createXmlWriter(ctxt: IOContext?, w: Writer?): XMLStreamWriter = + override fun _createXmlWriter( + ctxt: IOContext?, + w: Writer?, + ): XMLStreamWriter = super._createXmlWriter(ctxt, w).apply { configure() } - override fun createGenerator(out: OutputStream?, enc: JsonEncoding?): ToXmlGenerator = + override fun createGenerator( + out: OutputStream?, + enc: JsonEncoding?, + ): ToXmlGenerator = super.createGenerator(out, enc).apply { staxWriter.configure() } override fun createGenerator(out: OutputStream?): ToXmlGenerator = @@ -28,7 +33,10 @@ class NamespaceXmlFactory( override fun createGenerator(out: Writer?): ToXmlGenerator = super.createGenerator(out).apply { staxWriter.configure() } - override fun createGenerator(f: File?, enc: JsonEncoding?): ToXmlGenerator = + override fun createGenerator( + f: File?, + enc: JsonEncoding?, + ): ToXmlGenerator = super.createGenerator(f, enc).apply { staxWriter.configure() } override fun createGenerator(sw: XMLStreamWriter?): ToXmlGenerator = diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/Utils.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/Utils.kt index 96387f042..db2d6f2e4 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/Utils.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/Utils.kt @@ -23,15 +23,20 @@ fun KomgaUser.checkContentRestriction(series: SeriesDto) { * * @throws[ResponseStatusException] if the user cannot access the content */ -fun KomgaUser.checkContentRestriction(bookId: String, bookRepository: BookRepository, seriesMetadataRepository: SeriesMetadataRepository) { +fun KomgaUser.checkContentRestriction( + bookId: String, + bookRepository: BookRepository, + seriesMetadataRepository: SeriesMetadataRepository, +) { if (!sharedAllLibraries) { bookRepository.getLibraryIdOrNull(bookId)?.let { if (!canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } - if (restrictions.isRestricted) bookRepository.getSeriesIdOrNull(bookId)?.let { seriesId -> - seriesMetadataRepository.findById(seriesId).let { - if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - } - } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + if (restrictions.isRestricted) + bookRepository.getSeriesIdOrNull(bookId)?.let { seriesId -> + seriesMetadataRepository.findById(seriesId).let { + if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) + } + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/WebPubGenerator.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/WebPubGenerator.kt index b074007f2..e190e5ca3 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/WebPubGenerator.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/WebPubGenerator.kt @@ -45,18 +45,19 @@ class WebPubGenerator( private val bookAnalyzer: BookAnalyzer, private val mediaRepository: MediaRepository, ) { - private val wpKnownRoles = listOf( - "author", - "translator", - "editor", - "artist", - "illustrator", - "letterer", - "penciler", - "penciller", - "colorist", - "inker", - ) + private val wpKnownRoles = + listOf( + "author", + "translator", + "editor", + "artist", + "illustrator", + "letterer", + "penciler", + "penciller", + "colorist", + "inker", + ) private val recommendedImageMediaTypes = listOf("image/jpeg", "image/png", "image/gif") @@ -70,92 +71,120 @@ class WebPubGenerator( ) } - fun toOpdsPublicationDto(bookDto: BookDto, includeOpdsLinks: Boolean = false): WPPublicationDto { + fun toOpdsPublicationDto( + bookDto: BookDto, + includeOpdsLinks: Boolean = false, + ): WPPublicationDto { return bookDto.toBasePublicationDto(includeOpdsLinks).copy(images = buildThumbnailLinkDtos(bookDto.id)) } - private fun buildThumbnailLinkDtos(bookId: String) = listOf( - WPLinkDto( - href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("api", "v1").path("books/$bookId/thumbnail").toUriString(), - type = thumbnailType.mediaType, - ), - ) + private fun buildThumbnailLinkDtos(bookId: String) = + listOf( + WPLinkDto( + href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("api", "v1").path("books/$bookId/thumbnail").toUriString(), + type = thumbnailType.mediaType, + ), + ) - fun toManifestDivina(bookDto: BookDto, media: Media, seriesMetadata: SeriesMetadata): WPPublicationDto { + fun toManifestDivina( + bookDto: BookDto, + media: Media, + seriesMetadata: SeriesMetadata, + ): WPPublicationDto { val uriBuilder = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("api", "v1") return bookDto.toBasePublicationDto().let { val pages = if (media.profile == MediaProfile.PDF) bookAnalyzer.getPdfPagesDynamic(media) else media.pages it.copy( mediaType = MEDIATYPE_DIVINA_JSON, metadata = it.metadata.withSeriesMetadata(seriesMetadata).copy(conformsTo = PROFILE_DIVINA), - readingOrder = pages.mapIndexed { index: Int, page: BookPage -> - WPLinkDto( - href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}").toUriString(), - type = page.mediaType, - width = page.dimension?.width, - height = page.dimension?.height, - alternate = if (!recommendedImageMediaTypes.contains(page.mediaType) && imageConverter.canConvertMediaType(page.mediaType, MediaType.IMAGE_JPEG_VALUE)) listOf( - WPLinkDto( - href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}").queryParam("convert", "jpeg").toUriString(), - type = MediaType.IMAGE_JPEG_VALUE, - width = page.dimension?.width, - height = page.dimension?.height, - ), - ) else emptyList(), - ) - }, + readingOrder = + pages.mapIndexed { index: Int, page: BookPage -> + WPLinkDto( + href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}").toUriString(), + type = page.mediaType, + width = page.dimension?.width, + height = page.dimension?.height, + alternate = + if (!recommendedImageMediaTypes.contains(page.mediaType) && imageConverter.canConvertMediaType(page.mediaType, MediaType.IMAGE_JPEG_VALUE)) + listOf( + WPLinkDto( + href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}").queryParam("convert", "jpeg").toUriString(), + type = MediaType.IMAGE_JPEG_VALUE, + width = page.dimension?.width, + height = page.dimension?.height, + ), + ) + else + emptyList(), + ) + }, resources = buildThumbnailLinkDtos(bookDto.id), ) } } - fun toManifestPdf(bookDto: BookDto, media: Media, seriesMetadata: SeriesMetadata): WPPublicationDto { + fun toManifestPdf( + bookDto: BookDto, + media: Media, + seriesMetadata: SeriesMetadata, + ): WPPublicationDto { val uriBuilder = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("api", "v1") return bookDto.toBasePublicationDto().let { it.copy( mediaType = MEDIATYPE_WEBPUB_JSON, metadata = it.metadata.withSeriesMetadata(seriesMetadata).copy(conformsTo = PROFILE_PDF), - readingOrder = List(media.pageCount) { index: Int -> - WPLinkDto( - href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}/raw").toUriString(), - type = KomgaMediaType.PDF.type, - ) - }, + readingOrder = + List(media.pageCount) { index: Int -> + WPLinkDto( + href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/pages/${index + 1}/raw").toUriString(), + type = KomgaMediaType.PDF.type, + ) + }, resources = buildThumbnailLinkDtos(bookDto.id), ) } } - fun toManifestEpub(bookDto: BookDto, media: Media, seriesMetadata: SeriesMetadata): WPPublicationDto { + fun toManifestEpub( + bookDto: BookDto, + media: Media, + seriesMetadata: SeriesMetadata, + ): WPPublicationDto { val uriBuilder = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("api", "v1") - val extension = when { - media.extension is ProxyExtension && media.extension.proxyForType() -> mediaRepository.findExtensionByIdOrNull(media.bookId) as? MediaExtensionEpub - media.extension is MediaExtensionEpub -> media.extension - else -> null - } + val extension = + when { + media.extension is ProxyExtension && media.extension.proxyForType() -> mediaRepository.findExtensionByIdOrNull(media.bookId) as? MediaExtensionEpub + media.extension is MediaExtensionEpub -> media.extension + else -> null + } return bookDto.toBasePublicationDto().let { publication -> publication.copy( mediaType = MEDIATYPE_WEBPUB_JSON, - metadata = publication.metadata.withSeriesMetadata(seriesMetadata).copy( - conformsTo = PROFILE_EPUB, - rendition = when (extension?.isFixedLayout) { - true -> mapOf("layout" to "fixed") - false -> mapOf("layout" to "reflowable") - else -> emptyMap() + metadata = + publication.metadata.withSeriesMetadata(seriesMetadata).copy( + conformsTo = PROFILE_EPUB, + rendition = + when (extension?.isFixedLayout) { + true -> mapOf("layout" to "fixed") + false -> mapOf("layout" to "reflowable") + else -> emptyMap() + }, + ), + readingOrder = + media.files.filter { it.subType == MediaFile.SubType.EPUB_PAGE }.map { + WPLinkDto( + href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/").path(it.fileName).toUriString(), + type = it.mediaType, + ) }, - ), - readingOrder = media.files.filter { it.subType == MediaFile.SubType.EPUB_PAGE }.map { - WPLinkDto( - href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/").path(it.fileName).toUriString(), - type = it.mediaType, - ) - }, - resources = buildThumbnailLinkDtos(bookDto.id) + media.files.filter { it.subType == MediaFile.SubType.EPUB_ASSET }.map { - WPLinkDto( - href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/").path(it.fileName).toUriString(), - type = it.mediaType, - ) - }, + resources = + buildThumbnailLinkDtos(bookDto.id) + + media.files.filter { it.subType == MediaFile.SubType.EPUB_ASSET }.map { + WPLinkDto( + href = uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/").path(it.fileName).toUriString(), + type = it.mediaType, + ) + }, toc = extension?.toc?.map { it.toWPLinkDto(uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/")) } ?: emptyList(), landmarks = extension?.landmarks?.map { it.toWPLinkDto(uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/")) } ?: emptyList(), pageList = extension?.pageList?.map { it.toWPLinkDto(uriBuilder.cloneBuilder().path("books/${bookDto.id}/resource/")) } ?: emptyList(), @@ -163,50 +192,60 @@ class WebPubGenerator( } } - private fun EpubTocEntry.toWPLinkDto(uriBuilder: UriComponentsBuilder): WPLinkDto = WPLinkDto( - title = title, - href = href?.let { - val fragment = it.substringAfterLast("#", "") - val h = it.removeSuffix("#$fragment") - uriBuilder.cloneBuilder().path(h).toUriString() + if (fragment.isNotEmpty()) "#$fragment" else "" - }, - children = children.map { it.toWPLinkDto(uriBuilder) }, - ) + private fun EpubTocEntry.toWPLinkDto(uriBuilder: UriComponentsBuilder): WPLinkDto = + WPLinkDto( + title = title, + href = + href?.let { + val fragment = it.substringAfterLast("#", "") + val h = it.removeSuffix("#$fragment") + uriBuilder.cloneBuilder().path(h).toUriString() + if (fragment.isNotEmpty()) "#$fragment" else "" + }, + children = children.map { it.toWPLinkDto(uriBuilder) }, + ) - private fun BookDto.toWPMetadataDto(includeOpdsLinks: Boolean = false) = WPMetadataDto( - title = metadata.title, - description = metadata.summary, - numberOfPages = this.media.pagesCount, - modified = lastModified.toZonedDateTime(), - published = metadata.releaseDate, - subject = metadata.tags.toList(), - identifier = if (metadata.isbn.isNotBlank()) "urn:isbn:${metadata.isbn}" else null, - belongsTo = WPBelongsToDto( - series = listOf( - WPContributorDto( - seriesTitle, - metadata.numberSort, - if (includeOpdsLinks) listOf( - WPLinkDto( - href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v2").path("series/$seriesId").toUriString(), - type = MEDIATYPE_OPDS_JSON_VALUE, + private fun BookDto.toWPMetadataDto(includeOpdsLinks: Boolean = false) = + WPMetadataDto( + title = metadata.title, + description = metadata.summary, + numberOfPages = this.media.pagesCount, + modified = lastModified.toZonedDateTime(), + published = metadata.releaseDate, + subject = metadata.tags.toList(), + identifier = if (metadata.isbn.isNotBlank()) "urn:isbn:${metadata.isbn}" else null, + belongsTo = + WPBelongsToDto( + series = + listOf( + WPContributorDto( + seriesTitle, + metadata.numberSort, + if (includeOpdsLinks) + listOf( + WPLinkDto( + href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v2").path("series/$seriesId").toUriString(), + type = MEDIATYPE_OPDS_JSON_VALUE, + ), + ) + else + emptyList(), + ), ), - ) else emptyList(), ), - ), - ), - ) + ) - private fun WPMetadataDto.withSeriesMetadata(seriesMetadata: SeriesMetadata) = copy( - language = seriesMetadata.language, - readingProgression = when (seriesMetadata.readingDirection) { - SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT -> WPReadingProgressionDto.LTR - SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT -> WPReadingProgressionDto.RTL - SeriesMetadata.ReadingDirection.VERTICAL -> WPReadingProgressionDto.TTB - SeriesMetadata.ReadingDirection.WEBTOON -> WPReadingProgressionDto.TTB - null -> null - }, - ) + private fun WPMetadataDto.withSeriesMetadata(seriesMetadata: SeriesMetadata) = + copy( + language = seriesMetadata.language, + readingProgression = + when (seriesMetadata.readingDirection) { + SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT -> WPReadingProgressionDto.LTR + SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT -> WPReadingProgressionDto.RTL + SeriesMetadata.ReadingDirection.VERTICAL -> WPReadingProgressionDto.TTB + SeriesMetadata.ReadingDirection.WEBTOON -> WPReadingProgressionDto.TTB + null -> null + }, + ) private fun WPMetadataDto.withAuthors(authors: List): WPMetadataDto { val groups = authors.groupBy({ it.role }, { it.name }) @@ -238,10 +277,11 @@ class WebPubGenerator( } } - private fun mediaProfileToWebPub(profile: MediaProfile?): String = when (profile) { - MediaProfile.DIVINA -> MEDIATYPE_DIVINA_JSON_VALUE - MediaProfile.PDF -> MEDIATYPE_WEBPUB_JSON_VALUE - MediaProfile.EPUB -> MEDIATYPE_WEBPUB_JSON_VALUE - null -> MEDIATYPE_WEBPUB_JSON_VALUE - } + private fun mediaProfileToWebPub(profile: MediaProfile?): String = + when (profile) { + MediaProfile.DIVINA -> MEDIATYPE_DIVINA_JSON_VALUE + MediaProfile.PDF -> MEDIATYPE_WEBPUB_JSON_VALUE + MediaProfile.EPUB -> MEDIATYPE_WEBPUB_JSON_VALUE + null -> MEDIATYPE_WEBPUB_JSON_VALUE + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/OpdsCommonController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/OpdsCommonController.kt index c3dc1bb36..f289d8dd0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/OpdsCommonController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/OpdsCommonController.kt @@ -25,7 +25,6 @@ class OpdsCommonController( private val bookLifecycle: BookLifecycle, private val imageConverter: ImageConverter, ) { - @ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))]) @GetMapping( value = [ @@ -40,7 +39,9 @@ class OpdsCommonController( ): ByteArray { principal.user.checkContentRestriction(bookId, bookRepository, seriesMetadataRepository) val poster = bookLifecycle.getThumbnailBytesOriginal(bookId) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) - return if (poster.mediaType != ImageType.JPEG.mediaType) imageConverter.convertImage(poster.bytes, ImageType.JPEG.imageIOFormat) - else poster.bytes + return if (poster.mediaType != ImageType.JPEG.mediaType) + imageConverter.convertImage(poster.bytes, ImageType.JPEG.imageIOFormat) + else + poster.bytes } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/OpdsController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/OpdsController.kt index fcf94d15f..55faf9cee 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/OpdsController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/OpdsController.kt @@ -115,7 +115,6 @@ class OpdsController( @Qualifier("pdfImageType") private val pdfImageType: ImageType, ) { - private val komgaAuthor = OpdsAuthor("Komga", URI("https://github.com/gotson/komga")) private val decimalFormat = DecimalFormat("0.#") @@ -127,106 +126,117 @@ class OpdsController( private fun uriBuilder(path: String) = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v1.2").path(path) - private fun linkPage(uriBuilder: UriComponentsBuilder, page: Page): List { + private fun linkPage( + uriBuilder: UriComponentsBuilder, + page: Page, + ): List { return listOfNotNull( - if (!page.isFirst) OpdsLinkFeedNavigation( - OpdsLinkRel.PREVIOUS, - uriBuilder.cloneBuilder().queryParam("page", page.pageable.previousOrFirst().pageNumber).toUriString(), - ) - else null, - if (!page.isLast) OpdsLinkFeedNavigation( - OpdsLinkRel.NEXT, - uriBuilder.cloneBuilder().queryParam("page", page.pageable.next().pageNumber).toUriString(), - ) - else null, + if (!page.isFirst) + OpdsLinkFeedNavigation( + OpdsLinkRel.PREVIOUS, + uriBuilder.cloneBuilder().queryParam("page", page.pageable.previousOrFirst().pageNumber).toUriString(), + ) + else + null, + if (!page.isLast) + OpdsLinkFeedNavigation( + OpdsLinkRel.NEXT, + uriBuilder.cloneBuilder().queryParam("page", page.pageable.next().pageNumber).toUriString(), + ) + else + null, ) } @GetMapping(ROUTE_CATALOG) - fun getCatalog(): OpdsFeed = OpdsFeedNavigation( - id = "root", - title = "Komga OPDS catalog", - updated = ZonedDateTime.now(), - author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder(ROUTE_CATALOG).toUriString()), - linkStart(), - OpdsLinkSearch(uriBuilder(ROUTE_SEARCH).toUriString()), - OpdsLink(MEDIATYPE_OPDS_JSON_VALUE, "alternate", href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v2", "catalog").toUriString()), - ), - entries = listOf( - OpdsEntryNavigation( - title = "Keep Reading", - updated = ZonedDateTime.now(), - id = ID_KEEP_READING, - content = "Continue reading your in progress books", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_KEEP_READING).toUriString()), - ), - OpdsEntryNavigation( - title = "On Deck", - updated = ZonedDateTime.now(), - id = ID_ON_DECK, - content = "Browse what to read next", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_ON_DECK).toUriString()), - ), - OpdsEntryNavigation( - title = "All series", - updated = ZonedDateTime.now(), - id = ID_SERIES_ALL, - content = "Browse by series", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_ALL).toUriString()), - ), - OpdsEntryNavigation( - title = "Latest series", - updated = ZonedDateTime.now(), - id = ID_SERIES_LATEST, - content = "Browse latest series", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_LATEST).toUriString()), - ), - OpdsEntryNavigation( - title = "Latest books", - updated = ZonedDateTime.now(), - id = ID_BOOKS_LATEST, - content = "Browse latest books", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_BOOKS_LATEST).toUriString()), - ), - OpdsEntryNavigation( - title = "All libraries", - updated = ZonedDateTime.now(), - id = ID_LIBRARIES_ALL, - content = "Browse by library", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_LIBRARIES_ALL).toUriString()), - ), - OpdsEntryNavigation( - title = "All collections", - updated = ZonedDateTime.now(), - id = ID_COLLECTIONS_ALL, - content = "Browse by collection", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_COLLECTIONS_ALL).toUriString()), - ), - OpdsEntryNavigation( - title = "All read lists", - updated = ZonedDateTime.now(), - id = ID_READLISTS_ALL, - content = "Browse by read lists", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_READLISTS_ALL).toUriString()), - ), - OpdsEntryNavigation( - title = "All publishers", - updated = ZonedDateTime.now(), - id = ID_PUBLISHERS_ALL, - content = "Browse by publishers", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_PUBLISHERS_ALL).toUriString()), - ), - ), - ) + fun getCatalog(): OpdsFeed = + OpdsFeedNavigation( + id = "root", + title = "Komga OPDS catalog", + updated = ZonedDateTime.now(), + author = komgaAuthor, + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder(ROUTE_CATALOG).toUriString()), + linkStart(), + OpdsLinkSearch(uriBuilder(ROUTE_SEARCH).toUriString()), + OpdsLink(MEDIATYPE_OPDS_JSON_VALUE, "alternate", href = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v2", "catalog").toUriString()), + ), + entries = + listOf( + OpdsEntryNavigation( + title = "Keep Reading", + updated = ZonedDateTime.now(), + id = ID_KEEP_READING, + content = "Continue reading your in progress books", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_KEEP_READING).toUriString()), + ), + OpdsEntryNavigation( + title = "On Deck", + updated = ZonedDateTime.now(), + id = ID_ON_DECK, + content = "Browse what to read next", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_ON_DECK).toUriString()), + ), + OpdsEntryNavigation( + title = "All series", + updated = ZonedDateTime.now(), + id = ID_SERIES_ALL, + content = "Browse by series", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_ALL).toUriString()), + ), + OpdsEntryNavigation( + title = "Latest series", + updated = ZonedDateTime.now(), + id = ID_SERIES_LATEST, + content = "Browse latest series", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_LATEST).toUriString()), + ), + OpdsEntryNavigation( + title = "Latest books", + updated = ZonedDateTime.now(), + id = ID_BOOKS_LATEST, + content = "Browse latest books", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_BOOKS_LATEST).toUriString()), + ), + OpdsEntryNavigation( + title = "All libraries", + updated = ZonedDateTime.now(), + id = ID_LIBRARIES_ALL, + content = "Browse by library", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_LIBRARIES_ALL).toUriString()), + ), + OpdsEntryNavigation( + title = "All collections", + updated = ZonedDateTime.now(), + id = ID_COLLECTIONS_ALL, + content = "Browse by collection", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_COLLECTIONS_ALL).toUriString()), + ), + OpdsEntryNavigation( + title = "All read lists", + updated = ZonedDateTime.now(), + id = ID_READLISTS_ALL, + content = "Browse by read lists", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_READLISTS_ALL).toUriString()), + ), + OpdsEntryNavigation( + title = "All publishers", + updated = ZonedDateTime.now(), + id = ID_PUBLISHERS_ALL, + content = "Browse by publishers", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_PUBLISHERS_ALL).toUriString()), + ), + ), + ) @GetMapping(ROUTE_SEARCH) - fun getSearch(): OpenSearchDescription = OpenSearchDescription( - shortName = "Search", - description = "Search for series", - url = OpenSearchDescription.OpenSearchUrl(uriBuilder(ROUTE_SERIES_ALL).toUriString() + "?search={searchTerms}"), - ) + fun getSearch(): OpenSearchDescription = + OpenSearchDescription( + shortName = "Search", + description = "Search for series", + url = OpenSearchDescription.OpenSearchUrl(uriBuilder(ROUTE_SERIES_ALL).toUriString() + "?search={searchTerms}"), + ) @PageAsQueryParam @GetMapping(ROUTE_ON_DECK) @@ -234,12 +244,13 @@ class OpdsController( @AuthenticationPrincipal principal: KomgaPrincipal, @Parameter(hidden = true) page: Pageable, ): OpdsFeed { - val bookPage = bookDtoRepository.findAllOnDeck( - principal.user.id, - principal.user.getAuthorizedLibraryIds(null), - page, - principal.user.restrictions, - ) + val bookPage = + bookDtoRepository.findAllOnDeck( + principal.user.id, + principal.user.getAuthorizedLibraryIds(null), + page, + principal.user.restrictions, + ) val builder = uriBuilder(ROUTE_ON_DECK) @@ -248,11 +259,12 @@ class OpdsController( title = "On Deck", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), - linkStart(), - *linkPage(builder, bookPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), + linkStart(), + *linkPage(builder, bookPage).toTypedArray(), + ), entries = bookPage.content.getEntriesWithSeriesTitle(), ) } @@ -265,19 +277,21 @@ class OpdsController( ): OpdsFeed { val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("readProgress.readDate"))) - val bookSearch = BookSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - readStatus = setOf(ReadStatus.IN_PROGRESS), - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + readStatus = setOf(ReadStatus.IN_PROGRESS), + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ) - val bookPage = bookDtoRepository.findAll( - bookSearch, - principal.user.id, - pageable, - principal.user.restrictions, - ) + val bookPage = + bookDtoRepository.findAll( + bookSearch, + principal.user.id, + pageable, + principal.user.restrictions, + ) val builder = uriBuilder(ROUTE_ON_DECK) @@ -286,11 +300,12 @@ class OpdsController( title = "Keep Reading", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), - linkStart(), - *linkPage(builder, bookPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), + linkStart(), + *linkPage(builder, bookPage).toTypedArray(), + ), entries = bookPage.content.getEntriesWithSeriesTitle(), ) } @@ -304,33 +319,38 @@ class OpdsController( @Parameter(hidden = true) page: Pageable, ): OpdsFeed { val sort = - if (!searchTerm.isNullOrBlank()) Sort.by("relevance") - else Sort.by(Sort.Order.asc("metadata.titleSort")) + if (!searchTerm.isNullOrBlank()) + Sort.by("relevance") + else + Sort.by(Sort.Order.asc("metadata.titleSort")) val pageable = PageRequest.of(page.pageNumber, page.pageSize, sort) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - searchTerm = searchTerm, - publishers = publishers, - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + searchTerm = searchTerm, + publishers = publishers, + deleted = false, + ) val seriesPage = seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) - val builder = uriBuilder(ROUTE_SERIES_ALL) - .queryParamIfPresent("search", Optional.ofNullable(searchTerm)) - .queryParamIfPresent("publisher", Optional.ofNullable(publishers)) + val builder = + uriBuilder(ROUTE_SERIES_ALL) + .queryParamIfPresent("search", Optional.ofNullable(searchTerm)) + .queryParamIfPresent("publisher", Optional.ofNullable(publishers)) return OpdsFeedNavigation( id = ID_SERIES_ALL, title = if (!searchTerm.isNullOrBlank()) "Series search for: $searchTerm" else "All series", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), - linkStart(), - *linkPage(builder, seriesPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, builder.toUriString()), + linkStart(), + *linkPage(builder, seriesPage).toTypedArray(), + ), entries = seriesPage.content.map { it.toOpdsEntry() }, ) } @@ -343,10 +363,11 @@ class OpdsController( ): OpdsFeed { val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("lastModified"))) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + deleted = false, + ) val seriesPage = seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) @@ -357,11 +378,12 @@ class OpdsController( title = "Latest series", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, seriesPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, seriesPage).toTypedArray(), + ), entries = seriesPage.content.map { it.toOpdsEntry() }, ) } @@ -372,11 +394,12 @@ class OpdsController( @AuthenticationPrincipal principal: KomgaPrincipal, @Parameter(hidden = true) page: Pageable, ): OpdsFeed { - val bookSearch = BookSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("createdDate"))) val bookPage = bookDtoRepository.findAll(bookSearch, principal.user.id, pageable, principal.user.restrictions) @@ -388,11 +411,12 @@ class OpdsController( title = "Latest books", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, bookPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, bookPage).toTypedArray(), + ), entries = bookPage.content.getEntriesWithSeriesTitle(), ) } @@ -412,10 +436,11 @@ class OpdsController( title = "All libraries", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder(ROUTE_LIBRARIES_ALL).toUriString()), - linkStart(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder(ROUTE_LIBRARIES_ALL).toUriString()), + linkStart(), + ), entries = libraries.map { it.toOpdsEntry() }, ) } @@ -436,11 +461,12 @@ class OpdsController( title = "All collections", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, collections).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, collections).toTypedArray(), + ), entries = collections.content.map { it.toOpdsEntry() }, ) } @@ -461,11 +487,12 @@ class OpdsController( title = "All read lists", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, readLists).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, readLists).toTypedArray(), + ), entries = readLists.content.map { it.toOpdsEntry() }, ) } @@ -485,20 +512,22 @@ class OpdsController( title = "All publishers", updated = ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, publishers).toTypedArray(), - ), - entries = publishers.content.map { publisher -> - OpdsEntryNavigation( - title = publisher, - updated = ZonedDateTime.now(), - id = "publisher:${UriUtils.encodeQueryParam(publisher, StandardCharsets.UTF_8)}", - content = "", - link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_ALL).queryParam("publisher", publisher).toUriString()), - ) - }, + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, publishers).toTypedArray(), + ), + entries = + publishers.content.map { publisher -> + OpdsEntryNavigation( + title = publisher, + updated = ZonedDateTime.now(), + id = "publisher:${UriUtils.encodeQueryParam(publisher, StandardCharsets.UTF_8)}", + content = "", + link = OpdsLinkFeedNavigation(OpdsLinkRel.SUBSECTION, uriBuilder(ROUTE_SERIES_ALL).queryParam("publisher", publisher).toUriString()), + ) + }, ) } @@ -512,15 +541,17 @@ class OpdsController( seriesDtoRepository.findByIdOrNull(id, principal.user.id)?.let { series -> principal.user.checkContentRestriction(series) - val bookSearch = BookSearchWithReadProgress( - seriesIds = listOf(id), - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + seriesIds = listOf(id), + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("metadata.numberSort"))) - val entries = bookDtoRepository.findAll(bookSearch, principal.user.id, pageable, principal.user.restrictions) - .map { it.toOpdsEntry(mediaRepository.findById(it.id)) } + val entries = + bookDtoRepository.findAll(bookSearch, principal.user.id, pageable, principal.user.restrictions) + .map { it.toOpdsEntry(mediaRepository.findById(it.id)) } val uriBuilder = uriBuilder("series/$id") @@ -529,11 +560,12 @@ class OpdsController( title = series.metadata.title, updated = series.lastModified.toZonedDateTime(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), entries = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -548,15 +580,17 @@ class OpdsController( libraryRepository.findByIdOrNull(id)?.let { library -> if (!principal.user.canAccessLibrary(library)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = setOf(library.id), - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = setOf(library.id), + deleted = false, + ) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("metadata.titleSort"))) - val entries = seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) - .map { it.toOpdsEntry() } + val entries = + seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) + .map { it.toOpdsEntry() } val uriBuilder = uriBuilder("libraries/$id") @@ -565,11 +599,12 @@ class OpdsController( title = library.name, updated = library.lastModifiedDate.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), entries = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -583,17 +618,21 @@ class OpdsController( ): OpdsFeed = collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { collection -> val sort = - if (collection.ordered) Sort.by(Sort.Order.asc("collection.number")) - else Sort.by(Sort.Order.asc("metadata.titleSort")) + if (collection.ordered) + Sort.by(Sort.Order.asc("collection.number")) + else + Sort.by(Sort.Order.asc("metadata.titleSort")) val pageable = PageRequest.of(page.pageNumber, page.pageSize, sort) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + deleted = false, + ) - val entries = seriesDtoRepository.findAllByCollectionId(collection.id, seriesSearch, principal.user.id, pageable, principal.user.restrictions) - .map { it.toOpdsEntry() } + val entries = + seriesDtoRepository.findAllByCollectionId(collection.id, seriesSearch, principal.user.id, pageable, principal.user.restrictions) + .map { it.toOpdsEntry() } val uriBuilder = uriBuilder("collections/$id") @@ -602,11 +641,12 @@ class OpdsController( title = collection.name, updated = collection.lastModifiedDate.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), entries = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -620,27 +660,32 @@ class OpdsController( ): OpdsFeed = readListRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { readList -> val sort = - if (readList.ordered) Sort.by(Sort.Order.asc("readList.number")) - else Sort.by(Sort.Order.asc("metadata.releaseDate")) + if (readList.ordered) + Sort.by(Sort.Order.asc("readList.number")) + else + Sort.by(Sort.Order.asc("metadata.releaseDate")) val pageable = PageRequest.of(page.pageNumber, page.pageSize, sort) - val bookSearch = BookSearchWithReadProgress( - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ) - val booksPage = bookDtoRepository.findAllByReadListId( - readList.id, - principal.user.id, - principal.user.getAuthorizedLibraryIds(null), - bookSearch, - pageable, - principal.user.restrictions, - ) + val booksPage = + bookDtoRepository.findAllByReadListId( + readList.id, + principal.user.id, + principal.user.getAuthorizedLibraryIds(null), + bookSearch, + pageable, + principal.user.restrictions, + ) - val entries = booksPage.map { bookDto -> - bookDto.toOpdsEntry(mediaRepository.findById(bookDto.id)) { "${it.seriesTitle} ${it.metadata.number}: " } - } + val entries = + booksPage.map { bookDto -> + bookDto.toOpdsEntry(mediaRepository.findById(bookDto.id)) { "${it.seriesTitle} ${it.metadata.number}: " } + } val uriBuilder = uriBuilder("readlists/$id") @@ -649,11 +694,12 @@ class OpdsController( title = readList.name, updated = readList.lastModifiedDate.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), author = komgaAuthor, - links = listOf( - OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), - linkStart(), - *linkPage(uriBuilder, booksPage).toTypedArray(), - ), + links = + listOf( + OpdsLinkFeedNavigation(OpdsLinkRel.SELF, uriBuilder.toUriString()), + linkStart(), + *linkPage(uriBuilder, booksPage).toTypedArray(), + ), entries = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -685,42 +731,50 @@ class OpdsController( ) } - private fun BookDto.toOpdsEntry(media: Media, prepend: (BookDto) -> String = { "" }): OpdsEntryAcquisition { - val mediaTypes = when (media.profile) { - MediaProfile.DIVINA -> media.pages.map { it.mediaType }.distinct() - MediaProfile.PDF -> listOf(pdfImageType.mediaType) - MediaProfile.EPUB -> if (media.epubDivinaCompatible) media.pages.map { it.mediaType }.distinct() else emptyList() - null -> emptyList() - } + private fun BookDto.toOpdsEntry( + media: Media, + prepend: (BookDto) -> String = { "" }, + ): OpdsEntryAcquisition { + val mediaTypes = + when (media.profile) { + MediaProfile.DIVINA -> media.pages.map { it.mediaType }.distinct() + MediaProfile.PDF -> listOf(pdfImageType.mediaType) + MediaProfile.EPUB -> if (media.epubDivinaCompatible) media.pages.map { it.mediaType }.distinct() else emptyList() + null -> emptyList() + } val opdsLinkPageStreaming = - if (mediaTypes.isEmpty()) null - else if (mediaTypes.size == 1 && mediaTypes.first() in opdsPseSupportedFormats) { + if (mediaTypes.isEmpty()) { + null + } else if (mediaTypes.size == 1 && mediaTypes.first() in opdsPseSupportedFormats) { OpdsLinkPageStreaming(mediaTypes.first(), uriBuilder("books/$id/pages/").toUriString() + "{pageNumber}", media.pageCount, readProgress?.page, readProgress?.readDate) } else { OpdsLinkPageStreaming("image/jpeg", uriBuilder("books/$id/pages/").toUriString() + "{pageNumber}?convert=jpeg", media.pageCount, readProgress?.page, readProgress?.readDate) } - val thumbnailMediaType = when (media.profile) { - MediaProfile.PDF -> pdfImageType.mediaType - else -> "image/jpeg" - } + val thumbnailMediaType = + when (media.profile) { + MediaProfile.PDF -> pdfImageType.mediaType + else -> "image/jpeg" + } return OpdsEntryAcquisition( title = "${prepend(this)}${metadata.title}", updated = lastModified.toZonedDateTime(), id = id, - content = buildString { - append("${FilenameUtils.getExtension(url).lowercase()} - $size") - if (metadata.summary.isNotBlank()) append("\n\n${metadata.summary}") - }, + content = + buildString { + append("${FilenameUtils.getExtension(url).lowercase()} - $size") + if (metadata.summary.isNotBlank()) append("\n\n${metadata.summary}") + }, authors = metadata.authors.map { OpdsAuthor(it.name) }, - links = listOfNotNull( - OpdsLinkImageThumbnail("image/jpeg", uriBuilder("books/$id/thumbnail/small").toUriString()), - OpdsLinkImage(thumbnailMediaType, uriBuilder("books/$id/thumbnail").toUriString()), - OpdsLinkFileAcquisition(media.mediaType, uriBuilder("books/$id/file/${sanitize(FilenameUtils.getName(url))}").toUriString()), - opdsLinkPageStreaming, - ), + links = + listOfNotNull( + OpdsLinkImageThumbnail("image/jpeg", uriBuilder("books/$id/thumbnail/small").toUriString()), + OpdsLinkImage(thumbnailMediaType, uriBuilder("books/$id/thumbnail").toUriString()), + OpdsLinkFileAcquisition(media.mediaType, uriBuilder("books/$id/file/${sanitize(FilenameUtils.getName(url))}").toUriString()), + opdsLinkPageStreaming, + ), ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsAuthor.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsAuthor.kt index 04dcc42f1..3c46d2ee8 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsAuthor.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsAuthor.kt @@ -7,7 +7,6 @@ import java.net.URI data class OpdsAuthor( @JacksonXmlProperty(namespace = ATOM) val name: String, - @JsonInclude(JsonInclude.Include.NON_NULL) @JacksonXmlProperty(namespace = ATOM) val uri: URI? = null, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsEntry.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsEntry.kt index b21105a39..2c5dfe123 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsEntry.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsEntry.kt @@ -9,13 +9,10 @@ import java.time.ZonedDateTime abstract class OpdsEntry( @get:JacksonXmlProperty(namespace = ATOM) val title: String, - @get:JacksonXmlProperty(namespace = ATOM) val updated: ZonedDateTime, - @get:JacksonXmlProperty(namespace = ATOM) val id: String, - content: String, ) { @get:JacksonXmlProperty(namespace = ATOM) @@ -27,7 +24,6 @@ class OpdsEntryNavigation( updated: ZonedDateTime, id: String, content: String, - @JacksonXmlProperty(namespace = ATOM) val link: OpdsLink, ) : OpdsEntry(title, updated, id, content) @@ -37,11 +33,9 @@ class OpdsEntryAcquisition( updated: ZonedDateTime, id: String, content: String, - @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "author", namespace = ATOM) val authors: List = emptyList(), - @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "link", namespace = ATOM) val links: List, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsFeed.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsFeed.kt index 242fedccb..be86b6b02 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsFeed.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsFeed.kt @@ -10,20 +10,15 @@ import java.time.ZonedDateTime abstract class OpdsFeed( @JacksonXmlProperty(namespace = ATOM) val id: String, - @JacksonXmlProperty(namespace = ATOM) val title: String, - @JacksonXmlProperty(namespace = ATOM) val updated: ZonedDateTime, - @JacksonXmlProperty(namespace = ATOM) val author: OpdsAuthor, - @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "link", namespace = ATOM) val links: List, - @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "entry", namespace = ATOM) val entries: List, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsLink.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsLink.kt index 03ad0745b..9847cc02f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsLink.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpdsLink.kt @@ -8,10 +8,8 @@ import java.time.LocalDateTime open class OpdsLink( @get:JacksonXmlProperty(isAttribute = true) val type: String, - @get:JacksonXmlProperty(isAttribute = true) val rel: String, - @get:JacksonXmlProperty(isAttribute = true) val href: String, ) @@ -61,18 +59,15 @@ class OpdsLinkSearch(href: String) : OpdsLink( class OpdsLinkPageStreaming( mediaType: String, href: String, - @get:JacksonXmlProperty(isAttribute = true, namespace = OPDS_PSE) val count: Int, - @get:JacksonXmlProperty(isAttribute = true, namespace = OPDS_PSE) val lastRead: Int?, - @get:JacksonXmlProperty(isAttribute = true, namespace = OPDS_PSE) @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val lastReadDate: LocalDateTime?, ) : OpdsLink( - type = mediaType, - rel = "http://vaemendis.net/opds-pse/stream", - href = href, -) + type = mediaType, + rel = "http://vaemendis.net/opds-pse/stream", + href = href, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpenSearchDescription.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpenSearchDescription.kt index 403127344..84f2f8518 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpenSearchDescription.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/OpenSearchDescription.kt @@ -9,17 +9,13 @@ class OpenSearchDescription( @JacksonXmlProperty(localName = "ShortName", namespace = OPENSEARCH) @Size(min = 1, max = 16) val shortName: String, - @JacksonXmlProperty(localName = "Description", namespace = OPENSEARCH) @Size(min = 1, max = 1024) val description: String, - @JacksonXmlProperty(localName = "InputEncoding", namespace = OPENSEARCH) val inputEncoding: String = "UTF-8", - @JacksonXmlProperty(localName = "OutputEncoding", namespace = OPENSEARCH) val outputEncoding: String = "UTF-8", - @JacksonXmlProperty(localName = "Url", namespace = OPENSEARCH) val url: OpenSearchUrl, ) { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/XmlNamespaces.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/XmlNamespaces.kt index 7aa1558c9..853bb5717 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/XmlNamespaces.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v1/dto/XmlNamespaces.kt @@ -4,6 +4,7 @@ const val ATOM = "http://www.w3.org/2005/Atom" const val OPDS_PSE = "http://vaemendis.net/opds-pse/ns" const val OPENSEARCH = "http://a9.com/-/spec/opensearch/1.1/" -val prefixToNamespace = mapOf( - "pse" to OPDS_PSE, -) +val prefixToNamespace = + mapOf( + "pse" to OPDS_PSE, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v2/Opds2Controller.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v2/Opds2Controller.kt index c84099cda..fc4097181 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v2/Opds2Controller.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/opds/v2/Opds2Controller.kt @@ -60,43 +60,58 @@ class Opds2Controller( private val referentialRepository: ReferentialRepository, private val webPubGenerator: WebPubGenerator, ) { - private fun linkStart() = WPLinkDto( - title = "Home", - rel = OpdsLinkRel.START, - type = MEDIATYPE_OPDS_JSON_VALUE, - href = uriBuilder(ROUTE_CATALOG).toUriString(), - ) + private fun linkStart() = + WPLinkDto( + title = "Home", + rel = OpdsLinkRel.START, + type = MEDIATYPE_OPDS_JSON_VALUE, + href = uriBuilder(ROUTE_CATALOG).toUriString(), + ) - private fun linkSearch() = WPLinkDto( - title = "Search", - rel = OpdsLinkRel.SEARCH, - type = MEDIATYPE_OPDS_JSON_VALUE, - href = uriBuilder("search").toUriString() + "{?query}", - templated = true, - ) + private fun linkSearch() = + WPLinkDto( + title = "Search", + rel = OpdsLinkRel.SEARCH, + type = MEDIATYPE_OPDS_JSON_VALUE, + href = uriBuilder("search").toUriString() + "{?query}", + templated = true, + ) private fun uriBuilder(path: String) = ServletUriComponentsBuilder.fromCurrentContextPath().pathSegment("opds", "v2").path(path) - private fun linkPage(uriBuilder: UriComponentsBuilder, page: Page<*>): List { + private fun linkPage( + uriBuilder: UriComponentsBuilder, + page: Page<*>, + ): List { return listOfNotNull( - if (!page.isFirst) WPLinkDto( - rel = OpdsLinkRel.PREVIOUS, - href = uriBuilder.cloneBuilder().queryParam("page", page.pageable.previousOrFirst().pageNumber).toUriString(), - ) - else null, - if (!page.isLast) WPLinkDto( - rel = OpdsLinkRel.NEXT, - href = uriBuilder.cloneBuilder().queryParam("page", page.pageable.next().pageNumber).toUriString(), - ) - else null, + if (!page.isFirst) + WPLinkDto( + rel = OpdsLinkRel.PREVIOUS, + href = uriBuilder.cloneBuilder().queryParam("page", page.pageable.previousOrFirst().pageNumber).toUriString(), + ) + else + null, + if (!page.isLast) + WPLinkDto( + rel = OpdsLinkRel.NEXT, + href = uriBuilder.cloneBuilder().queryParam("page", page.pageable.next().pageNumber).toUriString(), + ) + else + null, ) } - private fun linkSelf(path: String, type: String? = null) = + private fun linkSelf( + path: String, + type: String? = null, + ) = linkSelf(uriBuilder(path), type) - private fun linkSelf(uriBuilder: UriComponentsBuilder, type: String? = null) = + private fun linkSelf( + uriBuilder: UriComponentsBuilder, + type: String? = null, + ) = WPLinkDto( rel = OpdsLinkRel.SELF, href = uriBuilder.toUriString(), @@ -113,15 +128,19 @@ class Opds2Controller( libraryRepository.findAllByIds(principal.user.sharedLibrariesIds) } return FeedGroupDto( - metadata = FeedMetadataDto( - title = "Libraries", - ), + metadata = + FeedMetadataDto( + title = "Libraries", + ), links = listOf(linkSelf("libraries")), navigation = libraries.map { it.toWPLinkDto() }, ) } - private fun getLibraryNavigation(user: KomgaUser, libraryId: String?): List { + private fun getLibraryNavigation( + user: KomgaUser, + libraryId: String?, + ): List { val uriBuilder = uriBuilder("libraries${if (libraryId != null) "/$libraryId" else ""}") val collections = collectionRepository.findAll(libraryId?.let { listOf(it) }, restrictions = user.restrictions, pageable = Pageable.ofSize(1)) @@ -130,137 +149,161 @@ class Opds2Controller( return listOfNotNull( WPLinkDto("Recommended", OpdsLinkRel.SUBSECTION, href = uriBuilder.toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), WPLinkDto("Browse", OpdsLinkRel.SUBSECTION, href = uriBuilder.cloneBuilder().pathSegment("browse").toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), - if (collections.isEmpty) null - else WPLinkDto("Collections", OpdsLinkRel.SUBSECTION, href = uriBuilder.cloneBuilder().pathSegment("collections").toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), - if (readLists.isEmpty) null - else WPLinkDto("Read lists", OpdsLinkRel.SUBSECTION, href = uriBuilder.cloneBuilder().pathSegment("readlists").toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), + if (collections.isEmpty) + null + else + WPLinkDto("Collections", OpdsLinkRel.SUBSECTION, href = uriBuilder.cloneBuilder().pathSegment("collections").toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), + if (readLists.isEmpty) + null + else + WPLinkDto("Read lists", OpdsLinkRel.SUBSECTION, href = uriBuilder.cloneBuilder().pathSegment("readlists").toUriString(), type = MEDIATYPE_OPDS_JSON_VALUE), ) } @GetMapping(value = [ROUTE_CATALOG, "libraries", "libraries/{id}"]) fun getLibrariesRecommended( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable(name = "id", required = false) libraryId: String?, ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val keepReading = bookDtoRepository.findAll( - BookSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - readStatus = setOf(ReadStatus.IN_PROGRESS), - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ), - principal.user.id, - PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("readProgress.readDate"))), - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val keepReading = + bookDtoRepository.findAll( + BookSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + readStatus = setOf(ReadStatus.IN_PROGRESS), + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ), + principal.user.id, + PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("readProgress.readDate"))), + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } - val onDeck = bookDtoRepository.findAllOnDeck( - principal.user.id, - authorizedLibraryIds, - Pageable.ofSize(RECOMMENDED_ITEMS_NUMBER), - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val onDeck = + bookDtoRepository.findAllOnDeck( + principal.user.id, + authorizedLibraryIds, + Pageable.ofSize(RECOMMENDED_ITEMS_NUMBER), + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } - val latestBooks = bookDtoRepository.findAll( - BookSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ), - principal.user.id, - PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("createdDate"))), - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val latestBooks = + bookDtoRepository.findAll( + BookSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ), + principal.user.id, + PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("createdDate"))), + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } - val latestSeries = seriesDtoRepository.findAll( - SeriesSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - deleted = false, - oneshot = false, - ), - principal.user.id, - PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("lastModified"))), - principal.user.restrictions, - ).map { it.toWPLinkDto() } + val latestSeries = + seriesDtoRepository.findAll( + SeriesSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + deleted = false, + oneshot = false, + ), + principal.user.id, + PageRequest.of(0, RECOMMENDED_ITEMS_NUMBER, Sort.by(Sort.Order.desc("lastModified"))), + principal.user.restrictions, + ).map { it.toWPLinkDto() } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Recommended", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Recommended", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + ), navigation = getLibraryNavigation(principal.user, libraryId), - groups = listOfNotNull( - if (library == null) getLibrariesFeedGroup(principal) else null, - if (!keepReading.isEmpty) FeedGroupDto( - FeedMetadataDto("Keep Reading", page = keepReading), - links = listOf(WPLinkDto("Keep Reading", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("keep-reading").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), - publications = keepReading.content, - ) else null, - if (!onDeck.isEmpty) FeedGroupDto( - FeedMetadataDto("On Deck", page = onDeck), - links = listOf(WPLinkDto("On Deck", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("on-deck").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), - publications = onDeck.content, - ) else null, - if (!latestBooks.isEmpty) FeedGroupDto( - FeedMetadataDto("Latest Books", page = latestBooks), - links = listOf(WPLinkDto("Latest Books", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("books", "latest").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), - publications = latestBooks.content, - ) else null, - if (!latestSeries.isEmpty) FeedGroupDto( - FeedMetadataDto("Latest Series", page = latestSeries), - links = listOf(WPLinkDto("Latest Series", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("series", "latest").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), - navigation = latestSeries.content, - ) else null, - ), + groups = + listOfNotNull( + if (library == null) getLibrariesFeedGroup(principal) else null, + if (!keepReading.isEmpty) + FeedGroupDto( + FeedMetadataDto("Keep Reading", page = keepReading), + links = listOf(WPLinkDto("Keep Reading", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("keep-reading").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), + publications = keepReading.content, + ) + else + null, + if (!onDeck.isEmpty) + FeedGroupDto( + FeedMetadataDto("On Deck", page = onDeck), + links = listOf(WPLinkDto("On Deck", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("on-deck").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), + publications = onDeck.content, + ) + else + null, + if (!latestBooks.isEmpty) + FeedGroupDto( + FeedMetadataDto("Latest Books", page = latestBooks), + links = listOf(WPLinkDto("Latest Books", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("books", "latest").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), + publications = latestBooks.content, + ) + else + null, + if (!latestSeries.isEmpty) + FeedGroupDto( + FeedMetadataDto("Latest Series", page = latestSeries), + links = listOf(WPLinkDto("Latest Series", OpdsLinkRel.SELF, uriBuilder.cloneBuilder().pathSegment("series", "latest").toUriString(), MEDIATYPE_OPDS_JSON_VALUE)), + navigation = latestSeries.content, + ) + else + null, + ), ) } @PageAsQueryParam @GetMapping(value = ["libraries/keep-reading", "libraries/{id}/keep-reading"]) fun getKeepReading( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable(name = "id", required = false) libraryId: String?, @Parameter(hidden = true) page: Pageable, ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val entries = bookDtoRepository.findAll( - BookSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - readStatus = setOf(ReadStatus.IN_PROGRESS), - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ), - principal.user.id, - PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("readProgress.readDate"))), - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val entries = + bookDtoRepository.findAll( + BookSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + readStatus = setOf(ReadStatus.IN_PROGRESS), + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ), + principal.user.id, + PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("readProgress.readDate"))), + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/keep-reading") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Keep Reading", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Keep Reading", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), publications = entries.content, ) } @@ -268,34 +311,36 @@ class Opds2Controller( @PageAsQueryParam @GetMapping(value = ["libraries/on-deck", "libraries/{id}/on-deck"]) fun getOnDeck( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable(name = "id", required = false) libraryId: String?, @Parameter(hidden = true) page: Pageable, ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val entries = bookDtoRepository.findAllOnDeck( - principal.user.id, - authorizedLibraryIds, - page, - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val entries = + bookDtoRepository.findAllOnDeck( + principal.user.id, + authorizedLibraryIds, + page, + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/on-deck") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - On Deck", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - On Deck", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), publications = entries.content, ) } @@ -303,38 +348,40 @@ class Opds2Controller( @PageAsQueryParam @GetMapping(value = ["libraries/books/latest", "libraries/{id}/books/latest"]) fun getLatestBooks( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable(name = "id", required = false) libraryId: String?, @Parameter(hidden = true) page: Pageable, ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val entries = bookDtoRepository.findAll( - BookSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ), - principal.user.id, - PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("createdDate"))), - principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + val entries = + bookDtoRepository.findAll( + BookSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ), + principal.user.id, + PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("createdDate"))), + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/books/latest") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Latest Books", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Latest Books", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), publications = entries.content, ) } @@ -348,31 +395,34 @@ class Opds2Controller( ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val entries = seriesDtoRepository.findAll( - SeriesSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - deleted = false, - oneshot = false, - ), - principal.user.id, - PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("lastModified"))), - principal.user.restrictions, - ).map { it.toWPLinkDto() } + val entries = + seriesDtoRepository.findAll( + SeriesSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + deleted = false, + oneshot = false, + ), + principal.user.id, + PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.desc("lastModified"))), + principal.user.restrictions, + ).map { it.toWPLinkDto() } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/books/latest") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Latest Series", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Latest Series", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), navigation = entries.content, ) } @@ -387,46 +437,52 @@ class Opds2Controller( ): FeedDto { val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = authorizedLibraryIds, - publishers = publishers, - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = authorizedLibraryIds, + publishers = publishers, + deleted = false, + ) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("metadata.titleSort"))) - val entries = seriesDtoRepository - .findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) - .map { it.toWPLinkDto() } + val entries = + seriesDtoRepository + .findAll(seriesSearch, principal.user.id, pageable, principal.user.restrictions) + .map { it.toWPLinkDto() } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/browse") - val publisherLinks = referentialRepository.findAllPublishers(authorizedLibraryIds) - .map { - WPLinkDto( - title = it, - href = uriBuilder.cloneBuilder().queryParam("publisher", it).toUriString(), - type = MEDIATYPE_OPDS_JSON_VALUE, - ) - } + val publisherLinks = + referentialRepository.findAllPublishers(authorizedLibraryIds) + .map { + WPLinkDto( + title = it, + href = uriBuilder.cloneBuilder().queryParam("publisher", it).toUriString(), + type = MEDIATYPE_OPDS_JSON_VALUE, + ) + } return FeedDto( - metadata = FeedMetadataDto( - title = library?.name ?: "All libraries", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = library?.name ?: "All libraries", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), navigation = getLibraryNavigation(principal.user, libraryId), - groups = listOfNotNull( - FeedGroupDto(FeedMetadataDto("Series"), navigation = entries.content), - if (publisherLinks.isNotEmpty()) FeedGroupDto(FeedMetadataDto("Publisher"), navigation = publisherLinks) else null, - ), + groups = + listOfNotNull( + FeedGroupDto(FeedMetadataDto("Series"), navigation = entries.content), + if (publisherLinks.isNotEmpty()) FeedGroupDto(FeedMetadataDto("Publisher"), navigation = publisherLinks) else null, + ), ) } @@ -440,30 +496,34 @@ class Opds2Controller( val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("name"))) - val entries = collectionRepository.findAll( - authorizedLibraryIds, - authorizedLibraryIds, - pageable = pageable, - ).map { it.toWPLinkDto() } + val entries = + collectionRepository.findAll( + authorizedLibraryIds, + authorizedLibraryIds, + pageable = pageable, + ).map { it.toWPLinkDto() } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/collections") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Collections", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Collections", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), navigation = getLibraryNavigation(principal.user, libraryId), - groups = listOfNotNull( - FeedGroupDto(FeedMetadataDto("Collections"), navigation = entries.content), - ), + groups = + listOfNotNull( + FeedGroupDto(FeedMetadataDto("Collections"), navigation = entries.content), + ), ) } @@ -476,17 +536,21 @@ class Opds2Controller( ): FeedDto = collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { collection -> val sort = - if (collection.ordered) Sort.by(Sort.Order.asc("collection.number")) - else Sort.by(Sort.Order.asc("metadata.titleSort")) + if (collection.ordered) + Sort.by(Sort.Order.asc("collection.number")) + else + Sort.by(Sort.Order.asc("metadata.titleSort")) val pageable = PageRequest.of(page.pageNumber, page.pageSize, sort) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - deleted = false, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + deleted = false, + ) - val entries = seriesDtoRepository.findAllByCollectionId(collection.id, seriesSearch, principal.user.id, pageable, principal.user.restrictions) - .map { it.toWPLinkDto() } + val entries = + seriesDtoRepository.findAllByCollectionId(collection.id, seriesSearch, principal.user.id, pageable, principal.user.restrictions) + .map { it.toWPLinkDto() } val uriBuilder = uriBuilder("collections/$id") @@ -496,12 +560,13 @@ class Opds2Controller( modified = collection.lastModifiedDate.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), page = entries, ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), navigation = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -516,60 +581,67 @@ class Opds2Controller( val (library, authorizedLibraryIds) = checkLibraryAccess(libraryId, principal) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("name"))) - val entries = readListRepository.findAll( - authorizedLibraryIds, - authorizedLibraryIds, - pageable = pageable, - ).map { it.toWPLinkDto() } + val entries = + readListRepository.findAll( + authorizedLibraryIds, + authorizedLibraryIds, + pageable = pageable, + ).map { it.toWPLinkDto() } val uriBuilder = uriBuilder("libraries${if (library != null) "/${library.id}" else ""}/readlists") return FeedDto( - metadata = FeedMetadataDto( - title = (library?.name ?: "All libraries") + " - Read Lists", - modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = (library?.name ?: "All libraries") + " - Read Lists", + modified = library?.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), navigation = getLibraryNavigation(principal.user, libraryId), - groups = listOfNotNull( - FeedGroupDto(FeedMetadataDto("Read Lists"), navigation = entries.content), - ), + groups = + listOfNotNull( + FeedGroupDto(FeedMetadataDto("Read Lists"), navigation = entries.content), + ), ) } @PageAsQueryParam @GetMapping("readlists/{id}") fun getOneReadList( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable id: String, @Parameter(hidden = true) page: Pageable, ): FeedDto = readListRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { readList -> val sort = - if (readList.ordered) Sort.by(Sort.Order.asc("readList.number")) - else Sort.by(Sort.Order.asc("metadata.releaseDate")) + if (readList.ordered) + Sort.by(Sort.Order.asc("readList.number")) + else + Sort.by(Sort.Order.asc("metadata.releaseDate")) val pageable = PageRequest.of(page.pageNumber, page.pageSize, sort) - val bookSearch = BookSearchWithReadProgress( - mediaStatus = setOf(Media.Status.READY), - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + mediaStatus = setOf(Media.Status.READY), + deleted = false, + ) - val booksPage = bookDtoRepository.findAllByReadListId( - readList.id, - principal.user.id, - principal.user.getAuthorizedLibraryIds(null), - bookSearch, - pageable, - principal.user.restrictions, - ) + val booksPage = + bookDtoRepository.findAllByReadListId( + readList.id, + principal.user.id, + principal.user.getAuthorizedLibraryIds(null), + bookSearch, + pageable, + principal.user.restrictions, + ) val entries = booksPage.map { webPubGenerator.toOpdsPublicationDto(it, true) } @@ -581,23 +653,29 @@ class Opds2Controller( modified = readList.lastModifiedDate.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(), page = entries, ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, booksPage).toTypedArray(), - ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, booksPage).toTypedArray(), + ), publications = entries.content, ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) - private fun checkLibraryAccess(libraryId: String?, principal: KomgaPrincipal): Pair?> { - val library = if (libraryId != null) - libraryRepository.findByIdOrNull(libraryId)?.let { library -> - if (!principal.user.canAccessLibrary(library)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - library - } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) - else null + private fun checkLibraryAccess( + libraryId: String?, + principal: KomgaPrincipal, + ): Pair?> { + val library = + if (libraryId != null) + libraryRepository.findByIdOrNull(libraryId)?.let { library -> + if (!principal.user.canAccessLibrary(library)) throw ResponseStatusException(HttpStatus.FORBIDDEN) + library + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + else + null val libraryIds = principal.user.getAuthorizedLibraryIds(if (libraryId != null) listOf(libraryId) else null) return Pair(library, libraryIds) @@ -606,7 +684,6 @@ class Opds2Controller( @PageAsQueryParam @GetMapping("series/{id}") fun getOneSeries( - @AuthenticationPrincipal principal: KomgaPrincipal, @PathVariable id: String, @RequestParam(name = "tag", required = false) tag: String? = null, @@ -615,46 +692,52 @@ class Opds2Controller( seriesDtoRepository.findByIdOrNull(id, "principal.user.id")?.let { series -> principal.user.checkContentRestriction(series) - val bookSearch = BookSearchWithReadProgress( - seriesIds = listOf(id), - mediaStatus = setOf(Media.Status.READY), - tags = if (tag != null) listOf(tag) else null, - deleted = false, - ) + val bookSearch = + BookSearchWithReadProgress( + seriesIds = listOf(id), + mediaStatus = setOf(Media.Status.READY), + tags = if (tag != null) listOf(tag) else null, + deleted = false, + ) val pageable = PageRequest.of(page.pageNumber, page.pageSize, Sort.by(Sort.Order.asc("metadata.numberSort"))) - val entries = bookDtoRepository.findAll(bookSearch, principal.user.id, pageable, principal.user.restrictions) - .map { webPubGenerator.toOpdsPublicationDto(it, true) } + val entries = + bookDtoRepository.findAll(bookSearch, principal.user.id, pageable, principal.user.restrictions) + .map { webPubGenerator.toOpdsPublicationDto(it, true) } val uriBuilder = uriBuilder("series/$id") - val tagLinks = referentialRepository.findAllBookTagsBySeries(series.id, null) - .map { - WPLinkDto( - title = it, - href = uriBuilder.cloneBuilder().queryParam("tag", it).toUriString(), - type = MEDIATYPE_OPDS_JSON_VALUE, - rel = if (it == tag) OpdsLinkRel.SELF else null, - ) - } + val tagLinks = + referentialRepository.findAllBookTagsBySeries(series.id, null) + .map { + WPLinkDto( + title = it, + href = uriBuilder.cloneBuilder().queryParam("tag", it).toUriString(), + type = MEDIATYPE_OPDS_JSON_VALUE, + rel = if (it == tag) OpdsLinkRel.SELF else null, + ) + } FeedDto( - metadata = FeedMetadataDto( - title = series.metadata.title, - modified = series.lastModified.toZonedDateTime(), - description = series.metadata.summary.ifBlank { series.booksMetadata.summary }, - page = entries, - ), - links = listOf( - linkSelf(uriBuilder), - linkStart(), - linkSearch(), - *linkPage(uriBuilder, entries).toTypedArray(), - ), + metadata = + FeedMetadataDto( + title = series.metadata.title, + modified = series.lastModified.toZonedDateTime(), + description = series.metadata.summary.ifBlank { series.booksMetadata.summary }, + page = entries, + ), + links = + listOf( + linkSelf(uriBuilder), + linkStart(), + linkSearch(), + *linkPage(uriBuilder, entries).toTypedArray(), + ), publications = entries.content, - facets = listOfNotNull( - if (tagLinks.isNotEmpty()) FacetDto(FeedMetadataDto("Tag"), tagLinks) else null, - ), + facets = + listOfNotNull( + if (tagLinks.isNotEmpty()) FacetDto(FeedMetadataDto("Tag"), tagLinks) else null, + ), ) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -665,63 +748,70 @@ class Opds2Controller( ): FeedDto { val pageable = PageRequest.of(0, 20, Sort.by("relevance")) - val resultsSeries = seriesDtoRepository - .findAll( - SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - searchTerm = query, - oneshot = false, - deleted = false, - ), - principal.user.id, + val resultsSeries = + seriesDtoRepository + .findAll( + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + searchTerm = query, + oneshot = false, + deleted = false, + ), + principal.user.id, + pageable, + principal.user.restrictions, + ) + .map { it.toWPLinkDto() } + + val resultsBooks = + bookDtoRepository + .findAll( + BookSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(null), + searchTerm = query, + deleted = false, + ), + principal.user.id, + pageable, + principal.user.restrictions, + ).map { webPubGenerator.toOpdsPublicationDto(it, true) } + + val resultsCollections = + collectionRepository.findAll( + principal.user.getAuthorizedLibraryIds(null), + principal.user.getAuthorizedLibraryIds(null), + query, pageable, principal.user.restrictions, - ) - .map { it.toWPLinkDto() } + ).map { it.toWPLinkDto() } - val resultsBooks = bookDtoRepository - .findAll( - BookSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(null), - searchTerm = query, - deleted = false, - ), - principal.user.id, + val resultsReadLists = + readListRepository.findAll( + principal.user.getAuthorizedLibraryIds(null), + principal.user.getAuthorizedLibraryIds(null), + query, pageable, principal.user.restrictions, - ).map { webPubGenerator.toOpdsPublicationDto(it, true) } - - val resultsCollections = collectionRepository.findAll( - principal.user.getAuthorizedLibraryIds(null), - principal.user.getAuthorizedLibraryIds(null), - query, - pageable, - principal.user.restrictions, - ).map { it.toWPLinkDto() } - - val resultsReadLists = readListRepository.findAll( - principal.user.getAuthorizedLibraryIds(null), - principal.user.getAuthorizedLibraryIds(null), - query, - pageable, - principal.user.restrictions, - ).map { it.toWPLinkDto() } + ).map { it.toWPLinkDto() } return FeedDto( - metadata = FeedMetadataDto( - title = "Search results", - modified = ZonedDateTime.now(), - ), - links = listOf( - linkStart(), - linkSearch(), - ), - groups = listOfNotNull( - if (!resultsSeries.isEmpty) FeedGroupDto(FeedMetadataDto("Series"), navigation = resultsSeries.content) else null, - if (!resultsBooks.isEmpty) FeedGroupDto(FeedMetadataDto("Books"), publications = resultsBooks.content) else null, - if (!resultsCollections.isEmpty) FeedGroupDto(FeedMetadataDto("Collections"), navigation = resultsCollections.content) else null, - if (!resultsReadLists.isEmpty) FeedGroupDto(FeedMetadataDto("Read Lists"), navigation = resultsReadLists.content) else null, - ), + metadata = + FeedMetadataDto( + title = "Search results", + modified = ZonedDateTime.now(), + ), + links = + listOf( + linkStart(), + linkSearch(), + ), + groups = + listOfNotNull( + if (!resultsSeries.isEmpty) FeedGroupDto(FeedMetadataDto("Series"), navigation = resultsSeries.content) else null, + if (!resultsBooks.isEmpty) FeedGroupDto(FeedMetadataDto("Books"), publications = resultsBooks.content) else null, + if (!resultsCollections.isEmpty) FeedGroupDto(FeedMetadataDto("Collections"), navigation = resultsCollections.content) else null, + if (!resultsReadLists.isEmpty) FeedGroupDto(FeedMetadataDto("Read Lists"), navigation = resultsReadLists.content) else null, + ), ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/BookDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/BookDtoRepository.kt index 78c439a6f..97541dc7a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/BookDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/BookDtoRepository.kt @@ -8,7 +8,12 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable interface BookDtoRepository { - fun findAll(search: BookSearchWithReadProgress, userId: String, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page + fun findAll( + search: BookSearchWithReadProgress, + userId: String, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page /** * Find books that are part of a readlist, optionally filtered by library @@ -22,10 +27,20 @@ interface BookDtoRepository { restrictions: ContentRestrictions = ContentRestrictions(), ): Page - fun findByIdOrNull(bookId: String, userId: String): BookDto? + fun findByIdOrNull( + bookId: String, + userId: String, + ): BookDto? - fun findPreviousInSeriesOrNull(bookId: String, userId: String): BookDto? - fun findNextInSeriesOrNull(bookId: String, userId: String): BookDto? + fun findPreviousInSeriesOrNull( + bookId: String, + userId: String, + ): BookDto? + + fun findNextInSeriesOrNull( + bookId: String, + userId: String, + ): BookDto? fun findPreviousInReadListOrNull( readList: ReadList, @@ -43,7 +58,15 @@ interface BookDtoRepository { restrictions: ContentRestrictions = ContentRestrictions(), ): BookDto? - fun findAllOnDeck(userId: String, filterOnLibraryIds: Collection?, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page + fun findAllOnDeck( + userId: String, + filterOnLibraryIds: Collection?, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page - fun findAllDuplicates(userId: String, pageable: Pageable): Page + fun findAllDuplicates( + userId: String, + pageable: Pageable, + ): Page } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/ReadProgressDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/ReadProgressDtoRepository.kt index d2fe11a58..2f66b0661 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/ReadProgressDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/ReadProgressDtoRepository.kt @@ -4,6 +4,13 @@ import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressDto import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressV2Dto interface ReadProgressDtoRepository { - fun findProgressV2BySeries(seriesId: String, userId: String): TachiyomiReadProgressV2Dto - fun findProgressByReadList(readListId: String, userId: String): TachiyomiReadProgressDto + fun findProgressV2BySeries( + seriesId: String, + userId: String, + ): TachiyomiReadProgressV2Dto + + fun findProgressByReadList( + readListId: String, + userId: String, + ): TachiyomiReadProgressDto } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/SeriesDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/SeriesDtoRepository.kt index 87a4036cd..5a27ede8d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/SeriesDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/persistence/SeriesDtoRepository.kt @@ -8,11 +8,36 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable interface SeriesDtoRepository { - fun findByIdOrNull(seriesId: String, userId: String): SeriesDto? + fun findByIdOrNull( + seriesId: String, + userId: String, + ): SeriesDto? - fun findAll(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page - fun findAllByCollectionId(collectionId: String, search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable, restrictions: ContentRestrictions = ContentRestrictions()): Page - fun findAllRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: String, restrictions: ContentRestrictions, pageable: Pageable): Page + fun findAll( + search: SeriesSearchWithReadProgress, + userId: String, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page - fun countByFirstCharacter(search: SeriesSearchWithReadProgress, userId: String, restrictions: ContentRestrictions): List + fun findAllByCollectionId( + collectionId: String, + search: SeriesSearchWithReadProgress, + userId: String, + pageable: Pageable, + restrictions: ContentRestrictions = ContentRestrictions(), + ): Page + + fun findAllRecentlyUpdated( + search: SeriesSearchWithReadProgress, + userId: String, + restrictions: ContentRestrictions, + pageable: Pageable, + ): Page + + fun countByFirstCharacter( + search: SeriesSearchWithReadProgress, + userId: String, + restrictions: ContentRestrictions, + ): List } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementController.kt index cb9124238..85d851b3d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementController.kt @@ -19,19 +19,19 @@ import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.server.ResponseStatusException import java.util.concurrent.TimeUnit -private const val website = "https://komga.org" +private const val WEBSITE = "https://komga.org" @RestController @RequestMapping("api/v1/announcements", produces = [MediaType.APPLICATION_JSON_VALUE]) class AnnouncementController( private val userRepository: KomgaUserRepository, ) { + private val webClient = WebClient.create("$WEBSITE/blog/feed.json") - private val webClient = WebClient.create("$website/blog/feed.json") - - private val cache = Caffeine.newBuilder() - .expireAfterAccess(1, TimeUnit.DAYS) - .build() + private val cache = + Caffeine.newBuilder() + .expireAfterAccess(1, TimeUnit.DAYS) + .build() @GetMapping @PreAuthorize("hasRole('$ROLE_ADMIN')") @@ -57,10 +57,11 @@ class AnnouncementController( } fun fetchWebsiteAnnouncements(): JsonFeedDto? { - val response = webClient.get() - .retrieve() - .toEntity(JsonFeedDto::class.java) - .block() + val response = + webClient.get() + .retrieve() + .toEntity(JsonFeedDto::class.java) + .block() return response?.body } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt index d5774e83b..8c9d28f65 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/BookController.kt @@ -136,7 +136,6 @@ class BookController( private val thumbnailBookRepository: ThumbnailBookRepository, private val webPubGenerator: WebPubGenerator, ) { - @PageableAsQueryParam @GetMapping("api/v1/books") fun getAllBooks( @@ -160,21 +159,24 @@ class BookController( } val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) - val bookSearch = BookSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), - searchTerm = searchTerm, - mediaStatus = mediaStatus, - readStatus = readStatus, - releasedAfter = releasedAfter, - tags = tags, - ) + val bookSearch = + BookSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), + searchTerm = searchTerm, + mediaStatus = mediaStatus, + readStatus = readStatus, + releasedAfter = releasedAfter, + tags = tags, + ) return bookDtoRepository.findAll(bookSearch, principal.user.id, pageRequest, principal.user.restrictions) .map { it.restrictUrl(!principal.user.roleAdmin) } @@ -191,12 +193,14 @@ class BookController( val sort = Sort.by(Sort.Order.desc("lastModifiedDate")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return bookDtoRepository.findAll( BookSearchWithReadProgress( @@ -238,12 +242,14 @@ class BookController( } val pageRequest = - if (unpaged) Pageable.unpaged() - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + Pageable.unpaged() + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return bookDtoRepository.findAllDuplicates(principal.user.id, pageRequest) } @@ -411,18 +417,20 @@ class BookController( val media = mediaRepository.findById(book.id) with(FileSystemResource(book.path)) { if (!exists()) throw FileNotFoundException(path) - val stream = StreamingResponseBody { os: OutputStream -> - this.inputStream.use { - IOUtils.copyLarge(it, os, ByteArray(8192)) - os.close() + val stream = + StreamingResponseBody { os: OutputStream -> + this.inputStream.use { + IOUtils.copyLarge(it, os, ByteArray(8192)) + os.close() + } } - } ResponseEntity.ok() .headers( HttpHeaders().apply { - contentDisposition = ContentDisposition.builder("attachment") - .filename(book.path.name, UTF_8) - .build() + contentDisposition = + ContentDisposition.builder("attachment") + .filename(book.path.name, UTF_8) + .build() }, ) .contentType(getMediaTypeOrDefault(media.mediaType)) @@ -509,59 +517,69 @@ class BookController( ): ResponseEntity = getBookPageInternal(bookId, if (zeroBasedIndex) pageNumber + 1 else pageNumber, convertTo, request, principal, acceptHeaders) - private fun getBookPageInternal(bookId: String, pageNumber: Int, convertTo: String?, request: ServletWebRequest, principal: KomgaPrincipal, acceptHeaders: MutableList?) = bookRepository.findByIdOrNull((bookId))?.let { book -> - val media = mediaRepository.findById(bookId) - if (request.checkNotModified(getBookLastModified(media))) { - return@let ResponseEntity - .status(HttpStatus.NOT_MODIFIED) - .setNotModified(media) - .body(ByteArray(0)) - } - - principal.user.checkContentRestriction(book) - - if (media.profile == MediaProfile.PDF && acceptHeaders != null && acceptHeaders.any { it.isCompatibleWith(MediaType.APPLICATION_PDF) }) { - // keep only pdf and image - acceptHeaders.removeIf { !it.isCompatibleWith(MediaType.APPLICATION_PDF) && !it.isCompatibleWith(MediaType("image")) } - MimeTypeUtils.sortBySpecificity(acceptHeaders) - if (acceptHeaders.first().isCompatibleWith(MediaType.APPLICATION_PDF)) - return getBookPageRaw(book, media, pageNumber) - } - - try { - val convertFormat = when (convertTo?.lowercase()) { - "jpeg" -> ImageType.JPEG - "png" -> ImageType.PNG - "", null -> null - else -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid conversion format: $convertTo") + private fun getBookPageInternal( + bookId: String, + pageNumber: Int, + convertTo: String?, + request: ServletWebRequest, + principal: KomgaPrincipal, + acceptHeaders: MutableList?, + ) = + bookRepository.findByIdOrNull((bookId))?.let { book -> + val media = mediaRepository.findById(bookId) + if (request.checkNotModified(getBookLastModified(media))) { + return@let ResponseEntity + .status(HttpStatus.NOT_MODIFIED) + .setNotModified(media) + .body(ByteArray(0)) } - val pageContent = bookLifecycle.getBookPage(book, pageNumber, convertFormat) + principal.user.checkContentRestriction(book) - ResponseEntity.ok() - .headers( - HttpHeaders().apply { - val extension = contentDetector.mediaTypeToExtension(pageContent.mediaType) ?: "jpeg" - val imageFileName = "${book.name}-$pageNumber$extension" - contentDisposition = ContentDisposition.builder("inline") - .filename(imageFileName, UTF_8) - .build() - }, - ) - .contentType(getMediaTypeOrDefault(pageContent.mediaType)) - .setNotModified(media) - .body(pageContent.bytes) - } catch (ex: IndexOutOfBoundsException) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist") - } catch (ex: ImageConversionException) { - throw ResponseStatusException(HttpStatus.NOT_FOUND, ex.message) - } catch (ex: MediaNotReadyException) { - throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed") - } catch (ex: NoSuchFileException) { - logger.warn(ex) { "File not found: $book" } - throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved") - } - } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + if (media.profile == MediaProfile.PDF && acceptHeaders != null && acceptHeaders.any { it.isCompatibleWith(MediaType.APPLICATION_PDF) }) { + // keep only pdf and image + acceptHeaders.removeIf { !it.isCompatibleWith(MediaType.APPLICATION_PDF) && !it.isCompatibleWith(MediaType("image")) } + MimeTypeUtils.sortBySpecificity(acceptHeaders) + if (acceptHeaders.first().isCompatibleWith(MediaType.APPLICATION_PDF)) + return getBookPageRaw(book, media, pageNumber) + } + + try { + val convertFormat = + when (convertTo?.lowercase()) { + "jpeg" -> ImageType.JPEG + "png" -> ImageType.PNG + "", null -> null + else -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid conversion format: $convertTo") + } + + val pageContent = bookLifecycle.getBookPage(book, pageNumber, convertFormat) + + ResponseEntity.ok() + .headers( + HttpHeaders().apply { + val extension = contentDetector.mediaTypeToExtension(pageContent.mediaType) ?: "jpeg" + val imageFileName = "${book.name}-$pageNumber$extension" + contentDisposition = + ContentDisposition.builder("inline") + .filename(imageFileName, UTF_8) + .build() + }, + ) + .contentType(getMediaTypeOrDefault(pageContent.mediaType)) + .setNotModified(media) + .body(pageContent.bytes) + } catch (ex: IndexOutOfBoundsException) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist") + } catch (ex: ImageConversionException) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, ex.message) + } catch (ex: MediaNotReadyException) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed") + } catch (ex: NoSuchFileException) { + logger.warn(ex) { "File not found: $book" } + throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved") + } + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @GetMapping( value = ["api/v1/books/{bookId}/pages/{pageNumber}/raw"], @@ -588,32 +606,38 @@ class BookController( getBookPageRaw(book, media, pageNumber) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) - private fun getBookPageRaw(book: Book, media: Media, pageNumber: Int): ResponseEntity = try { - val pageContent = bookAnalyzer.getPageContentRaw(BookWithMedia(book, media), pageNumber) + private fun getBookPageRaw( + book: Book, + media: Media, + pageNumber: Int, + ): ResponseEntity = + try { + val pageContent = bookAnalyzer.getPageContentRaw(BookWithMedia(book, media), pageNumber) - ResponseEntity.ok() - .headers( - HttpHeaders().apply { - val extension = contentDetector.mediaTypeToExtension(pageContent.mediaType) ?: "" - val pageFileName = "${book.name}-$pageNumber$extension" - contentDisposition = ContentDisposition.builder("inline") - .filename(pageFileName, UTF_8) - .build() - }, - ) - .contentType(getMediaTypeOrDefault(pageContent.mediaType)) - .setNotModified(media) - .body(pageContent.bytes) - } catch (ex: IndexOutOfBoundsException) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist") - } catch (ex: MediaUnsupportedException) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message) - } catch (ex: MediaNotReadyException) { - throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed") - } catch (ex: NoSuchFileException) { - logger.warn(ex) { "File not found: $book" } - throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved") - } + ResponseEntity.ok() + .headers( + HttpHeaders().apply { + val extension = contentDetector.mediaTypeToExtension(pageContent.mediaType) ?: "" + val pageFileName = "${book.name}-$pageNumber$extension" + contentDisposition = + ContentDisposition.builder("inline") + .filename(pageFileName, UTF_8) + .build() + }, + ) + .contentType(getMediaTypeOrDefault(pageContent.mediaType)) + .setNotModified(media) + .body(pageContent.bytes) + } catch (ex: IndexOutOfBoundsException) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist") + } catch (ex: MediaUnsupportedException) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message) + } catch (ex: MediaNotReadyException) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed") + } catch (ex: NoSuchFileException) { + logger.warn(ex) { "File not found: $book" } + throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved") + } @ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))]) @GetMapping( @@ -702,18 +726,20 @@ class BookController( if (!isFont) principal!!.user.checkContentRestriction(book) val res = media.files.firstOrNull { it.fileName == resourceName } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) - val bytes = try { - bookAnalyzer.getFileContent(BookWithMedia(book, media), resourceName) - } catch (e: EntryNotFoundException) { - throw ResponseStatusException(HttpStatus.NOT_FOUND) - } + val bytes = + try { + bookAnalyzer.getFileContent(BookWithMedia(book, media), resourceName) + } catch (e: EntryNotFoundException) { + throw ResponseStatusException(HttpStatus.NOT_FOUND) + } return ResponseEntity.ok() .headers( HttpHeaders().apply { - contentDisposition = ContentDisposition.builder("inline") - .filename(FilenameUtils.getName(resourceName), UTF_8) - .build() + contentDisposition = + ContentDisposition.builder("inline") + .filename(FilenameUtils.getName(resourceName), UTF_8) + .build() }, ) .contentType(getMediaTypeOrDefault(res.mediaType)) @@ -742,8 +768,9 @@ class BookController( principal.user.checkContentRestriction(book) - val extension = mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + val extension = + mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) ResponseEntity.ok() .contentType(MEDIATYPE_POSITION_LIST_JSON) @@ -798,11 +825,12 @@ class BookController( bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let { bookDto -> if (bookDto.media.mediaProfile != MediaProfile.EPUB.name) throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Book media type '${bookDto.media.mediaType}' not compatible with requested profile") principal.user.checkContentRestriction(bookDto) - val manifest = webPubGenerator.toManifestEpub( - bookDto, - mediaRepository.findById(bookId), - seriesMetadataRepository.findById(bookDto.seriesId), - ) + val manifest = + webPubGenerator.toManifestEpub( + bookDto, + mediaRepository.findById(bookId), + seriesMetadataRepository.findById(bookDto.seriesId), + ) ResponseEntity.ok() .contentType(manifest.mediaType) .body(manifest) @@ -819,11 +847,12 @@ class BookController( bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let { bookDto -> if (bookDto.media.mediaProfile != MediaProfile.PDF.name) throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Book media type '${bookDto.media.mediaType}' not compatible with requested profile") principal.user.checkContentRestriction(bookDto) - val manifest = webPubGenerator.toManifestPdf( - bookDto, - mediaRepository.findById(bookDto.id), - seriesMetadataRepository.findById(bookDto.seriesId), - ) + val manifest = + webPubGenerator.toManifestPdf( + bookDto, + mediaRepository.findById(bookDto.id), + seriesMetadataRepository.findById(bookDto.seriesId), + ) ResponseEntity.ok() .contentType(manifest.mediaType) .body(manifest) @@ -839,11 +868,12 @@ class BookController( ): ResponseEntity = bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let { bookDto -> principal.user.checkContentRestriction(bookDto) - val manifest = webPubGenerator.toManifestDivina( - bookDto, - mediaRepository.findById(bookDto.id), - seriesMetadataRepository.findById(bookDto.seriesId), - ) + val manifest = + webPubGenerator.toManifestDivina( + bookDto, + mediaRepository.findById(bookDto.id), + seriesMetadataRepository.findById(bookDto.seriesId), + ) ResponseEntity.ok() .contentType(manifest.mediaType) .body(manifest) @@ -852,7 +882,9 @@ class BookController( @PostMapping("api/v1/books/{bookId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable bookId: String) { + fun analyze( + @PathVariable bookId: String, + ) { bookRepository.findByIdOrNull(bookId)?.let { book -> taskEmitter.analyzeBook(book, HIGH_PRIORITY) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -861,7 +893,9 @@ class BookController( @PostMapping("api/v1/books/{bookId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable bookId: String) { + fun refreshMetadata( + @PathVariable bookId: String, + ) { bookRepository.findByIdOrNull(bookId)?.let { book -> taskEmitter.refreshBookMetadata(book, priority = HIGH_PRIORITY) taskEmitter.refreshBookLocalArtwork(book, priority = HIGH_PRIORITY) @@ -897,14 +931,15 @@ class BookController( @RequestBody newMetadatas: Map, ) { - val updatedBooks = newMetadatas.mapNotNull { (bookId, newMetadata) -> - bookMetadataRepository.findByIdOrNull(bookId)?.let { existing -> - val updated = existing.patch(newMetadata) - bookMetadataRepository.update(updated) + val updatedBooks = + newMetadatas.mapNotNull { (bookId, newMetadata) -> + bookMetadataRepository.findByIdOrNull(bookId)?.let { existing -> + val updated = existing.patch(newMetadata) + bookMetadataRepository.update(updated) - bookRepository.findByIdOrNull(bookId) + bookRepository.findByIdOrNull(bookId) + } } - } updatedBooks.forEach { eventPublisher.publishEvent(DomainEvent.BookUpdated(it)) } updatedBooks.map { it.seriesId }.distinct().forEach { taskEmitter.aggregateSeriesMetadata(it) } @@ -1006,9 +1041,10 @@ class BookController( */ private fun KomgaUser.checkContentRestriction(book: BookDto) { if (!canAccessLibrary(book.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - if (restrictions.isRestricted) seriesMetadataRepository.findById(book.seriesId).let { - if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - } + if (restrictions.isRestricted) + seriesMetadataRepository.findById(book.seriesId).let { + if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) + } } /** @@ -1019,8 +1055,9 @@ class BookController( */ private fun KomgaUser.checkContentRestriction(book: Book) { if (!canAccessLibrary(book.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - if (restrictions.isRestricted) seriesMetadataRepository.findById(book.seriesId).let { - if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - } + if (restrictions.isRestricted) + seriesMetadataRepository.findById(book.seriesId).let { + if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) + } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ClaimController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ClaimController.kt index ac6cfe5e0..49913624f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ClaimController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ClaimController.kt @@ -22,7 +22,6 @@ import org.springframework.web.server.ResponseStatusException class ClaimController( private val userDetailsLifecycle: KomgaUserLifecycle, ) { - @GetMapping fun getClaimStatus() = ClaimStatus(userDetailsLifecycle.countUsers() > 0) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemController.kt index f7c456728..7ee682954 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemController.kt @@ -18,7 +18,6 @@ import kotlin.streams.asSequence @RequestMapping("api/v1/filesystem", produces = [MediaType.APPLICATION_JSON_VALUE]) @PreAuthorize("hasRole('ADMIN')") class FileSystemController { - private val fs = FileSystems.getDefault() @PostMapping @@ -35,14 +34,15 @@ class FileSystemController { try { DirectoryListingDto( parent = (p.parent ?: "").toString(), - directories = Files.list(p).use { dirStream -> - dirStream.asSequence() - .filter { Files.isDirectory(it) } - .filter { !Files.isHidden(it) } - .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.toString() }) - .map { it.toDto() } - .toList() - }, + directories = + Files.list(p).use { dirStream -> + dirStream.asSequence() + .filter { Files.isDirectory(it) } + .filter { !Files.isHidden(it) } + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.toString() }) + .map { it.toDto() } + .toList() + }, ) } catch (e: Exception) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Path does not exist") diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/HistoricalEventController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/HistoricalEventController.kt index 07cf4b4df..6f777edcb 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/HistoricalEventController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/HistoricalEventController.kt @@ -18,21 +18,23 @@ import org.springframework.web.bind.annotation.RestController class HistoricalEventController( private val historicalEventDtoRepository: HistoricalEventDtoRepository, ) { - @GetMapping @PageableAsQueryParam fun getAll( @Parameter(hidden = true) page: Pageable, ): Page { val sort = - if (page.sort.isSorted) page.sort - else Sort.by(Sort.Order.desc("timestamp")) + if (page.sort.isSorted) + page.sort + else + Sort.by(Sort.Order.desc("timestamp")) - val pageRequest = PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + val pageRequest = + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return historicalEventDtoRepository.findAll(pageRequest) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt index 7167ae844..6fe1790f1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LibraryController.kt @@ -49,7 +49,6 @@ class LibraryController( private val bookRepository: BookRepository, private val seriesRepository: SeriesRepository, ) { - @GetMapping fun getAll( @AuthenticationPrincipal principal: KomgaPrincipal, @@ -145,38 +144,39 @@ class LibraryController( library: LibraryUpdateDto, ) { libraryRepository.findByIdOrNull(libraryId)?.let { existing -> - val toUpdate = with(library) { - existing.copy( - id = libraryId, - name = name ?: existing.name, - root = root?.let { filePathToUrl(root!!) } ?: existing.root, - importComicInfoBook = importComicInfoBook ?: existing.importComicInfoBook, - importComicInfoSeries = importComicInfoSeries ?: existing.importComicInfoSeries, - importComicInfoCollection = importComicInfoCollection ?: existing.importComicInfoCollection, - importComicInfoReadList = importComicInfoReadList ?: existing.importComicInfoReadList, - importComicInfoSeriesAppendVolume = importComicInfoSeriesAppendVolume ?: existing.importComicInfoSeriesAppendVolume, - importEpubBook = importEpubBook ?: existing.importEpubBook, - importEpubSeries = importEpubSeries ?: existing.importEpubSeries, - importMylarSeries = importMylarSeries ?: existing.importMylarSeries, - importLocalArtwork = importLocalArtwork ?: existing.importLocalArtwork, - importBarcodeIsbn = importBarcodeIsbn ?: existing.importBarcodeIsbn, - scanForceModifiedTime = scanForceModifiedTime ?: existing.scanForceModifiedTime, - scanInterval = scanInterval?.toDomain() ?: existing.scanInterval, - scanOnStartup = scanOnStartup ?: existing.scanOnStartup, - scanCbx = scanCbx ?: existing.scanCbx, - scanPdf = scanPdf ?: existing.scanPdf, - scanEpub = scanEpub ?: existing.scanEpub, - scanDirectoryExclusions = if (isSet("scanDirectoryExclusions")) scanDirectoryExclusions ?: emptySet() else existing.scanDirectoryExclusions, - repairExtensions = repairExtensions ?: existing.repairExtensions, - convertToCbz = convertToCbz ?: existing.convertToCbz, - emptyTrashAfterScan = emptyTrashAfterScan ?: existing.emptyTrashAfterScan, - seriesCover = seriesCover?.toDomain() ?: existing.seriesCover, - hashFiles = hashFiles ?: existing.hashFiles, - hashPages = hashPages ?: existing.hashPages, - analyzeDimensions = analyzeDimensions ?: existing.analyzeDimensions, - oneshotsDirectory = if (isSet("oneshotsDirectory")) oneshotsDirectory?.ifBlank { null } else existing.oneshotsDirectory, - ) - } + val toUpdate = + with(library) { + existing.copy( + id = libraryId, + name = name ?: existing.name, + root = root?.let { filePathToUrl(root!!) } ?: existing.root, + importComicInfoBook = importComicInfoBook ?: existing.importComicInfoBook, + importComicInfoSeries = importComicInfoSeries ?: existing.importComicInfoSeries, + importComicInfoCollection = importComicInfoCollection ?: existing.importComicInfoCollection, + importComicInfoReadList = importComicInfoReadList ?: existing.importComicInfoReadList, + importComicInfoSeriesAppendVolume = importComicInfoSeriesAppendVolume ?: existing.importComicInfoSeriesAppendVolume, + importEpubBook = importEpubBook ?: existing.importEpubBook, + importEpubSeries = importEpubSeries ?: existing.importEpubSeries, + importMylarSeries = importMylarSeries ?: existing.importMylarSeries, + importLocalArtwork = importLocalArtwork ?: existing.importLocalArtwork, + importBarcodeIsbn = importBarcodeIsbn ?: existing.importBarcodeIsbn, + scanForceModifiedTime = scanForceModifiedTime ?: existing.scanForceModifiedTime, + scanInterval = scanInterval?.toDomain() ?: existing.scanInterval, + scanOnStartup = scanOnStartup ?: existing.scanOnStartup, + scanCbx = scanCbx ?: existing.scanCbx, + scanPdf = scanPdf ?: existing.scanPdf, + scanEpub = scanEpub ?: existing.scanEpub, + scanDirectoryExclusions = if (isSet("scanDirectoryExclusions")) scanDirectoryExclusions ?: emptySet() else existing.scanDirectoryExclusions, + repairExtensions = repairExtensions ?: existing.repairExtensions, + convertToCbz = convertToCbz ?: existing.convertToCbz, + emptyTrashAfterScan = emptyTrashAfterScan ?: existing.emptyTrashAfterScan, + seriesCover = seriesCover?.toDomain() ?: existing.seriesCover, + hashFiles = hashFiles ?: existing.hashFiles, + hashPages = hashPages ?: existing.hashPages, + analyzeDimensions = analyzeDimensions ?: existing.analyzeDimensions, + oneshotsDirectory = if (isSet("oneshotsDirectory")) oneshotsDirectory?.ifBlank { null } else existing.oneshotsDirectory, + ) + } try { libraryLifecycle.updateLibrary(toUpdate) } catch (e: Exception) { @@ -197,7 +197,9 @@ class LibraryController( @DeleteMapping("/{libraryId}") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) - fun deleteOne(@PathVariable libraryId: String) { + fun deleteOne( + @PathVariable libraryId: String, + ) { libraryRepository.findByIdOrNull(libraryId)?.let { libraryLifecycle.deleteLibrary(it) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @@ -218,14 +220,18 @@ class LibraryController( @PostMapping("{libraryId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable libraryId: String) { + fun analyze( + @PathVariable libraryId: String, + ) { taskEmitter.analyzeBook(bookRepository.findAll(BookSearch(libraryIds = listOf(libraryId))), HIGH_PRIORITY) } @PostMapping("{libraryId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable libraryId: String) { + fun refreshMetadata( + @PathVariable libraryId: String, + ) { val books = bookRepository.findAll(BookSearch(libraryIds = listOf(libraryId))) taskEmitter.refreshBookMetadata(books, priority = HIGH_PRIORITY) taskEmitter.refreshBookLocalArtwork(books, priority = HIGH_PRIORITY) @@ -235,7 +241,9 @@ class LibraryController( @PostMapping("{libraryId}/empty-trash") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun emptyTrash(@PathVariable libraryId: String) { + fun emptyTrash( + @PathVariable libraryId: String, + ) { libraryRepository.findByIdOrNull(libraryId)?.let { library -> taskEmitter.emptyTrash(library.id, HIGH_PRIORITY) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LoginController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LoginController.kt index ddce5d073..f6b707cd9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LoginController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/LoginController.kt @@ -16,10 +16,13 @@ import org.springframework.web.bind.annotation.RestController class LoginController( private val cookieSerializer: CookieSerializer, ) { - @GetMapping("api/v1/login/set-cookie") @ResponseStatus(HttpStatus.NO_CONTENT) - fun headerToCookie(request: HttpServletRequest, response: HttpServletResponse, session: HttpSession) { + fun headerToCookie( + request: HttpServletRequest, + response: HttpServletResponse, + session: HttpSession, + ) { cookieSerializer.writeCookieValue(CookieSerializer.CookieValue(request, response, session.id)) } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2Controller.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2Controller.kt index 1494de631..2ec1eaf3e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2Controller.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2Controller.kt @@ -10,10 +10,10 @@ import org.springframework.web.bind.annotation.RestController class OAuth2Controller( clientRegistrationRepository: InMemoryClientRegistrationRepository?, ) { - - val registrationIds = clientRegistrationRepository?.map { - OAuth2ClientDto(it.clientName, it.registrationId) - } ?: emptyList() + val registrationIds = + clientRegistrationRepository?.map { + OAuth2ClientDto(it.clientName, it.registrationId) + } ?: emptyList() @RequestMapping("providers") fun getProviders() = registrationIds diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/PageHashController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/PageHashController.kt index e42b86813..ad1ca57e6 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/PageHashController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/PageHashController.kt @@ -43,7 +43,6 @@ class PageHashController( private val pageHashLifecycle: PageHashLifecycle, private val taskEmitter: TaskEmitter, ) { - @GetMapping @PageableAsQueryParam fun getKnownPageHashes( @@ -114,19 +113,20 @@ class PageHashController( fun performDelete( @PathVariable pageHash: String, ) { - val toRemove = pageHashRepository.findMatchesByHash(pageHash, Pageable.unpaged()) - .groupBy( - { it.bookId }, - { - BookPageNumbered( - fileName = it.fileName, - mediaType = it.mediaType, - fileHash = pageHash, - fileSize = it.fileSize, - pageNumber = it.pageNumber, - ) - }, - ) + val toRemove = + pageHashRepository.findMatchesByHash(pageHash, Pageable.unpaged()) + .groupBy( + { it.bookId }, + { + BookPageNumbered( + fileName = it.fileName, + mediaType = it.mediaType, + fileHash = pageHash, + fileSize = it.fileSize, + pageNumber = it.pageNumber, + ) + }, + ) taskEmitter.removeDuplicatePages(toRemove) } @@ -137,18 +137,19 @@ class PageHashController( @PathVariable pageHash: String, @RequestBody matchDto: PageHashMatchDto, ) { - val toRemove = Pair( - matchDto.bookId, - listOf( - BookPageNumbered( - fileName = matchDto.fileName, - mediaType = matchDto.mediaType, - fileHash = pageHash, - fileSize = matchDto.fileSize, - pageNumber = matchDto.pageNumber, + val toRemove = + Pair( + matchDto.bookId, + listOf( + BookPageNumbered( + fileName = matchDto.fileName, + mediaType = matchDto.mediaType, + fileHash = pageHash, + fileSize = matchDto.fileSize, + pageNumber = matchDto.pageNumber, + ), ), - ), - ) + ) taskEmitter.removeDuplicatePages(toRemove.first, toRemove.second) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReadListController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReadListController.kt index f7ddadf1e..5d0a8dcaf 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReadListController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReadListController.kt @@ -97,7 +97,6 @@ class ReadListController( private val bookLifecycle: BookLifecycle, private val eventPublisher: ApplicationEventPublisher, ) { - @PageableWithoutSortAsQueryParam @GetMapping fun getAll( @@ -107,19 +106,22 @@ class ReadListController( @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable, ): Page { - val sort = when { - page.sort.isSorted -> page.sort - !searchTerm.isNullOrBlank() -> Sort.by("relevance") - else -> Sort.by(Sort.Order.asc("name")) - } + val sort = + when { + page.sort.isSorted -> page.sort + !searchTerm.isNullOrBlank() -> Sort.by("relevance") + else -> Sort.by(Sort.Order.asc("name")) + } val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return readListRepository.findAll(principal.user.getAuthorizedLibraryIds(libraryIds), principal.user.getAuthorizedLibraryIds(null), searchTerm, pageRequest, principal.user.restrictions) .map { it.toDto() } @@ -268,12 +270,13 @@ class ReadListController( readList: ReadListUpdateDto, ) { readListRepository.findByIdOrNull(id)?.let { existing -> - val updated = existing.copy( - name = readList.name ?: existing.name, - summary = readList.summary ?: existing.summary, - ordered = readList.ordered ?: existing.ordered, - bookIds = readList.bookIds?.toIndexedMap() ?: existing.bookIds, - ) + val updated = + existing.copy( + name = readList.name ?: existing.name, + summary = readList.summary ?: existing.summary, + ordered = readList.ordered ?: existing.ordered, + bookIds = readList.bookIds?.toIndexedMap() ?: existing.bookIds, + ) try { readListLifecycle.updateReadList(updated) } catch (e: DuplicateNameException) { @@ -310,25 +313,30 @@ class ReadListController( ): Page = readListRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { readList -> val sort = - if (readList.ordered) Sort.by(Sort.Order.asc("readList.number")) - else Sort.by(Sort.Order.asc("metadata.releaseDate")) + if (readList.ordered) + Sort.by(Sort.Order.asc("readList.number")) + else + Sort.by(Sort.Order.asc("metadata.releaseDate")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) - val bookSearch = BookSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), - readStatus = readStatus, - mediaStatus = mediaStatus, - deleted = deleted, - tags = tags, - authors = authors, - ) + val bookSearch = + BookSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), + readStatus = readStatus, + mediaStatus = mediaStatus, + deleted = deleted, + tags = tags, + authors = authors, + ) bookDtoRepository.findAllByReadListId( readList.id, @@ -416,38 +424,41 @@ class ReadListController( ): ResponseEntity { readListRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { readList -> - val books = readList.bookIds - .mapNotNull { bookRepository.findByIdOrNull(it.value)?.let { book -> it.key to book } } - .toMap() + val books = + readList.bookIds + .mapNotNull { bookRepository.findByIdOrNull(it.value)?.let { book -> it.key to book } } + .toMap() - val streamingResponse = StreamingResponseBody { responseStream: OutputStream -> - ZipArchiveOutputStream(responseStream).use { zipStream -> - zipStream.setMethod(ZipArchiveOutputStream.DEFLATED) - zipStream.setLevel(Deflater.NO_COMPRESSION) - zipStream.setUseZip64(Zip64Mode.Always) - books.forEach { (index, book) -> - val file = FileSystemResource(book.path) - if (!file.exists()) { - logger.warn { "Book file not found, skipping archive entry: ${file.path}" } - return@forEach - } + val streamingResponse = + StreamingResponseBody { responseStream: OutputStream -> + ZipArchiveOutputStream(responseStream).use { zipStream -> + zipStream.setMethod(ZipArchiveOutputStream.DEFLATED) + zipStream.setLevel(Deflater.NO_COMPRESSION) + zipStream.setUseZip64(Zip64Mode.Always) + books.forEach { (index, book) -> + val file = FileSystemResource(book.path) + if (!file.exists()) { + logger.warn { "Book file not found, skipping archive entry: ${file.path}" } + return@forEach + } - logger.debug { "Adding file to zip archive: ${file.path}" } - file.inputStream.use { - zipStream.putArchiveEntry(ZipArchiveEntry("${index + 1} - ${file.filename}")) - IOUtils.copyLarge(it, zipStream, ByteArray(8192)) - zipStream.closeArchiveEntry() + logger.debug { "Adding file to zip archive: ${file.path}" } + file.inputStream.use { + zipStream.putArchiveEntry(ZipArchiveEntry("${index + 1} - ${file.filename}")) + IOUtils.copyLarge(it, zipStream, ByteArray(8192)) + zipStream.closeArchiveEntry() + } } } } - } return ResponseEntity.ok() .headers( HttpHeaders().apply { - contentDisposition = ContentDisposition.builder("attachment") - .filename(readList.name + ".zip", UTF_8) - .build() + contentDisposition = + ContentDisposition.builder("attachment") + .filename(readList.name + ".zip", UTF_8) + .build() }, ) .contentType(MediaType.parseMediaType(ZIP.type)) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReferentialController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReferentialController.kt index f02dde79d..f74a2bca5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReferentialController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/ReferentialController.kt @@ -21,7 +21,6 @@ import org.springframework.web.bind.annotation.RestController class ReferentialController( private val referentialRepository: ReferentialRepository, ) { - @GetMapping("v1/authors") fun getAuthorsV1( @AuthenticationPrincipal principal: KomgaPrincipal, @@ -52,11 +51,13 @@ class ReferentialController( @Parameter(hidden = true) page: Pageable, ): Page { val pageRequest = - if (unpaged) Pageable.unpaged() - else PageRequest.of( - page.pageNumber, - page.pageSize, - ) + if (unpaged) + Pageable.unpaged() + else + PageRequest.of( + page.pageNumber, + page.pageSize, + ) return when { libraryId != null -> referentialRepository.findAllAuthorsByNameAndLibrary(search, role, libraryId, principal.user.getAuthorizedLibraryIds(null), pageRequest) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionController.kt index d339ca673..73ae69075 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionController.kt @@ -73,7 +73,6 @@ class SeriesCollectionController( private val thumbnailSeriesCollectionRepository: ThumbnailSeriesCollectionRepository, private val eventPublisher: ApplicationEventPublisher, ) { - @PageableWithoutSortAsQueryParam @GetMapping fun getAll( @@ -83,18 +82,21 @@ class SeriesCollectionController( @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable, ): Page { - val sort = when { - !searchTerm.isNullOrBlank() -> Sort.by("relevance") - else -> Sort.by(Sort.Order.asc("name")) - } + val sort = + when { + !searchTerm.isNullOrBlank() -> Sort.by("relevance") + else -> Sort.by(Sort.Order.asc("name")) + } val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return collectionRepository.findAll(principal.user.getAuthorizedLibraryIds(libraryIds), principal.user.getAuthorizedLibraryIds(null), searchTerm, pageRequest, principal.user.restrictions) .map { it.toDto() } @@ -231,11 +233,12 @@ class SeriesCollectionController( collection: CollectionUpdateDto, ) { collectionRepository.findByIdOrNull(id)?.let { existing -> - val updated = existing.copy( - name = collection.name ?: existing.name, - ordered = collection.ordered ?: existing.ordered, - seriesIds = collection.seriesIds ?: existing.seriesIds, - ) + val updated = + existing.copy( + name = collection.name ?: existing.name, + ordered = collection.ordered ?: existing.ordered, + seriesIds = collection.seriesIds ?: existing.seriesIds, + ) try { collectionLifecycle.updateCollection(updated) } catch (e: DuplicateNameException) { @@ -278,31 +281,36 @@ class SeriesCollectionController( ): Page = collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null), principal.user.restrictions)?.let { collection -> val sort = - if (collection.ordered) Sort.by(Sort.Order.asc("collection.number")) - else Sort.by(Sort.Order.asc("metadata.titleSort")) + if (collection.ordered) + Sort.by(Sort.Order.asc("collection.number")) + else + Sort.by(Sort.Order.asc("metadata.titleSort")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), - metadataStatus = metadataStatus, - publishers = publishers, - deleted = deleted, - complete = complete, - languages = languages, - genres = genres, - tags = tags, - ageRatings = ageRatings?.map { it.toIntOrNull() }, - releaseYears = releaseYears, - readStatus = readStatus, - authors = authors, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), + metadataStatus = metadataStatus, + publishers = publishers, + deleted = deleted, + complete = complete, + languages = languages, + genres = genres, + tags = tags, + ageRatings = ageRatings?.map { it.toIntOrNull() }, + releaseYears = releaseYears, + readStatus = readStatus, + authors = authors, + ) seriesDtoRepository.findAllByCollectionId(collection.id, seriesSearch, principal.user.id, pageRequest, principal.user.restrictions) .map { it.restrictUrl(!principal.user.roleAdmin) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt index d5a17dba9..8507121c5 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt @@ -115,7 +115,6 @@ class SeriesController( private val imageAnalyzer: ImageAnalyzer, private val thumbnailsSeriesRepository: ThumbnailSeriesRepository, ) { - @PageableAsQueryParam @AuthorsAsQueryParam @Parameters( @@ -159,38 +158,42 @@ class SeriesController( } val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), - collectionIds = collectionIds, - searchTerm = searchTerm, - searchRegex = searchRegex?.let { - when (it.second.lowercase()) { - "title" -> Pair(it.first, SeriesSearch.SearchField.TITLE) - "title_sort" -> Pair(it.first, SeriesSearch.SearchField.TITLE_SORT) - else -> null - } - }, - metadataStatus = metadataStatus, - publishers = publishers, - deleted = deleted, - complete = complete, - oneshot = oneshot, - languages = languages, - genres = genres, - tags = tags, - ageRatings = ageRatings?.map { it.toIntOrNull() }, - releaseYears = releaseYears, - readStatus = readStatus, - authors = authors, - sharingLabels = sharingLabels, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), + collectionIds = collectionIds, + searchTerm = searchTerm, + searchRegex = + searchRegex?.let { + when (it.second.lowercase()) { + "title" -> Pair(it.first, SeriesSearch.SearchField.TITLE) + "title_sort" -> Pair(it.first, SeriesSearch.SearchField.TITLE_SORT) + else -> null + } + }, + metadataStatus = metadataStatus, + publishers = publishers, + deleted = deleted, + complete = complete, + oneshot = oneshot, + languages = languages, + genres = genres, + tags = tags, + ageRatings = ageRatings?.map { it.toIntOrNull() }, + releaseYears = releaseYears, + readStatus = readStatus, + authors = authors, + sharingLabels = sharingLabels, + ) return seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageRequest, principal.user.restrictions) .map { it.restrictUrl(!principal.user.roleAdmin) } @@ -229,31 +232,33 @@ class SeriesController( @Parameter(hidden = true) @Authors authors: List?, @Parameter(hidden = true) page: Pageable, ): List { - val seriesSearch = SeriesSearchWithReadProgress( - libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), - collectionIds = collectionIds, - searchTerm = searchTerm, - searchRegex = searchRegex?.let { - when (it.second.lowercase()) { - "title" -> Pair(it.first, SeriesSearch.SearchField.TITLE) - "title_sort" -> Pair(it.first, SeriesSearch.SearchField.TITLE_SORT) - else -> null - } - }, - metadataStatus = metadataStatus, - publishers = publishers, - deleted = deleted, - complete = complete, - oneshot = oneshot, - languages = languages, - genres = genres, - tags = tags, - ageRatings = ageRatings?.map { it.toIntOrNull() }, - releaseYears = releaseYears, - readStatus = readStatus, - authors = authors, - sharingLabels = sharingLabels, - ) + val seriesSearch = + SeriesSearchWithReadProgress( + libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), + collectionIds = collectionIds, + searchTerm = searchTerm, + searchRegex = + searchRegex?.let { + when (it.second.lowercase()) { + "title" -> Pair(it.first, SeriesSearch.SearchField.TITLE) + "title_sort" -> Pair(it.first, SeriesSearch.SearchField.TITLE_SORT) + else -> null + } + }, + metadataStatus = metadataStatus, + publishers = publishers, + deleted = deleted, + complete = complete, + oneshot = oneshot, + languages = languages, + genres = genres, + tags = tags, + ageRatings = ageRatings?.map { it.toIntOrNull() }, + releaseYears = releaseYears, + readStatus = readStatus, + authors = authors, + sharingLabels = sharingLabels, + ) return seriesDtoRepository.countByFirstCharacter(seriesSearch, principal.user.id, principal.user.restrictions) } @@ -272,12 +277,14 @@ class SeriesController( val sort = Sort.by(Sort.Order.desc("lastModified")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return seriesDtoRepository.findAll( SeriesSearchWithReadProgress( @@ -305,12 +312,14 @@ class SeriesController( val sort = Sort.by(Sort.Order.desc("created")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return seriesDtoRepository.findAll( SeriesSearchWithReadProgress( @@ -338,12 +347,14 @@ class SeriesController( val sort = Sort.by(Sort.Order.desc("lastModified")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return seriesDtoRepository.findAllRecentlyUpdated( SeriesSearchWithReadProgress( @@ -478,16 +489,20 @@ class SeriesController( principal.user.checkContentRestriction(seriesId) val sort = - if (page.sort.isSorted) page.sort - else Sort.by(Sort.Order.asc("metadata.numberSort")) + if (page.sort.isSorted) + page.sort + else + Sort.by(Sort.Order.asc("metadata.numberSort")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return bookDtoRepository.findAll( BookSearchWithReadProgress( @@ -517,14 +532,18 @@ class SeriesController( @PostMapping("v1/series/{seriesId}/analyze") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun analyze(@PathVariable seriesId: String) { + fun analyze( + @PathVariable seriesId: String, + ) { taskEmitter.analyzeBook(bookRepository.findAllBySeriesId(seriesId), HIGH_PRIORITY) } @PostMapping("v1/series/{seriesId}/metadata/refresh") @PreAuthorize("hasRole('$ROLE_ADMIN')") @ResponseStatus(HttpStatus.ACCEPTED) - fun refreshMetadata(@PathVariable seriesId: String) { + fun refreshMetadata( + @PathVariable seriesId: String, + ) { val books = bookRepository.findAllBySeriesId(seriesId) taskEmitter.refreshBookMetadata(books, priority = HIGH_PRIORITY) taskEmitter.refreshBookLocalArtwork(books, priority = HIGH_PRIORITY) @@ -543,48 +562,64 @@ class SeriesController( @AuthenticationPrincipal principal: KomgaPrincipal, ) = seriesMetadataRepository.findByIdOrNull(seriesId)?.let { existing -> - val updated = with(newMetadata) { - existing.copy( - status = status ?: existing.status, - statusLock = statusLock ?: existing.statusLock, - title = title ?: existing.title, - titleLock = titleLock ?: existing.titleLock, - titleSort = titleSort ?: existing.titleSort, - titleSortLock = titleSortLock ?: existing.titleSortLock, - summary = summary ?: existing.summary, - summaryLock = summaryLock ?: existing.summaryLock, - language = language ?: existing.language, - languageLock = languageLock ?: existing.languageLock, - readingDirection = if (isSet("readingDirection")) readingDirection else existing.readingDirection, - readingDirectionLock = readingDirectionLock ?: existing.readingDirectionLock, - publisher = publisher ?: existing.publisher, - publisherLock = publisherLock ?: existing.publisherLock, - ageRating = if (isSet("ageRating")) ageRating else existing.ageRating, - ageRatingLock = ageRatingLock ?: existing.ageRatingLock, - genres = if (isSet("genres")) { - if (genres != null) genres!! else emptySet() - } else existing.genres, - genresLock = genresLock ?: existing.genresLock, - tags = if (isSet("tags")) { - if (tags != null) tags!! else emptySet() - } else existing.tags, - tagsLock = tagsLock ?: existing.tagsLock, - totalBookCount = if (isSet("totalBookCount")) totalBookCount else existing.totalBookCount, - totalBookCountLock = totalBookCountLock ?: existing.totalBookCountLock, - sharingLabels = if (isSet("sharingLabels")) { - if (sharingLabels != null) sharingLabels!! else emptySet() - } else existing.sharingLabels, - sharingLabelsLock = sharingLabelsLock ?: existing.sharingLabelsLock, - links = if (isSet("links")) { - if (links != null) links!!.map { WebLink(it.label!!, URI(it.url!!)) } else emptyList() - } else existing.links, - linksLock = linksLock ?: existing.linksLock, - alternateTitles = if (isSet("alternateTitles")) { - if (alternateTitles != null) alternateTitles!!.map { AlternateTitle(it.label!!, it.title!!) } else emptyList() - } else existing.alternateTitles, - alternateTitlesLock = alternateTitlesLock ?: existing.alternateTitlesLock, - ) - } + val updated = + with(newMetadata) { + existing.copy( + status = status ?: existing.status, + statusLock = statusLock ?: existing.statusLock, + title = title ?: existing.title, + titleLock = titleLock ?: existing.titleLock, + titleSort = titleSort ?: existing.titleSort, + titleSortLock = titleSortLock ?: existing.titleSortLock, + summary = summary ?: existing.summary, + summaryLock = summaryLock ?: existing.summaryLock, + language = language ?: existing.language, + languageLock = languageLock ?: existing.languageLock, + readingDirection = if (isSet("readingDirection")) readingDirection else existing.readingDirection, + readingDirectionLock = readingDirectionLock ?: existing.readingDirectionLock, + publisher = publisher ?: existing.publisher, + publisherLock = publisherLock ?: existing.publisherLock, + ageRating = if (isSet("ageRating")) ageRating else existing.ageRating, + ageRatingLock = ageRatingLock ?: existing.ageRatingLock, + genres = + if (isSet("genres")) { + if (genres != null) genres!! else emptySet() + } else { + existing.genres + }, + genresLock = genresLock ?: existing.genresLock, + tags = + if (isSet("tags")) { + if (tags != null) tags!! else emptySet() + } else { + existing.tags + }, + tagsLock = tagsLock ?: existing.tagsLock, + totalBookCount = if (isSet("totalBookCount")) totalBookCount else existing.totalBookCount, + totalBookCountLock = totalBookCountLock ?: existing.totalBookCountLock, + sharingLabels = + if (isSet("sharingLabels")) { + if (sharingLabels != null) sharingLabels!! else emptySet() + } else { + existing.sharingLabels + }, + sharingLabelsLock = sharingLabelsLock ?: existing.sharingLabelsLock, + links = + if (isSet("links")) { + if (links != null) links!!.map { WebLink(it.label!!, URI(it.url!!)) } else emptyList() + } else { + existing.links + }, + linksLock = linksLock ?: existing.linksLock, + alternateTitles = + if (isSet("alternateTitles")) { + if (alternateTitles != null) alternateTitles!!.map { AlternateTitle(it.label!!, it.title!!) } else emptyList() + } else { + existing.alternateTitles + }, + alternateTitlesLock = alternateTitlesLock ?: existing.alternateTitlesLock, + ) + } seriesMetadataRepository.update(updated) seriesRepository.findByIdOrNull(seriesId)?.let { eventPublisher.publishEvent(DomainEvent.SeriesUpdated(it)) } @@ -654,34 +689,36 @@ class SeriesController( val books = bookRepository.findAllBySeriesId(seriesId) - val streamingResponse = StreamingResponseBody { responseStream: OutputStream -> - ZipArchiveOutputStream(responseStream).use { zipStream -> - zipStream.setMethod(ZipArchiveOutputStream.DEFLATED) - zipStream.setLevel(Deflater.NO_COMPRESSION) - zipStream.setUseZip64(Zip64Mode.Always) - books.forEach { book -> - val file = FileSystemResource(book.path) - if (!file.exists()) { - logger.warn { "Book file not found, skipping archive entry: ${file.path}" } - return@forEach - } + val streamingResponse = + StreamingResponseBody { responseStream: OutputStream -> + ZipArchiveOutputStream(responseStream).use { zipStream -> + zipStream.setMethod(ZipArchiveOutputStream.DEFLATED) + zipStream.setLevel(Deflater.NO_COMPRESSION) + zipStream.setUseZip64(Zip64Mode.Always) + books.forEach { book -> + val file = FileSystemResource(book.path) + if (!file.exists()) { + logger.warn { "Book file not found, skipping archive entry: ${file.path}" } + return@forEach + } - logger.debug { "Adding file to zip archive: ${file.path}" } - file.inputStream.use { - zipStream.putArchiveEntry(ZipArchiveEntry(file.filename)) - IOUtils.copyLarge(it, zipStream, ByteArray(8192)) - zipStream.closeArchiveEntry() + logger.debug { "Adding file to zip archive: ${file.path}" } + file.inputStream.use { + zipStream.putArchiveEntry(ZipArchiveEntry(file.filename)) + IOUtils.copyLarge(it, zipStream, ByteArray(8192)) + zipStream.closeArchiveEntry() + } } } } - } return ResponseEntity.ok() .headers( HttpHeaders().apply { - contentDisposition = ContentDisposition.builder("attachment") - .filename(seriesMetadataRepository.findById(seriesId).title + ".zip", UTF_8) - .build() + contentDisposition = + ContentDisposition.builder("attachment") + .filename(seriesMetadataRepository.findById(seriesId).title + ".zip", UTF_8) + .build() }, ) .contentType(MediaType.parseMediaType(ZIP.type)) @@ -712,8 +749,9 @@ class SeriesController( if (!canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } - if (restrictions.isRestricted) seriesMetadataRepository.findById(seriesId).let { - if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) - } + if (restrictions.isRestricted) + seriesMetadataRepository.findById(seriesId).let { + if (!isContentAllowed(it.ageRating, it.sharingLabels)) throw ResponseStatusException(HttpStatus.FORBIDDEN) + } } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt index baad8847c..a7f35984e 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SettingsController.kt @@ -32,7 +32,6 @@ class SettingsController( serverProperties: ServerProperties, servletContext: ServletContext, ) { - private val effectiveServerPort = serverProperties.port private val effectiveServerContextPath = servletContext.contextPath diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TaskController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TaskController.kt index cd4850e9f..25747515d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TaskController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TaskController.kt @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RestController class TaskController( private val tasksRepository: TasksRepository, ) { - @DeleteMapping("api/v1/tasks") @ResponseStatus(HttpStatus.OK) @PreAuthorize("hasRole('$ROLE_ADMIN')") diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt index 1ba9598dc..402efa016 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/TransientBooksController.kt @@ -37,7 +37,6 @@ class TransientBooksController( private val transientBookRepository: TransientBookRepository, private val bookAnalyzer: BookAnalyzer, ) { - @PostMapping fun scanForTransientBooks( @RequestBody request: ScanRequestDto, @@ -53,9 +52,10 @@ class TransientBooksController( @PostMapping("{id}/analyze") fun analyze( @PathVariable id: String, - ): TransientBookDto = transientBookRepository.findByIdOrNull(id)?.let { - transientBookLifecycle.analyzeAndPersist(it).toDto() - } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + ): TransientBookDto = + transientBookRepository.findByIdOrNull(id)?.let { + transientBookLifecycle.analyzeAndPersist(it).toDto() + } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) @GetMapping( value = ["{id}/pages/{pageNumber}"], @@ -81,6 +81,7 @@ class TransientBooksController( throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved") } } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) + private fun TransientBook.toDto(): TransientBookDto { val pages = if (media.profile == MediaProfile.PDF) bookAnalyzer.getPdfPagesDynamic(media) else media.pages return TransientBookDto( @@ -91,16 +92,17 @@ class TransientBooksController( sizeBytes = book.fileSize, status = media.status.toString(), mediaType = media.mediaType ?: "", - pages = pages.mapIndexed { index, bookPage -> - PageDto( - number = index + 1, - fileName = bookPage.fileName, - mediaType = bookPage.mediaType, - width = bookPage.dimension?.width, - height = bookPage.dimension?.height, - sizeBytes = bookPage.fileSize, - ) - }, + pages = + pages.mapIndexed { index, bookPage -> + PageDto( + number = index + 1, + fileName = bookPage.fileName, + mediaType = bookPage.mediaType, + width = bookPage.dimension?.width, + height = bookPage.dimension?.height, + sizeBytes = bookPage.fileSize, + ) + }, files = media.files.map { it.fileName }, comment = media.comment ?: "", number = metadata.number, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/UserController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/UserController.kt index ababbbf7f..c2f104513 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/UserController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/UserController.kt @@ -54,11 +54,12 @@ class UserController( private val authenticationActivityRepository: AuthenticationActivityRepository, env: Environment, ) { - private val demo = env.activeProfiles.contains("demo") @GetMapping("me") - fun getMe(@AuthenticationPrincipal principal: KomgaPrincipal): UserDto = + fun getMe( + @AuthenticationPrincipal principal: KomgaPrincipal, + ): UserDto = principal.toDto() @PatchMapping("me/password") @@ -114,28 +115,48 @@ class UserController( @AuthenticationPrincipal principal: KomgaPrincipal, ) { userRepository.findByIdOrNull(id)?.let { existing -> - val updatedUser = with(patch) { - existing.copy( - roleAdmin = if (isSet("roles")) roles!!.contains(ROLE_ADMIN) else existing.roleAdmin, - roleFileDownload = if (isSet("roles")) roles!!.contains(ROLE_FILE_DOWNLOAD) else existing.roleFileDownload, - rolePageStreaming = if (isSet("roles")) roles!!.contains(ROLE_PAGE_STREAMING) else existing.rolePageStreaming, - sharedAllLibraries = if (isSet("sharedLibraries")) sharedLibraries!!.all else existing.sharedAllLibraries, - sharedLibrariesIds = if (isSet("sharedLibraries")) { - if (sharedLibraries!!.all) emptySet() - else libraryRepository.findAllByIds(sharedLibraries!!.libraryIds).map { it.id }.toSet() - } else existing.sharedLibrariesIds, - restrictions = ContentRestrictions( - ageRestriction = if (isSet("ageRestriction")) { - if (ageRestriction == null) null - else AgeRestriction(ageRestriction!!.age, ageRestriction!!.restriction) - } else existing.restrictions.ageRestriction, - labelsAllow = if (isSet("labelsAllow")) labelsAllow - ?: emptySet() else existing.restrictions.labelsAllow, - labelsExclude = if (isSet("labelsExclude")) labelsExclude - ?: emptySet() else existing.restrictions.labelsExclude, - ), - ) - } + val updatedUser = + with(patch) { + existing.copy( + roleAdmin = if (isSet("roles")) roles!!.contains(ROLE_ADMIN) else existing.roleAdmin, + roleFileDownload = if (isSet("roles")) roles!!.contains(ROLE_FILE_DOWNLOAD) else existing.roleFileDownload, + rolePageStreaming = if (isSet("roles")) roles!!.contains(ROLE_PAGE_STREAMING) else existing.rolePageStreaming, + sharedAllLibraries = if (isSet("sharedLibraries")) sharedLibraries!!.all else existing.sharedAllLibraries, + sharedLibrariesIds = + if (isSet("sharedLibraries")) { + if (sharedLibraries!!.all) + emptySet() + else + libraryRepository.findAllByIds(sharedLibraries!!.libraryIds).map { it.id }.toSet() + } else { + existing.sharedLibrariesIds + }, + restrictions = + ContentRestrictions( + ageRestriction = + if (isSet("ageRestriction")) { + if (ageRestriction == null) + null + else + AgeRestriction(ageRestriction!!.age, ageRestriction!!.restriction) + } else { + existing.restrictions.ageRestriction + }, + labelsAllow = + if (isSet("labelsAllow")) + labelsAllow + ?: emptySet() + else + existing.restrictions.labelsAllow, + labelsExclude = + if (isSet("labelsExclude")) + labelsExclude + ?: emptySet() + else + existing.restrictions.labelsExclude, + ), + ) + } userLifecycle.updateUser(updatedUser) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } @@ -164,16 +185,20 @@ class UserController( ): Page { if (demo && !principal.user.roleAdmin) throw ResponseStatusException(HttpStatus.FORBIDDEN) val sort = - if (page.sort.isSorted) page.sort - else Sort.by(Sort.Order.desc("dateTime")) + if (page.sort.isSorted) + page.sort + else + Sort.by(Sort.Order.desc("dateTime")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return authenticationActivityRepository.findAllByUser(principal.user, pageRequest).map { it.toDto() } } @@ -186,16 +211,20 @@ class UserController( @Parameter(hidden = true) page: Pageable, ): Page { val sort = - if (page.sort.isSorted) page.sort - else Sort.by(Sort.Order.desc("dateTime")) + if (page.sort.isSorted) + page.sort + else + Sort.by(Sort.Order.desc("dateTime")) val pageRequest = - if (unpaged) UnpagedSorted(sort) - else PageRequest.of( - page.pageNumber, - page.pageSize, - sort, - ) + if (unpaged) + UnpagedSorted(sort) + else + PageRequest.of( + page.pageNumber, + page.pageSize, + sort, + ) return authenticationActivityRepository.findAll(pageRequest).map { it.toDto() } } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookDto.kt index 0fba31073..1b4968037 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookDto.kt @@ -64,7 +64,6 @@ data class BookMetadataDto( val isbnLock: Boolean, val links: List, val linksLock: Boolean, - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val created: LocalDateTime, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookMetadataUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookMetadataUpdateDto.kt index 135bba614..cc17ef2ac 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookMetadataUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/BookMetadataUpdateDto.kt @@ -14,6 +14,7 @@ import kotlin.properties.Delegates class BookMetadataUpdateDto { private val isSet = mutableMapOf() + fun isSet(prop: String) = isSet.getOrDefault(prop, false) @get:NullOrNotBlank @@ -105,19 +106,28 @@ fun BookMetadata.patch(patch: BookMetadataUpdateDto) = numberSortLock = patch.numberSortLock ?: this.numberSortLock, releaseDate = if (patch.isSet("releaseDate")) patch.releaseDate else this.releaseDate, releaseDateLock = patch.releaseDateLock ?: this.releaseDateLock, - authors = if (patch.isSet("authors")) { - if (patch.authors != null) patch.authors!!.map { Author(it.name ?: "", it.role ?: "") } else emptyList() - } else this.authors, + authors = + if (patch.isSet("authors")) { + if (patch.authors != null) patch.authors!!.map { Author(it.name ?: "", it.role ?: "") } else emptyList() + } else { + this.authors + }, authorsLock = patch.authorsLock ?: this.authorsLock, - tags = if (patch.isSet("tags")) { - if (patch.tags != null) patch.tags!! else emptySet() - } else this.tags, + tags = + if (patch.isSet("tags")) { + if (patch.tags != null) patch.tags!! else emptySet() + } else { + this.tags + }, tagsLock = patch.tagsLock ?: this.tagsLock, isbn = if (patch.isSet("isbn")) patch.isbn?.filter { it.isDigit() } ?: "" else this.isbn, isbnLock = patch.isbnLock ?: this.isbnLock, - links = if (patch.isSet("links")) { - if (patch.links != null) patch.links!!.map { WebLink(it.label!!, URI(it.url!!)) } else emptyList() - } else this.links, + links = + if (patch.isSet("links")) { + if (patch.links != null) patch.links!!.map { WebLink(it.label!!, URI(it.url!!)) } else emptyList() + } else { + this.links + }, linksLock = patch.linksLock ?: this.linksLock, ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/CollectionDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/CollectionDto.kt index daafe2c0c..c7ef16fe0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/CollectionDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/CollectionDto.kt @@ -9,14 +9,11 @@ data class CollectionDto( val id: String, val name: String, val ordered: Boolean, - val seriesIds: List, - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val createdDate: LocalDateTime, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val lastModifiedDate: LocalDateTime, - val filtered: Boolean, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt index 84cbf6bd5..fee0b6b0f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryDto.kt @@ -35,34 +35,35 @@ data class LibraryDto( val unavailable: Boolean, ) -fun Library.toDto(includeRoot: Boolean) = LibraryDto( - id = id, - name = name, - root = if (includeRoot) this.root.toFilePath() else "", - importComicInfoBook = importComicInfoBook, - importComicInfoSeries = importComicInfoSeries, - importComicInfoCollection = importComicInfoCollection, - importComicInfoReadList = importComicInfoReadList, - importComicInfoSeriesAppendVolume = importComicInfoSeriesAppendVolume, - importEpubBook = importEpubBook, - importEpubSeries = importEpubSeries, - importMylarSeries = importMylarSeries, - importLocalArtwork = importLocalArtwork, - importBarcodeIsbn = importBarcodeIsbn, - scanForceModifiedTime = scanForceModifiedTime, - scanInterval = scanInterval.toDto(), - scanOnStartup = scanOnStartup, - scanCbx = scanCbx, - scanPdf = scanPdf, - scanEpub = scanEpub, - scanDirectoryExclusions = scanDirectoryExclusions, - repairExtensions = repairExtensions, - convertToCbz = convertToCbz, - emptyTrashAfterScan = emptyTrashAfterScan, - seriesCover = seriesCover.toDto(), - hashFiles = hashFiles, - hashPages = hashPages, - analyzeDimensions = analyzeDimensions, - oneshotsDirectory = oneshotsDirectory, - unavailable = unavailableDate != null, -) +fun Library.toDto(includeRoot: Boolean) = + LibraryDto( + id = id, + name = name, + root = if (includeRoot) this.root.toFilePath() else "", + importComicInfoBook = importComicInfoBook, + importComicInfoSeries = importComicInfoSeries, + importComicInfoCollection = importComicInfoCollection, + importComicInfoReadList = importComicInfoReadList, + importComicInfoSeriesAppendVolume = importComicInfoSeriesAppendVolume, + importEpubBook = importEpubBook, + importEpubSeries = importEpubSeries, + importMylarSeries = importMylarSeries, + importLocalArtwork = importLocalArtwork, + importBarcodeIsbn = importBarcodeIsbn, + scanForceModifiedTime = scanForceModifiedTime, + scanInterval = scanInterval.toDto(), + scanOnStartup = scanOnStartup, + scanCbx = scanCbx, + scanPdf = scanPdf, + scanEpub = scanEpub, + scanDirectoryExclusions = scanDirectoryExclusions, + repairExtensions = repairExtensions, + convertToCbz = convertToCbz, + emptyTrashAfterScan = emptyTrashAfterScan, + seriesCover = seriesCover.toDto(), + hashFiles = hashFiles, + hashPages = hashPages, + analyzeDimensions = analyzeDimensions, + oneshotsDirectory = oneshotsDirectory, + unavailable = unavailableDate != null, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt index 3d25163e9..f46481ce1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/LibraryUpdateDto.kt @@ -5,6 +5,7 @@ import kotlin.properties.Delegates class LibraryUpdateDto { private val isSet = mutableMapOf() + fun isSet(prop: String) = isSet.getOrDefault(prop, false) @get:NullOrNotBlank diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashKnownDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashKnownDto.kt index c3ca2f859..6285bb791 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashKnownDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashKnownDto.kt @@ -10,17 +10,17 @@ data class PageHashKnownDto( val action: PageHashKnown.Action, val deleteCount: Int, val matchCount: Int, - val created: LocalDateTime, val lastModified: LocalDateTime, ) -fun PageHashKnown.toDto() = PageHashKnownDto( - hash = hash, - size = size, - action = action, - deleteCount = deleteCount, - matchCount = matchCount, - created = createdDate.toUTC(), - lastModified = lastModifiedDate.toUTC(), -) +fun PageHashKnown.toDto() = + PageHashKnownDto( + hash = hash, + size = size, + action = action, + deleteCount = deleteCount, + matchCount = matchCount, + created = createdDate.toUTC(), + lastModified = lastModifiedDate.toUTC(), + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashUnknownDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashUnknownDto.kt index fd75da2a6..c3a779994 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashUnknownDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/PageHashUnknownDto.kt @@ -8,8 +8,9 @@ data class PageHashUnknownDto( val matchCount: Int, ) -fun PageHashUnknown.toDto() = PageHashUnknownDto( - hash = hash, - size = size, - matchCount = matchCount, -) +fun PageHashUnknown.toDto() = + PageHashUnknownDto( + hash = hash, + size = size, + matchCount = matchCount, + ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListDto.kt index ee3bd40fa..59058a459 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListDto.kt @@ -10,14 +10,11 @@ data class ReadListDto( val name: String, val summary: String, val ordered: Boolean, - val bookIds: List, - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val createdDate: LocalDateTime, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val lastModifiedDate: LocalDateTime, - val filtered: Boolean, ) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListRequestMatchDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListRequestMatchDto.kt index d08d06e30..f8d55d182 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListRequestMatchDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadListRequestMatchDto.kt @@ -59,6 +59,7 @@ data class ReadListRequestBookMatchSeriesDto( @JsonFormat(pattern = "yyyy-MM-dd") val releaseDate: LocalDate?, ) + data class ReadListRequestBookMatchBookDto( val bookId: String, val number: String, diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadProgressUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadProgressUpdateDto.kt index dec92a58e..84f428bec 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadProgressUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ReadProgressUpdateDto.kt @@ -22,8 +22,11 @@ annotation class ReadProgressUpdateDtoConstraint( ) class ReadProgressUpdateDtoValidator : ConstraintValidator { - override fun isValid(value: ReadProgressUpdateDto?, context: ConstraintValidatorContext?): Boolean = + override fun isValid( + value: ReadProgressUpdateDto?, + context: ConstraintValidatorContext?, + ): Boolean = value != null && ( value.page != null || (value.completed != null && value.completed) - ) + ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ScanIntervalDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ScanIntervalDto.kt index 9221f7a24..eaf0c8e54 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ScanIntervalDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ScanIntervalDto.kt @@ -11,20 +11,22 @@ enum class ScanIntervalDto { WEEKLY, } -fun Library.ScanInterval.toDto() = when (this) { - Library.ScanInterval.DISABLED -> ScanIntervalDto.DISABLED - Library.ScanInterval.HOURLY -> ScanIntervalDto.HOURLY - Library.ScanInterval.EVERY_6H -> ScanIntervalDto.EVERY_6H - Library.ScanInterval.EVERY_12H -> ScanIntervalDto.EVERY_12H - Library.ScanInterval.DAILY -> ScanIntervalDto.DAILY - Library.ScanInterval.WEEKLY -> ScanIntervalDto.WEEKLY -} +fun Library.ScanInterval.toDto() = + when (this) { + Library.ScanInterval.DISABLED -> ScanIntervalDto.DISABLED + Library.ScanInterval.HOURLY -> ScanIntervalDto.HOURLY + Library.ScanInterval.EVERY_6H -> ScanIntervalDto.EVERY_6H + Library.ScanInterval.EVERY_12H -> ScanIntervalDto.EVERY_12H + Library.ScanInterval.DAILY -> ScanIntervalDto.DAILY + Library.ScanInterval.WEEKLY -> ScanIntervalDto.WEEKLY + } -fun ScanIntervalDto.toDomain() = when (this) { - ScanIntervalDto.DISABLED -> Library.ScanInterval.DISABLED - ScanIntervalDto.HOURLY -> Library.ScanInterval.HOURLY - ScanIntervalDto.EVERY_6H -> Library.ScanInterval.EVERY_6H - ScanIntervalDto.EVERY_12H -> Library.ScanInterval.EVERY_12H - ScanIntervalDto.DAILY -> Library.ScanInterval.DAILY - ScanIntervalDto.WEEKLY -> Library.ScanInterval.WEEKLY -} +fun ScanIntervalDto.toDomain() = + when (this) { + ScanIntervalDto.DISABLED -> Library.ScanInterval.DISABLED + ScanIntervalDto.HOURLY -> Library.ScanInterval.HOURLY + ScanIntervalDto.EVERY_6H -> Library.ScanInterval.EVERY_6H + ScanIntervalDto.EVERY_12H -> Library.ScanInterval.EVERY_12H + ScanIntervalDto.DAILY -> Library.ScanInterval.DAILY + ScanIntervalDto.WEEKLY -> Library.ScanInterval.WEEKLY + } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesCoverDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesCoverDto.kt index 12f855c14..ed8dadedd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesCoverDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesCoverDto.kt @@ -9,16 +9,18 @@ enum class SeriesCoverDto { LAST, } -fun Library.SeriesCover.toDto() = when (this) { - Library.SeriesCover.FIRST -> SeriesCoverDto.FIRST - Library.SeriesCover.FIRST_UNREAD_OR_FIRST -> SeriesCoverDto.FIRST_UNREAD_OR_FIRST - Library.SeriesCover.FIRST_UNREAD_OR_LAST -> SeriesCoverDto.FIRST_UNREAD_OR_LAST - Library.SeriesCover.LAST -> SeriesCoverDto.LAST -} +fun Library.SeriesCover.toDto() = + when (this) { + Library.SeriesCover.FIRST -> SeriesCoverDto.FIRST + Library.SeriesCover.FIRST_UNREAD_OR_FIRST -> SeriesCoverDto.FIRST_UNREAD_OR_FIRST + Library.SeriesCover.FIRST_UNREAD_OR_LAST -> SeriesCoverDto.FIRST_UNREAD_OR_LAST + Library.SeriesCover.LAST -> SeriesCoverDto.LAST + } -fun SeriesCoverDto.toDomain() = when (this) { - SeriesCoverDto.FIRST -> Library.SeriesCover.FIRST - SeriesCoverDto.FIRST_UNREAD_OR_FIRST -> Library.SeriesCover.FIRST_UNREAD_OR_FIRST - SeriesCoverDto.FIRST_UNREAD_OR_LAST -> Library.SeriesCover.FIRST_UNREAD_OR_LAST - SeriesCoverDto.LAST -> Library.SeriesCover.LAST -} +fun SeriesCoverDto.toDomain() = + when (this) { + SeriesCoverDto.FIRST -> Library.SeriesCover.FIRST + SeriesCoverDto.FIRST_UNREAD_OR_FIRST -> Library.SeriesCover.FIRST_UNREAD_OR_FIRST + SeriesCoverDto.FIRST_UNREAD_OR_LAST -> Library.SeriesCover.FIRST_UNREAD_OR_LAST + SeriesCoverDto.LAST -> Library.SeriesCover.LAST + } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt index 57186c2e2..e52a2986b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt @@ -57,7 +57,6 @@ data class SeriesMetadataDto( val linksLock: Boolean, val alternateTitles: List, val alternateTitlesLock: Boolean, - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val created: LocalDateTime, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") @@ -71,7 +70,6 @@ data class BookMetadataAggregationDto( val releaseDate: LocalDate?, val summary: String, val summaryNumber: String, - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") val created: LocalDateTime, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt index cb41019eb..e8536ec3b 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt @@ -10,6 +10,7 @@ import kotlin.properties.Delegates class SeriesMetadataUpdateDto { private val isSet = mutableMapOf() + fun isSet(prop: String) = isSet.getOrDefault(prop, false) val status: SeriesMetadata.Status? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt index 4d1eb1c4d..bacb4ee4a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SettingsUpdateDto.kt @@ -7,6 +7,7 @@ import kotlin.properties.Delegates class SettingsUpdateDto { private val isSet = mutableMapOf() + fun isSet(prop: String) = isSet.getOrDefault(prop, false) var deleteEmptyCollections: Boolean? = null diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ThumbnailSizeDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ThumbnailSizeDto.kt index 7d63f0f00..1193fe257 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ThumbnailSizeDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/ThumbnailSizeDto.kt @@ -9,16 +9,18 @@ enum class ThumbnailSizeDto { XLARGE, } -fun ThumbnailSize.toDto() = when (this) { - ThumbnailSize.DEFAULT -> ThumbnailSizeDto.DEFAULT - ThumbnailSize.MEDIUM -> ThumbnailSizeDto.MEDIUM - ThumbnailSize.LARGE -> ThumbnailSizeDto.LARGE - ThumbnailSize.XLARGE -> ThumbnailSizeDto.XLARGE -} +fun ThumbnailSize.toDto() = + when (this) { + ThumbnailSize.DEFAULT -> ThumbnailSizeDto.DEFAULT + ThumbnailSize.MEDIUM -> ThumbnailSizeDto.MEDIUM + ThumbnailSize.LARGE -> ThumbnailSizeDto.LARGE + ThumbnailSize.XLARGE -> ThumbnailSizeDto.XLARGE + } -fun ThumbnailSizeDto.toDomain() = when (this) { - ThumbnailSizeDto.DEFAULT -> ThumbnailSize.DEFAULT - ThumbnailSizeDto.MEDIUM -> ThumbnailSize.MEDIUM - ThumbnailSizeDto.LARGE -> ThumbnailSize.LARGE - ThumbnailSizeDto.XLARGE -> ThumbnailSize.XLARGE -} +fun ThumbnailSizeDto.toDomain() = + when (this) { + ThumbnailSizeDto.DEFAULT -> ThumbnailSize.DEFAULT + ThumbnailSizeDto.MEDIUM -> ThumbnailSize.MEDIUM + ThumbnailSizeDto.LARGE -> ThumbnailSize.LARGE + ThumbnailSizeDto.XLARGE -> ThumbnailSize.XLARGE + } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/UserUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/UserUpdateDto.kt index e882c90ff..8f02d08f0 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/UserUpdateDto.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/UserUpdateDto.kt @@ -7,6 +7,7 @@ import kotlin.properties.Delegates class UserUpdateDto { private val isSet = mutableMapOf() + fun isSet(prop: String) = isSet.getOrDefault(prop, false) @get:Valid diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/apprunner/PasswordResetRunner.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/apprunner/PasswordResetRunner.kt index 1ecbecd34..8e8ae643f 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/apprunner/PasswordResetRunner.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/apprunner/PasswordResetRunner.kt @@ -18,6 +18,7 @@ class PasswordResetRunner( ) : ApplicationRunner { private val resetFor = "reset" private val resetTo = "newpassword" + override fun run(args: ApplicationArguments) { val newPassword = args.getOptionValues(resetTo)?.firstOrNull() val resetFor = args.getOptionValues(resetFor)?.toSet() ?: emptySet() diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/AuthenticationActivityCleanupController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/AuthenticationActivityCleanupController.kt index 85e680b2c..32ea0e27d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/AuthenticationActivityCleanupController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/AuthenticationActivityCleanupController.kt @@ -15,7 +15,6 @@ private val logger = KotlinLogging.logger {} class AuthenticationActivityCleanupController( private val authenticationActivityRepository: AuthenticationActivityRepository, ) { - // Run every day @Scheduled(fixedRate = 86_400_000) fun cleanup() { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/InitialUserController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/InitialUserController.kt index a3df325c8..476726fc2 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/InitialUserController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/InitialUserController.kt @@ -19,7 +19,6 @@ class InitialUserController( private val userLifecycle: KomgaUserLifecycle, private val initialUsers: List, ) { - @EventListener(ApplicationReadyEvent::class) fun createInitialUserOnStartupIfNoneExist() { if (userLifecycle.countUsers() == 0L) { @@ -38,17 +37,19 @@ class InitialUserController( @Profile("dev") class InitialUsersDevConfiguration { @Bean - fun initialUsers(): List = listOf( - KomgaUser("admin@example.org", "admin", roleAdmin = true), - KomgaUser("user@example.org", "user", roleAdmin = false), - ) + fun initialUsers(): List = + listOf( + KomgaUser("admin@example.org", "admin", roleAdmin = true), + KomgaUser("user@example.org", "user", roleAdmin = false), + ) } @Configuration @Profile("!dev") class InitialUsersProdConfiguration { @Bean - fun initialUsers(): List = listOf( - KomgaUser("admin@example.org", RandomStringUtils.randomAlphanumeric(12), roleAdmin = true), - ) + fun initialUsers(): List = + listOf( + KomgaUser("admin@example.org", RandomStringUtils.randomAlphanumeric(12), roleAdmin = true), + ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/MetricsPublisherController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/MetricsPublisherController.kt index 4ef54e7d1..67610a2c7 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/MetricsPublisherController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/MetricsPublisherController.kt @@ -45,7 +45,6 @@ class MetricsPublisherController( private val sidecarRepository: SidecarRepository, private val meterRegistry: MeterRegistry, ) { - init { Timer.builder(METER_TASKS_EXECUTION) .description("Task execution time") @@ -60,26 +59,29 @@ class MetricsPublisherController( private final val entitiesNoTags = listOf(LIBRARIES, COLLECTIONS, READLISTS) private final val allEntities = entitiesMultiTag + entitiesNoTags - val multiGauges = entitiesMultiTag.associateWith { entity -> - MultiGauge.builder("komga.$entity") - .description("The number of $entity") - .baseUnit("count") - .register(meterRegistry) - } - - val noTagGauges = entitiesNoTags.associateWith { entity -> - AtomicLong(0).also { value -> - Gauge.builder("komga.$entity", value) { value.get().toDouble() } + val multiGauges = + entitiesMultiTag.associateWith { entity -> + MultiGauge.builder("komga.$entity") .description("The number of $entity") .baseUnit("count") .register(meterRegistry) } - } - val bookFileSizeGauge = MultiGauge.builder("komga.$BOOKS_FILESIZE") - .description("The cumulated filesize of books") - .baseUnit("bytes") - .register(meterRegistry) + val noTagGauges = + entitiesNoTags.associateWith { entity -> + AtomicLong(0).also { value -> + Gauge.builder("komga.$entity", value) { value.get().toDouble() } + .description("The number of $entity") + .baseUnit("count") + .register(meterRegistry) + } + } + + val bookFileSizeGauge = + MultiGauge.builder("komga.$BOOKS_FILESIZE") + .description("The cumulated filesize of books") + .baseUnit("bytes") + .register(meterRegistry) @EventListener private fun pushMetricsOnEvent(event: DomainEvent) { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicScannerController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicScannerController.kt index e0db8cee8..4abc0ee52 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicScannerController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicScannerController.kt @@ -18,7 +18,6 @@ class PeriodicScannerController( private val libraryRepository: LibraryRepository, private val libraryScanScheduler: LibraryScanScheduler, ) { - @EventListener(classes = [ApplicationReadyEvent::class]) fun scanOnStartup() { libraryRepository.findAll() diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/SearchIndexController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/SearchIndexController.kt index 2eb207f53..d14b95ff1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/SearchIndexController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/SearchIndexController.kt @@ -18,7 +18,6 @@ class SearchIndexController( private val luceneHelper: LuceneHelper, private val taskEmitter: TaskEmitter, ) { - @EventListener(ApplicationReadyEvent::class) fun createIndexIfNoneExist() { if (!luceneHelper.indexExists()) { diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/ThumbnailMetadataFixerController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/ThumbnailMetadataFixerController.kt index 916d279ff..b03eef435 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/ThumbnailMetadataFixerController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/ThumbnailMetadataFixerController.kt @@ -15,7 +15,6 @@ private val logger = KotlinLogging.logger {} class ThumbnailMetadataFixerController( private val taskEmitter: TaskEmitter, ) { - @EventListener(ApplicationReadyEvent::class) fun findThumbnailsWithoutMetadata() { logger.info { "Find and fix thumbnails without metadata" } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/sse/SseController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/sse/SseController.kt index 00d757205..f1811c661 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/sse/SseController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/sse/SseController.kt @@ -39,7 +39,6 @@ class SseController( private val bookRepository: BookRepository, private val tasksRepository: TasksRepository, ) : SmartLifecycle { - private var acceptingConnections = true private val emitters = Collections.synchronizedMap(HashMap()) @@ -108,7 +107,12 @@ class SseController( } } - private fun emitSse(name: String, data: Any, adminOnly: Boolean = false, userIdOnly: String? = null) { + private fun emitSse( + name: String, + data: Any, + adminOnly: Boolean = false, + userIdOnly: String? = null, + ) { logger.debug { "Publish SSE: '$name':$data" } synchronized(emitters) { diff --git a/komga/src/main/kotlin/org/gotson/komga/language/LanguageUtils.kt b/komga/src/main/kotlin/org/gotson/komga/language/LanguageUtils.kt index 2c7525ea8..b520a733a 100644 --- a/komga/src/main/kotlin/org/gotson/komga/language/LanguageUtils.kt +++ b/komga/src/main/kotlin/org/gotson/komga/language/LanguageUtils.kt @@ -46,7 +46,10 @@ fun Iterable.lowerNotBlank() = fun Iterable.toSetOrNull() = this.toSet().ifEmpty { null } -fun LocalDateTime.notEquals(other: LocalDateTime, precision: TemporalUnit = ChronoUnit.MILLIS) = +fun LocalDateTime.notEquals( + other: LocalDateTime, + precision: TemporalUnit = ChronoUnit.MILLIS, +) = this.truncatedTo(precision) != other.truncatedTo(precision) fun String.stripAccents(): String = StringUtils.stripAccents(this) diff --git a/komga/src/test/kotlin/org/gotson/komga/AutowiringTest.kt b/komga/src/test/kotlin/org/gotson/komga/AutowiringTest.kt index d17809983..a9483a94f 100644 --- a/komga/src/test/kotlin/org/gotson/komga/AutowiringTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/AutowiringTest.kt @@ -12,7 +12,6 @@ class AutowiringTest( @Autowired private val dataSources: List, @Autowired private val dslContexts: List, ) { - @Test fun `Application loads properly with test properties`() = Unit diff --git a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskProcessorTest.kt b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskProcessorTest.kt index ab7b3dea8..91f307ad2 100644 --- a/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskProcessorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/application/tasks/TaskProcessorTest.kt @@ -20,14 +20,16 @@ class TaskProcessorTest( @Autowired private val taskEmitter: TaskEmitter, @Autowired private val taskProcessor: TaskProcessor, ) { - @MockkBean private lateinit var mockBookLifecycle: BookLifecycle @MockkBean private lateinit var mockBookRepository: BookRepository - fun testTasks(sleep: Duration = 3.seconds, block: () -> Unit) { + fun testTasks( + sleep: Duration = 3.seconds, + block: () -> Unit, + ) { taskProcessor.processTasks = false block() taskProcessor.processTasks = true diff --git a/komga/src/test/kotlin/org/gotson/komga/architecture/CodingRulesTest.kt b/komga/src/test/kotlin/org/gotson/komga/architecture/CodingRulesTest.kt index 6c45cf552..71652a813 100644 --- a/komga/src/test/kotlin/org/gotson/komga/architecture/CodingRulesTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/architecture/CodingRulesTest.kt @@ -13,26 +13,24 @@ import org.gotson.komga.Application @AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.DoNotIncludeTests::class]) class CodingRulesTest { + @ArchTest + private val noAccessToStandardStreams = NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS @ArchTest - private val no_access_to_standard_streams = NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS + private val noGenericExceptions = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS @ArchTest - private val no_generic_exceptions = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS + private val noJodatime = NO_CLASSES_SHOULD_USE_JODATIME @ArchTest - private val no_jodatime = NO_CLASSES_SHOULD_USE_JODATIME + private val noJavaUtilLogging = NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING @ArchTest - private val no_java_util_logging = NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING - - @ArchTest - private val no_field_injection = NO_CLASSES_SHOULD_USE_FIELD_INJECTION + private val noFieldInjection = NO_CLASSES_SHOULD_USE_FIELD_INJECTION } @AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.OnlyIncludeTests::class]) class TestCodingRulesTest { - @ArchTest - private val no_junit_assertions = noClasses().should().dependOnClassesThat().haveFullyQualifiedName("org.junit.jupiter.api.Assertions") + private val noJunitAssertions = noClasses().should().dependOnClassesThat().haveFullyQualifiedName("org.junit.jupiter.api.Assertions") } diff --git a/komga/src/test/kotlin/org/gotson/komga/architecture/DomainDrivenDesignRulesTest.kt b/komga/src/test/kotlin/org/gotson/komga/architecture/DomainDrivenDesignRulesTest.kt index 95b61d86e..e5a62dd1c 100644 --- a/komga/src/test/kotlin/org/gotson/komga/architecture/DomainDrivenDesignRulesTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/architecture/DomainDrivenDesignRulesTest.kt @@ -10,9 +10,8 @@ import org.gotson.komga.Application @AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.DoNotIncludeTests::class]) class DomainDrivenDesignRulesTest { - @ArchTest - val domain_model_should_not_access_other_packages: ArchRule = + val domainModelShouldNotAccessOtherPackages: ArchRule = noClasses() .that().resideInAPackage("..domain..model..") .should().dependOnClassesThat().resideInAnyPackage( @@ -23,7 +22,7 @@ class DomainDrivenDesignRulesTest { ) @ArchTest - var classes_named_controller_should_be_in_an_interfaces_package: ArchRule = + var classesNamedControllerShouldBeInAnInterfacesPackage: ArchRule = classes() .that().haveSimpleNameContaining("Controller") .should().resideInAPackage("..interfaces..") diff --git a/komga/src/test/kotlin/org/gotson/komga/architecture/NamingConventionTest.kt b/komga/src/test/kotlin/org/gotson/komga/architecture/NamingConventionTest.kt index b2384a26c..34c48efc9 100644 --- a/komga/src/test/kotlin/org/gotson/komga/architecture/NamingConventionTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/architecture/NamingConventionTest.kt @@ -12,9 +12,8 @@ import org.springframework.web.bind.annotation.RestController @AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.DoNotIncludeTests::class]) class NamingConventionTest { - @ArchTest - val services_should_not_have_names_containing_service_or_manager: ArchRule = + val servicesShouldNotHaveNamesContainingServiceOrManager: ArchRule = noClasses() .that().resideInAnyPackage("..domain..service..", "..application..service..") .should().haveSimpleNameContaining("service") @@ -24,7 +23,7 @@ class NamingConventionTest { .because("it doesn't bear any intent") @ArchTest - val controllers_should_be_suffixed: ArchRule = + val controllersShouldBeSuffixed: ArchRule = classes() .that().areAnnotatedWith(RestController::class.java) .or().areAnnotatedWith(Controller::class.java) diff --git a/komga/src/test/kotlin/org/gotson/komga/architecture/SlicesIsolationRulesTest.kt b/komga/src/test/kotlin/org/gotson/komga/architecture/SlicesIsolationRulesTest.kt index 8cd33396b..a272f774e 100644 --- a/komga/src/test/kotlin/org/gotson/komga/architecture/SlicesIsolationRulesTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/architecture/SlicesIsolationRulesTest.kt @@ -10,7 +10,7 @@ import org.gotson.komga.Application @AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.DoNotIncludeTests::class]) class SlicesIsolationRulesTest { @ArchTest - val interfaces_should_only_use_their_own_slice: ArchRule = + val interfacesShouldOnlyUseTheirOwnSlice: ArchRule = slices() .matching("..interfaces.(*)..").namingSlices("Interface $1") .`as`("Interfaces").should().notDependOnEachOther() diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/BCP47TagValidatorTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/BCP47TagValidatorTest.kt index b35cfb2f5..32670e669 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/BCP47TagValidatorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/BCP47TagValidatorTest.kt @@ -7,10 +7,12 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream class BCP47TagValidatorTest { - @ParameterizedTest @MethodSource("languagesNormalized") - fun `given source languageTag when normalizing then result is expected`(source: String?, expected: String) { + fun `given source languageTag when normalizing then result is expected`( + source: String?, + expected: String, + ) { assertThat(BCP47TagValidator.normalize(source)).isEqualTo(expected) } @@ -28,7 +30,10 @@ class BCP47TagValidatorTest { @ParameterizedTest @MethodSource("languagesValid") - fun `given source languageTag when validating then result is expected`(source: String?, expected: Boolean) { + fun `given source languageTag when validating then result is expected`( + source: String?, + expected: Boolean, + ) { assertThat(BCP47TagValidator.isValid(source)).isEqualTo(expected) } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/ContentRestrictionsTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/ContentRestrictionsTest.kt index 74db4a1e3..1f257ccc5 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/ContentRestrictionsTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/ContentRestrictionsTest.kt @@ -4,7 +4,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class ContentRestrictionsTest { - @Test fun `given no arguments when creating restriction then all restrictions are null`() { val restriction = ContentRestrictions() @@ -40,10 +39,11 @@ class ContentRestrictionsTest { @Test fun `given empty labels when creating restriction then label restrictions are normalized`() { - val restriction = ContentRestrictions( - labelsAllow = setOf("", " "), - labelsExclude = setOf("", " "), - ) + val restriction = + ContentRestrictions( + labelsAllow = setOf("", " "), + labelsExclude = setOf("", " "), + ) assertThat(restriction.labelsAllow).isEmpty() assertThat(restriction.labelsExclude).isEmpty() @@ -51,10 +51,11 @@ class ContentRestrictionsTest { @Test fun `given labels with duplicate values when creating restriction then label restrictions are normalized`() { - val restriction = ContentRestrictions( - labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), - labelsExclude = setOf("c", "d", "D", "d ", "d", " D "), - ) + val restriction = + ContentRestrictions( + labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), + labelsExclude = setOf("c", "d", "D", "d ", "d", " D "), + ) assertThat(restriction.labelsAllow).containsExactlyInAnyOrder("a", "b") assertThat(restriction.labelsExclude).containsExactlyInAnyOrder("c", "d") @@ -62,10 +63,11 @@ class ContentRestrictionsTest { @Test fun `given labels with same value in both allow and exclude when creating restriction then exclude labels are removed from allow labels`() { - val restriction = ContentRestrictions( - labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), - labelsExclude = setOf(" A ", "d", "D", "d ", "d", " D "), - ) + val restriction = + ContentRestrictions( + labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), + labelsExclude = setOf(" A ", "d", "D", "d ", "d", " D "), + ) assertThat(restriction.labelsAllow).containsExactlyInAnyOrder("b") assertThat(restriction.labelsExclude).containsExactlyInAnyOrder("a", "d") @@ -73,10 +75,11 @@ class ContentRestrictionsTest { @Test fun `given allow labels with all values in exclude labels when creating restriction then allow labels is null`() { - val restriction = ContentRestrictions( - labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), - labelsExclude = setOf(" A ", "b", "B", "B ", "b", " B "), - ) + val restriction = + ContentRestrictions( + labelsAllow = setOf("a", "b", "B", "b ", "b", " B "), + labelsExclude = setOf(" A ", "b", "B", "B ", "b", " B "), + ) assertThat(restriction.labelsAllow).isEmpty() assertThat(restriction.labelsExclude).containsExactlyInAnyOrder("a", "b") diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/KomgaUserTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/KomgaUserTest.kt index 4c98bf342..8ead0b6a0 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/KomgaUserTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/KomgaUserTest.kt @@ -5,12 +5,10 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class KomgaUserTest { - val defaultUser = KomgaUser("user@example.org", "aPassword", false) @Nested inner class ContentRestriction { - @Test fun `given user with age AllowOnlyUnder restriction when checking for content restriction then it is accurate`() { val user = defaultUser.copy(restrictions = ContentRestrictions(AgeRestriction(5, AllowExclude.ALLOW_ONLY))) @@ -54,12 +52,14 @@ class KomgaUserTest { @Test fun `given user with both sharing label AllowOnly and Exclude restriction when checking for content restriction then it is accurate`() { - val user = defaultUser.copy( - restrictions = ContentRestrictions( - labelsAllow = setOf("allow", "both"), - labelsExclude = setOf("exclude", "both"), - ), - ) + val user = + defaultUser.copy( + restrictions = + ContentRestrictions( + labelsAllow = setOf("allow", "both"), + labelsExclude = setOf("exclude", "both"), + ), + ) assertThat(user.isContentAllowed(sharingLabels = setOf("allow"))).isTrue assertThat(user.isContentAllowed(sharingLabels = setOf("allow", "other"))).isTrue @@ -70,12 +70,14 @@ class KomgaUserTest { @Test fun `given user with both age AllowOnlyUnder restriction and sharing label AllowOnly restriction when checking for content restriction then it is accurate`() { - val user = defaultUser.copy( - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), - labelsAllow = setOf("allow"), - ), - ) + val user = + defaultUser.copy( + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), + labelsAllow = setOf("allow"), + ), + ) assertThat(user.isContentAllowed(ageRating = 5)).`as`("age 5 only is sufficient").isTrue assertThat(user.isContentAllowed(ageRating = 15)).`as`("age 15 is not allowed").isFalse @@ -91,12 +93,14 @@ class KomgaUserTest { @Test fun `given user with both age AllowOnlyUnder restriction and sharing label Exclude restriction when checking for content restriction then it is accurate`() { - val user = defaultUser.copy( - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), - labelsExclude = setOf("exclude"), - ), - ) + val user = + defaultUser.copy( + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), + labelsExclude = setOf("exclude"), + ), + ) assertThat(user.isContentAllowed(ageRating = 5)).isTrue assertThat(user.isContentAllowed(ageRating = 15)).isFalse diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/PageHashTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/PageHashTest.kt index 6fe22f70d..35034eda7 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/PageHashTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/PageHashTest.kt @@ -4,7 +4,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class PageHashTest { - @Test fun `given negative size when creating a PageHash then its size is null`() { val pageHash = PageHash("abc", -5) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/ProxyExtensionTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/ProxyExtensionTest.kt index 4a0c4f8de..3910e6153 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/ProxyExtensionTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/ProxyExtensionTest.kt @@ -4,7 +4,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class ProxyExtensionTest { - @Test fun `when creating proxy of MediaExtension class then it is created`() { val proxy = ProxyExtension.of(MediaExtensionEpub::class.qualifiedName) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt index ad781f32c..7e2088a2e 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/model/Utils.kt @@ -23,7 +23,11 @@ fun makeBook( ) } -fun makeSeries(name: String, libraryId: String = "", url: URL? = null): Series { +fun makeSeries( + name: String, + libraryId: String = "", + url: URL? = null, +): Series { Thread.sleep(5) return Series( name = name, @@ -33,7 +37,12 @@ fun makeSeries(name: String, libraryId: String = "", url: URL? = null): Series { ) } -fun makeLibrary(name: String = "default", path: String = "file:/${name.replace(" ", "_")}", id: String = TsidCreator.getTsid256().toString(), url: URL? = null): Library { +fun makeLibrary( + name: String = "default", + path: String = "file:/${name.replace(" ", "_")}", + id: String = TsidCreator.getTsid256().toString(), + url: URL? = null, +): Library { return Library( name = name, root = url ?: URL(path), diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt index ed82840ff..7b635b855 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookAnalyzerTest.kt @@ -29,7 +29,6 @@ import kotlin.io.path.toPath class BookAnalyzerTest( @Autowired private val komgaProperties: KomgaProperties, ) { - @SpykBean private lateinit var bookAnalyzer: BookAnalyzer @@ -207,9 +206,10 @@ class BookAnalyzerTest( val mediaType = "image/${directory.fileName.extension}" - val hashes = files.map { - bookAnalyzer.hashPage(BookPage(it.name, mediaType = mediaType), it.inputStream().readBytes()) - } + val hashes = + files.map { + bookAnalyzer.hashPage(BookPage(it.name, mediaType = mediaType), it.inputStream().readBytes()) + } assertThat(hashes.first()).isEqualTo(hashes.last()) } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookImporterTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookImporterTest.kt index a030a0aca..88d07b95a 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookImporterTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookImporterTest.kt @@ -58,7 +58,6 @@ class BookImporterTest( @Autowired private val readListRepository: ReadListRepository, @Autowired private val readListLifecycle: ReadListLifecycle, ) { - private val library = makeLibrary("lib", "file:/library") private val user1 = KomgaUser("user1@example.org", "", false) private val user2 = KomgaUser("user2@example.org", "", false) @@ -98,9 +97,10 @@ class BookImporterTest( val sourceFile = Paths.get("/non-existent") // when - val thrown = Assertions.catchThrowable { - bookImporter.importBook(sourceFile, makeSeries("a series"), CopyMode.COPY) - } + val thrown = + Assertions.catchThrowable { + bookImporter.importBook(sourceFile, makeSeries("a series"), CopyMode.COPY) + } // then assertThat(thrown).hasCauseInstanceOf(FileNotFoundException::class.java) @@ -118,9 +118,10 @@ class BookImporterTest( val series = makeSeries("dest", url = destDir.toUri().toURL()) // when - val thrown = Assertions.catchThrowable { - bookImporter.importBook(sourceFile, series, CopyMode.COPY) - } + val thrown = + Assertions.catchThrowable { + bookImporter.importBook(sourceFile, series, CopyMode.COPY) + } // then assertThat(thrown).hasCauseInstanceOf(FileAlreadyExistsException::class.java) @@ -139,9 +140,10 @@ class BookImporterTest( val series = makeSeries("dest").copy(url = destDir.toUri().toURL()) // when - val thrown = Assertions.catchThrowable { - bookImporter.importBook(sourceFile, series, CopyMode.COPY, destinationName = "dest") - } + val thrown = + Assertions.catchThrowable { + bookImporter.importBook(sourceFile, series, CopyMode.COPY, destinationName = "dest") + } // then assertThat(thrown).hasCauseInstanceOf(FileAlreadyExistsException::class.java) @@ -162,9 +164,10 @@ class BookImporterTest( libraryRepository.insert(libraryJimfs) // when - val thrown = Assertions.catchThrowable { - bookImporter.importBook(sourceFile, series, CopyMode.COPY) - } + val thrown = + Assertions.catchThrowable { + bookImporter.importBook(sourceFile, series, CopyMode.COPY) + } // then assertThat(thrown).isInstanceOf(PathContainedInPath::class.java) @@ -181,16 +184,18 @@ class BookImporterTest( val sourceFile = sourceDir.resolve("2.cbz").createFile() val destDir = fs.getPath("/library/series").createDirectories() - val existingBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, existingBooks) - seriesLifecycle.sortBooks(series) - } + val existingBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, existingBooks) + seriesLifecycle.sortBooks(series) + } // when bookImporter.importBook(sourceFile, series, CopyMode.COPY) @@ -224,16 +229,18 @@ class BookImporterTest( sourceDir.resolve("BOOK 2-1.jpg").createFile() val destDir = fs.getPath("/library/series").createDirectories() - val existingBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, existingBooks) - seriesLifecycle.sortBooks(series) - } + val existingBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, existingBooks) + seriesLifecycle.sortBooks(series) + } // when bookImporter.importBook(sourceFile, series, CopyMode.COPY) @@ -257,16 +264,18 @@ class BookImporterTest( sourceDir.resolve("BOOK 2-1.jpg").createFile() val destDir = fs.getPath("/library/series").createDirectories() - val existingBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, existingBooks) - seriesLifecycle.sortBooks(series) - } + val existingBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, existingBooks) + seriesLifecycle.sortBooks(series) + } // when bookImporter.importBook(sourceFile, series, CopyMode.COPY, destinationName = "book 5") @@ -292,16 +301,18 @@ class BookImporterTest( val existingSidecar2 = destDir.resolve("4-1.jpg").createFile() val bookToUpgrade = makeBook("2", libraryId = library.id, url = existingFile.toUri().toURL()) - val otherBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) - seriesLifecycle.sortBooks(series) - } + val otherBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) + seriesLifecycle.sortBooks(series) + } // when bookImporter.importBook(sourceFile, series, CopyMode.MOVE, upgradeBookId = bookToUpgrade.id) @@ -336,16 +347,18 @@ class BookImporterTest( val existingFile = destDir.resolve("4.cbz").createFile() val bookToUpgrade = makeBook("2", libraryId = library.id, url = existingFile.toUri().toURL()) - val otherBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) - seriesLifecycle.sortBooks(series) - } + val otherBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) + seriesLifecycle.sortBooks(series) + } metadataRepository.findById(bookToUpgrade.id).let { metadataRepository.update( @@ -396,16 +409,18 @@ class BookImporterTest( val existingFile = destDir.resolve("2.cbz").createFile() val bookToUpgrade = makeBook("2", libraryId = library.id, url = existingFile.toUri().toURL()) - val otherBooks = listOf( - makeBook("1", libraryId = library.id), - makeBook("3", libraryId = library.id), - ) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) - seriesLifecycle.sortBooks(series) - } + val otherBooks = + listOf( + makeBook("1", libraryId = library.id), + makeBook("3", libraryId = library.id), + ) + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, listOf(bookToUpgrade) + otherBooks) + seriesLifecycle.sortBooks(series) + } // when bookImporter.importBook(sourceFile, series, CopyMode.COPY, destinationName = "2", upgradeBookId = bookToUpgrade.id) @@ -436,12 +451,13 @@ class BookImporterTest( val existingFile = destDir.resolve("1.cbz").createFile() val bookToUpgrade = makeBook("1", libraryId = library.id, url = existingFile.toUri().toURL()) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, listOf(bookToUpgrade)) - seriesLifecycle.sortBooks(series) - } + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, listOf(bookToUpgrade)) + seriesLifecycle.sortBooks(series) + } mediaRepository.findById(bookToUpgrade.id).let { media -> mediaRepository.update( @@ -485,17 +501,19 @@ class BookImporterTest( val existingFile = destDir.resolve("1.cbz").createFile() val bookToUpgrade = makeBook("1", libraryId = library.id, url = existingFile.toUri().toURL()) - val series = makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) - .also { series -> - seriesLifecycle.createSeries(series) - seriesLifecycle.addBooks(series, listOf(bookToUpgrade)) - seriesLifecycle.sortBooks(series) - } + val series = + makeSeries("series", url = destDir.toUri().toURL(), libraryId = library.id) + .also { series -> + seriesLifecycle.createSeries(series) + seriesLifecycle.addBooks(series, listOf(bookToUpgrade)) + seriesLifecycle.sortBooks(series) + } - val readList = ReadList( - name = "readlist", - bookIds = listOf(bookToUpgrade.id).toIndexedMap(), - ) + val readList = + ReadList( + name = "readlist", + bookIds = listOf(bookToUpgrade.id).toIndexedMap(), + ) readListLifecycle.addReadList(readList) // when diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt index 586d910b0..706429c7b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/BookLifecycleTest.kt @@ -55,7 +55,6 @@ class BookLifecycleTest( @Autowired private val userRepository: KomgaUserRepository, @Autowired private val thumbnailBookRepository: ThumbnailBookRepository, ) { - @SpykBean private lateinit var bookLifecycle: BookLifecycle @@ -303,11 +302,12 @@ class BookLifecycleTest( } private val device = R2Device("abc", "device") - private val epubResources = listOf( - MediaFile("ch1.xhtml", "application/xhtml+xml"), - MediaFile("ch2.xhtml", "application/xhtml+xml"), - MediaFile("ch3.xhtml", "application/xhtml+xml"), - ) + private val epubResources = + listOf( + MediaFile("ch1.xhtml", "application/xhtml+xml"), + MediaFile("ch2.xhtml", "application/xhtml+xml"), + MediaFile("ch3.xhtml", "application/xhtml+xml"), + ) private fun makeEpubPositions(): List { var startPosition = 1 @@ -330,9 +330,10 @@ class BookLifecycleTest( bookLifecycle.markProgression(book, user1, progress) // when - val thrown = catchThrowable { - bookLifecycle.markProgression(book, user1, progress.copy(modified = progress.modified.minusHours(1))) - } + val thrown = + catchThrowable { + bookLifecycle.markProgression(book, user1, progress.copy(modified = progress.modified.minusHours(1))) + } // then assertThat(thrown) @@ -349,9 +350,10 @@ class BookLifecycleTest( } // when - val thrown = catchThrowable { - bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("", "", locations = R2Locator.Location(position = 15)))) - } + val thrown = + catchThrowable { + bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("", "", locations = R2Locator.Location(position = 15)))) + } // then assertThat(thrown) @@ -367,9 +369,10 @@ class BookLifecycleTest( } // when - val thrown = catchThrowable { - bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch5.xhtml", "", locations = R2Locator.Location(position = 15)))) - } + val thrown = + catchThrowable { + bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch5.xhtml", "", locations = R2Locator.Location(position = 15)))) + } // then assertThat(thrown) @@ -385,9 +388,10 @@ class BookLifecycleTest( } // when - val thrown = catchThrowable { - bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch1.xhtml", "", locations = R2Locator.Location(position = 15)))) - } + val thrown = + catchThrowable { + bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch1.xhtml", "", locations = R2Locator.Location(position = 15)))) + } // then assertThat(thrown) @@ -403,9 +407,10 @@ class BookLifecycleTest( } // when - val thrown = catchThrowable { - bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch1.xhtml", "", locations = R2Locator.Location(progression = 0.3F)))) - } + val thrown = + catchThrowable { + bookLifecycle.markProgression(book, user1, R2Progression(ZonedDateTime.now(), device, R2Locator("ch1.xhtml", "", locations = R2Locator.Location(progression = 0.3F)))) + } // then assertThat(thrown) @@ -442,13 +447,14 @@ class BookLifecycleTest( } // when - val newProgression = R2Progression( - ZonedDateTime.now(), - device, - with(epubPositions[0]) { - copy(locations = locations!!.copy(progression = locations!!.progression!! + 0.5F)) - }, - ) + val newProgression = + R2Progression( + ZonedDateTime.now(), + device, + with(epubPositions[0]) { + copy(locations = locations!!.copy(progression = locations!!.progression!! + 0.5F)) + }, + ) assertThatNoException().isThrownBy { bookLifecycle.markProgression(book, user1, newProgression) } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/FileSystemScannerTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/FileSystemScannerTest.kt index 1a709d6e6..44570b5a4 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/FileSystemScannerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/FileSystemScannerTest.kt @@ -164,10 +164,11 @@ class FileSystemScannerTest { val root = fs.getPath("/root") Files.createDirectory(root) - val subDirs = listOf( - "series1" to listOf("volume1.cbz", "volume2.cbz"), - "series2" to listOf("book1.cbz", "book2.cbz"), - ).toMap() + val subDirs = + listOf( + "series1" to listOf("volume1.cbz", "volume2.cbz"), + "series2" to listOf("book1.cbz", "book2.cbz"), + ).toMap() subDirs.forEach { (dir, files) -> makeSubDir(root, dir, files) @@ -205,10 +206,11 @@ class FileSystemScannerTest { val link = fs.getPath("/link") Files.createSymbolicLink(link, root) - val subDirs = listOf( - "series1" to listOf("volume1.cbz", "volume2.cbz"), - "series2" to listOf("book1.cbz", "book2.cbz"), - ).toMap() + val subDirs = + listOf( + "series1" to listOf("volume1.cbz", "volume2.cbz"), + "series2" to listOf("book1.cbz", "book2.cbz"), + ).toMap() subDirs.forEach { (dir, files) -> makeSubDir(root, dir, files) @@ -243,10 +245,11 @@ class FileSystemScannerTest { val root = fs.getPath("/root") Files.createDirectory(root) - val subDirs = listOf( - "series1" to listOf("volume1.cbz", "volume2.cbz"), - "series2" to listOf("book1.cbz", "book2.cbz"), - ).toMap() + val subDirs = + listOf( + "series1" to listOf("volume1.cbz", "volume2.cbz"), + "series2" to listOf("book1.cbz", "book2.cbz"), + ).toMap() subDirs.forEach { (dir, files) -> makeSubDir(root, dir, files) @@ -393,7 +396,11 @@ class FileSystemScannerTest { } } - private fun makeSubDir(root: Path, name: String, files: List): Path { + private fun makeSubDir( + root: Path, + name: String, + files: List, + ): Path { val dir = root.resolve(name) Files.createDirectory(dir) files.forEach { Files.createFile(root.resolve(root.fileSystem.getPath(name, it))) } diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt index c3409e4e2..49ee5d445 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryContentLifecycleTest.kt @@ -75,7 +75,6 @@ class LibraryContentLifecycleTest( @Autowired private val seriesDtoRepository: SeriesDtoRepository, @Autowired private val thumbnailBookRepository: ThumbnailBookRepository, ) { - @MockkBean private lateinit var mockScanner: FileSystemScanner @@ -515,10 +514,11 @@ class LibraryContentLifecycleTest( val library = makeLibrary().copy(emptyTrashAfterScan = true) libraryRepository.insert(library) - every { mockScanner.scanRootFolder(any()) } returns mapOf( - makeSeries(name = "series") to listOf(makeBook("book1"), makeBook("book3")), - makeSeries(name = "series2") to listOf(makeBook("book2")), - ).toScanResult() andThenThrows DirectoryNotFoundException("") + every { mockScanner.scanRootFolder(any()) } returns + mapOf( + makeSeries(name = "series") to listOf(makeBook("book1"), makeBook("book3")), + makeSeries(name = "series2") to listOf(makeBook("book2")), + ).toScanResult() andThenThrows DirectoryNotFoundException("") libraryContentLifecycle.scanRootFolder(library) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt index b8414e44d..8c5b3d0c7 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/LibraryLifecycleTest.kt @@ -24,7 +24,6 @@ class LibraryLifecycleTest( @Autowired private val libraryRepository: LibraryRepository, @Autowired private val libraryLifecycle: LibraryLifecycle, ) { - @AfterEach fun `clear repositories`() { libraryRepository.deleteAll() @@ -44,45 +43,53 @@ class LibraryLifecycleTest( @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) - .also { it.toFile().deleteOnExit() } - .toUri().toURL(), - ), - ) - } + val thrown = + catchThrowable { + libraryLifecycle.addLibrary( + Library( + "test", + Files.createTempFile(null, null) + .also { it.toFile().deleteOnExit() } + .toUri().toURL(), + ), + ) + } // then assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java) } @Test - fun `given existing library when adding library with same name then exception is thrown`(@TempDir path1: Path, @TempDir path2: Path) { + fun `given existing library when adding library with same name then exception is thrown`( + @TempDir path1: Path, + @TempDir path2: Path, + ) { // given libraryLifecycle.addLibrary(Library("test", path1.toUri().toURL())) // when - val thrown = catchThrowable { - libraryLifecycle.addLibrary(Library("test", path2.toUri().toURL())) - } + val thrown = + catchThrowable { + libraryLifecycle.addLibrary(Library("test", path2.toUri().toURL())) + } // 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`(@TempDir parent: Path) { + fun `given existing library when adding library with root folder as child of existing library then exception is thrown`( + @TempDir parent: Path, + ) { // given libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) // when val child = Files.createTempDirectory(parent, "") - val thrown = catchThrowable { - libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) - } + val thrown = + catchThrowable { + libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) + } // then assertThat(thrown) @@ -91,15 +98,18 @@ class LibraryLifecycleTest( } @Test - fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`(@TempDir parent: Path) { + fun `given existing library when adding library with root folder as parent of existing library then exception is thrown`( + @TempDir parent: Path, + ) { // given val child = Files.createTempDirectory(parent, null) libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) // when - val thrown = catchThrowable { - libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) - } + val thrown = + catchThrowable { + libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) + } // then assertThat(thrown) @@ -114,7 +124,9 @@ class LibraryLifecycleTest( private lateinit var library: Library @BeforeAll - fun setup(@TempDir root: Path) { + fun setup( + @TempDir root: Path, + ) { rootFolder = root library = Library("Existing", rootFolder.toUri().toURL()) } @@ -138,14 +150,17 @@ class LibraryLifecycleTest( val existing = libraryLifecycle.addLibrary(library) // when - val toUpdate = existing.copy( - name = "test", - root = Files.createTempFile(null, null) - .also { it.toFile().deleteOnExit() }.toUri().toURL(), - ) - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(toUpdate) - } + val toUpdate = + existing.copy( + name = "test", + root = + Files.createTempFile(null, null) + .also { it.toFile().deleteOnExit() }.toUri().toURL(), + ) + val thrown = + catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } // then assertThat(thrown).isInstanceOf(DirectoryNotFoundException::class.java) @@ -157,25 +172,30 @@ class LibraryLifecycleTest( val existing = libraryLifecycle.addLibrary(library) // when - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(existing) - } + 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`(@TempDir path1: Path, @TempDir path2: Path) { + fun `given existing library when updating library with same name then exception is thrown`( + @TempDir path1: Path, + @TempDir path2: Path, + ) { // given libraryLifecycle.addLibrary(Library("test", path1.toUri().toURL())) val existing = libraryLifecycle.addLibrary(library) // when val toUpdate = existing.copy(name = "test", root = path2.toUri().toURL()) - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(toUpdate) - } + val thrown = + catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } // then assertThat(thrown).isInstanceOf(DuplicateNameException::class.java) @@ -189,16 +209,19 @@ class LibraryLifecycleTest( // when val child = Files.createTempDirectory(rootFolder, "") val toUpdate = existing.copy(root = child.toUri().toURL()) - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(toUpdate) - } + 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`(@TempDir parent: Path) { + fun `given existing library when updating library with root folder as child of existing library then exception is thrown`( + @TempDir parent: Path, + ) { // given libraryLifecycle.addLibrary(Library("parent", parent.toUri().toURL())) val existing = libraryLifecycle.addLibrary(library) @@ -206,9 +229,10 @@ class LibraryLifecycleTest( // when val child = Files.createTempDirectory(parent, "") val toUpdate = existing.copy(root = child.toUri().toURL()) - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(toUpdate) - } + val thrown = + catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } // then assertThat(thrown) @@ -217,23 +241,28 @@ class LibraryLifecycleTest( } @Test - fun `given single existing library when updating library with root folder as parent of existing library then no exception is thrown`(@TempDir parent: Path) { + fun `given single existing library when updating library with root folder as parent of existing library then no exception is thrown`( + @TempDir parent: Path, + ) { // given 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) - } + 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`(@TempDir parent: Path) { + fun `given existing library when updating library with root folder as parent of existing library then exception is thrown`( + @TempDir parent: Path, + ) { // given val child = Files.createTempDirectory(parent, null) libraryLifecycle.addLibrary(Library("child", child.toUri().toURL())) @@ -241,9 +270,10 @@ class LibraryLifecycleTest( // when val toUpdate = existing.copy(root = parent.toUri().toURL()) - val thrown = catchThrowable { - libraryLifecycle.updateLibrary(toUpdate) - } + val thrown = + catchThrowable { + libraryLifecycle.updateLibrary(toUpdate) + } // then assertThat(thrown) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataAggregatorTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataAggregatorTest.kt index b09d77b30..a47c9fb2a 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataAggregatorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataAggregatorTest.kt @@ -11,10 +11,11 @@ class MetadataAggregatorTest { @Test fun `given metadatas when aggregating then aggregation is relevant`() { - val metadatas = listOf( - BookMetadata(title = "ignored", summary = "summary 1", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), releaseDate = LocalDate.of(2020, 1, 1), tags = setOf("tag1")), - BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F, authors = listOf(Author("author3", "role3"), Author("author2", "role3")), releaseDate = LocalDate.of(2021, 1, 1), tags = setOf("tag2")), - ) + val metadatas = + listOf( + BookMetadata(title = "ignored", summary = "summary 1", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), releaseDate = LocalDate.of(2020, 1, 1), tags = setOf("tag1")), + BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F, authors = listOf(Author("author3", "role3"), Author("author2", "role3")), releaseDate = LocalDate.of(2021, 1, 1), tags = setOf("tag2")), + ) val aggregation = aggregator.aggregate(metadatas) @@ -27,10 +28,11 @@ class MetadataAggregatorTest { @Test fun `given metadatas with summary only on second book when aggregating then aggregation has second book's summary`() { - val metadatas = listOf( - BookMetadata(title = "ignored", number = "1", numberSort = 1F), - BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F), - ) + val metadatas = + listOf( + BookMetadata(title = "ignored", number = "1", numberSort = 1F), + BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F), + ) val aggregation = aggregator.aggregate(metadatas) @@ -40,10 +42,11 @@ class MetadataAggregatorTest { @Test fun `given metadatas with second book with earlier release date when aggregating then aggregation has release date from second book`() { - val metadatas = listOf( - BookMetadata(title = "ignored", number = "1", numberSort = 1F, releaseDate = LocalDate.of(2020, 1, 1)), - BookMetadata(title = "ignored", number = "2", numberSort = 2F, releaseDate = LocalDate.of(2019, 1, 1)), - ) + val metadatas = + listOf( + BookMetadata(title = "ignored", number = "1", numberSort = 1F, releaseDate = LocalDate.of(2020, 1, 1)), + BookMetadata(title = "ignored", number = "2", numberSort = 2F, releaseDate = LocalDate.of(2019, 1, 1)), + ) val aggregation = aggregator.aggregate(metadatas) @@ -52,10 +55,11 @@ class MetadataAggregatorTest { @Test fun `given metadatas with duplicate authors or tags when aggregating then aggregation has no duplicates`() { - val metadatas = listOf( - BookMetadata(title = "ignored", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1", "tag2")), - BookMetadata(title = "ignored", number = "2", numberSort = 2F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1")), - ) + val metadatas = + listOf( + BookMetadata(title = "ignored", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1", "tag2")), + BookMetadata(title = "ignored", number = "2", numberSort = 2F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1")), + ) val aggregation = aggregator.aggregate(metadatas) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataApplierTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataApplierTest.kt index f83e652f6..30dbcf7b0 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataApplierTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/MetadataApplierTest.kt @@ -13,40 +13,40 @@ import java.net.URI import java.time.LocalDate class MetadataApplierTest { - private val metadataApplier = MetadataApplier() @Nested inner class Book { - @Test fun `given locked metadata when applying patch then metadata is not changed`() { - val metadata = BookMetadata( - title = "title", - number = "1", - numberSort = 1F, - titleLock = true, - summaryLock = true, - numberLock = true, - numberSortLock = true, - releaseDateLock = true, - authorsLock = true, - tagsLock = true, - isbnLock = true, - linksLock = true, - ) + val metadata = + BookMetadata( + title = "title", + number = "1", + numberSort = 1F, + titleLock = true, + summaryLock = true, + numberLock = true, + numberSortLock = true, + releaseDateLock = true, + authorsLock = true, + tagsLock = true, + isbnLock = true, + linksLock = true, + ) - val patch = BookMetadataPatch( - title = "new title", - summary = "new summary", - number = "2", - numberSort = 2F, - releaseDate = LocalDate.of(2020, 12, 2), - authors = listOf(Author("Marcel", "writer")), - isbn = "9782811632397", - links = listOf(WebLink("Comixology", URI("https://www.comixology.com/Sandman/digital-comic/727888"))), - tags = setOf("tag1", "tag2"), - ) + val patch = + BookMetadataPatch( + title = "new title", + summary = "new summary", + number = "2", + numberSort = 2F, + releaseDate = LocalDate.of(2020, 12, 2), + authors = listOf(Author("Marcel", "writer")), + isbn = "9782811632397", + links = listOf(WebLink("Comixology", URI("https://www.comixology.com/Sandman/digital-comic/727888"))), + tags = setOf("tag1", "tag2"), + ) val patched = metadataApplier.apply(patch, metadata) @@ -64,23 +64,25 @@ class MetadataApplierTest { @Test fun `given unlocked metadata when applying patch then metadata is changed`() { - val metadata = BookMetadata( - title = "title", - number = "1", - numberSort = 1F, - ) + val metadata = + BookMetadata( + title = "title", + number = "1", + numberSort = 1F, + ) - val patch = BookMetadataPatch( - title = "new title", - summary = "new summary", - number = "2", - numberSort = 2F, - releaseDate = LocalDate.of(2020, 12, 2), - authors = listOf(Author("Marcel", "writer")), - isbn = "9782811632397", - links = listOf(WebLink("Comixology", URI("https://www.comixology.com/Sandman/digital-comic/727888"))), - tags = setOf("tag1", "tag2"), - ) + val patch = + BookMetadataPatch( + title = "new title", + summary = "new summary", + number = "2", + numberSort = 2F, + releaseDate = LocalDate.of(2020, 12, 2), + authors = listOf(Author("Marcel", "writer")), + isbn = "9782811632397", + links = listOf(WebLink("Comixology", URI("https://www.comixology.com/Sandman/digital-comic/727888"))), + tags = setOf("tag1", "tag2"), + ) val patched = metadataApplier.apply(patch, metadata) @@ -108,37 +110,38 @@ class MetadataApplierTest { @Nested inner class Series { - @Test fun `given locked metadata when applying patch then metadata is not changed`() { - val metadata = SeriesMetadata( - title = "title", - statusLock = true, - titleLock = true, - titleSortLock = true, - summaryLock = true, - readingDirectionLock = true, - publisherLock = true, - ageRatingLock = true, - languageLock = true, - genresLock = true, - tagsLock = true, - totalBookCountLock = true, - ) + val metadata = + SeriesMetadata( + title = "title", + statusLock = true, + titleLock = true, + titleSortLock = true, + summaryLock = true, + readingDirectionLock = true, + publisherLock = true, + ageRatingLock = true, + languageLock = true, + genresLock = true, + tagsLock = true, + totalBookCountLock = true, + ) - val patch = SeriesMetadataPatch( - title = "new title", - titleSort = "new title sort", - status = SeriesMetadata.Status.ENDED, - summary = "new summary", - readingDirection = SeriesMetadata.ReadingDirection.VERTICAL, - publisher = "new publisher", - ageRating = 12, - language = "en", - genres = setOf("shonen"), - totalBookCount = 12, - collections = emptySet(), - ) + val patch = + SeriesMetadataPatch( + title = "new title", + titleSort = "new title sort", + status = SeriesMetadata.Status.ENDED, + summary = "new summary", + readingDirection = SeriesMetadata.ReadingDirection.VERTICAL, + publisher = "new publisher", + ageRating = 12, + language = "en", + genres = setOf("shonen"), + totalBookCount = 12, + collections = emptySet(), + ) val patched = metadataApplier.apply(patch, metadata) @@ -157,23 +160,25 @@ class MetadataApplierTest { @Test fun `given unlocked metadata when applying patch then metadata is changed`() { - val metadata = SeriesMetadata( - title = "title", - ) + val metadata = + SeriesMetadata( + title = "title", + ) - val patch = SeriesMetadataPatch( - title = "new title", - titleSort = "new title sort", - status = SeriesMetadata.Status.ENDED, - summary = "new summary", - readingDirection = SeriesMetadata.ReadingDirection.VERTICAL, - publisher = "new publisher", - ageRating = 12, - language = "en", - genres = setOf("shonen"), - totalBookCount = 12, - collections = emptySet(), - ) + val patch = + SeriesMetadataPatch( + title = "new title", + titleSort = "new title sort", + status = SeriesMetadata.Status.ENDED, + summary = "new summary", + readingDirection = SeriesMetadata.ReadingDirection.VERTICAL, + publisher = "new publisher", + ageRating = 12, + language = "en", + genres = setOf("shonen"), + totalBookCount = 12, + collections = emptySet(), + ) val patched = metadataApplier.apply(patch, metadata) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/ReadListMatcherTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/ReadListMatcherTest.kt index 9e2ad3024..caa072cf2 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/ReadListMatcherTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/ReadListMatcherTest.kt @@ -39,7 +39,6 @@ class ReadListMatcherTest( @Autowired private val bookMetadataRepository: BookMetadataRepository, @Autowired private val readListMatcher: ReadListMatcher, ) { - private val library = makeLibrary() @MockkBean @@ -68,51 +67,58 @@ class ReadListMatcherTest( @Nested inner class Match { - private fun Collection.mapIds() = map { - it.matches - .mapKeys { (series, _) -> series.id } - .mapValues { (_, books) -> books.map { book -> book.id } } - } + private fun Collection.mapIds() = + map { + it.matches + .mapKeys { (series, _) -> series.id } + .mapValues { (_, books) -> books.map { book -> book.id } } + } @Test fun `given request with existing series and books when matching then all requests are matched with a single result`() { // given - val booksSeries1 = listOf( - makeBook("book1", libraryId = library.id), - makeBook("book5", libraryId = library.id), - ) - val series1 = makeSeries(name = "batman", libraryId = library.id).also { 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 booksSeries1 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book5", libraryId = library.id), + ) + val series1 = + makeSeries(name = "batman", libraryId = library.id).also { 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), - ) - val series2 = makeSeries(name = "joker", libraryId = library.id).also { s -> - seriesLifecycle.createSeries(s) - seriesLifecycle.addBooks(s, booksSeries2) - seriesLifecycle.sortBooks(s) + val booksSeries2 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book2", libraryId = library.id), + ) + val series2 = + makeSeries(name = "joker", libraryId = library.id).also { s -> + seriesLifecycle.createSeries(s) + seriesLifecycle.addBooks(s, booksSeries2) + seriesLifecycle.sortBooks(s) - bookMetadataRepository.findById(booksSeries2[0].id).let { - bookMetadataRepository.update(it.copy(number = "0025")) + bookMetadataRepository.findById(booksSeries2[0].id).let { + bookMetadataRepository.update(it.copy(number = "0025")) + } } - } - val request = ReadListRequest( - name = "readlist request", - books = listOf( - ReadListRequestBook(series = setOf("Batman: White Knight"), number = "1"), - ReadListRequestBook(series = setOf("joker"), number = "02"), - ReadListRequestBook(series = setOf("Batman: White Knight"), number = "2"), - ReadListRequestBook(series = setOf("joker"), number = "25"), - ), - ) + val request = + ReadListRequest( + name = "readlist request", + books = + listOf( + ReadListRequestBook(series = setOf("Batman: White Knight"), number = "1"), + ReadListRequestBook(series = setOf("joker"), number = "02"), + ReadListRequestBook(series = setOf("Batman: White Knight"), number = "2"), + ReadListRequestBook(series = setOf("joker"), number = "25"), + ), + ) // when val result = readListMatcher.matchReadListRequest(request) @@ -139,46 +145,52 @@ class ReadListMatcherTest( @Test fun `given request with existing read list when matching then result has no readlist name and appropriate error code but correct matches`() { // given - val booksSeries1 = listOf( - makeBook("book1", libraryId = library.id), - makeBook("book5", libraryId = library.id), - ) - val series1 = makeSeries(name = "batman", libraryId = library.id).also { 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 booksSeries1 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book5", libraryId = library.id), + ) + val series1 = + makeSeries(name = "batman", libraryId = library.id).also { 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), - ) - val series2 = makeSeries(name = "joker", libraryId = library.id).also { s -> - seriesLifecycle.createSeries(s) - seriesLifecycle.addBooks(s, booksSeries2) - seriesLifecycle.sortBooks(s) + val booksSeries2 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book2", libraryId = library.id), + ) + val series2 = + makeSeries(name = "joker", libraryId = library.id).also { s -> + seriesLifecycle.createSeries(s) + seriesLifecycle.addBooks(s, booksSeries2) + seriesLifecycle.sortBooks(s) - bookMetadataRepository.findById(booksSeries2[0].id).let { - bookMetadataRepository.update(it.copy(number = "0025")) + bookMetadataRepository.findById(booksSeries2[0].id).let { + bookMetadataRepository.update(it.copy(number = "0025")) + } } - } readListLifecycle.addReadList( ReadList(name = "my ReadList"), ) - val request = ReadListRequest( - name = "my readlist", - books = listOf( - ReadListRequestBook(series = setOf("batman: white knight"), number = "1"), - ReadListRequestBook(series = setOf("joker"), number = "2"), - ReadListRequestBook(series = setOf("BATMAN: WHITE KNIGHT"), number = "2"), - ReadListRequestBook(series = setOf("joker"), number = "25"), - ), - ) + val request = + ReadListRequest( + name = "my readlist", + books = + listOf( + ReadListRequestBook(series = setOf("batman: white knight"), number = "1"), + ReadListRequestBook(series = setOf("joker"), number = "2"), + ReadListRequestBook(series = setOf("BATMAN: WHITE KNIGHT"), number = "2"), + ReadListRequestBook(series = setOf("joker"), number = "25"), + ), + ) // when val result = readListMatcher.matchReadListRequest(request) @@ -205,42 +217,48 @@ class ReadListMatcherTest( @Test fun `given request and some matching series or books when matching then returns all matches`() { // given - val booksSeries1 = listOf( - makeBook("book1", libraryId = library.id), - makeBook("book5", libraryId = library.id), - ) - val series1 = makeSeries(name = "batman", libraryId = library.id).also { s -> - seriesLifecycle.createSeries(s) - seriesLifecycle.addBooks(s, booksSeries1) - seriesLifecycle.sortBooks(s) + val booksSeries1 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book5", libraryId = library.id), + ) + val series1 = + makeSeries(name = "batman", libraryId = library.id).also { s -> + seriesLifecycle.createSeries(s) + seriesLifecycle.addBooks(s, booksSeries1) + seriesLifecycle.sortBooks(s) - bookMetadataRepository.findById(booksSeries1[0].id).let { - bookMetadataRepository.update(it.copy(number = "2")) + 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), - ) - val series2 = makeSeries(name = "joker", libraryId = library.id).also { s -> - seriesLifecycle.createSeries(s) - seriesLifecycle.addBooks(s, booksSeries2) - seriesLifecycle.sortBooks(s) - } + val booksSeries2 = + listOf( + makeBook("book1", libraryId = library.id), + makeBook("book2", libraryId = library.id), + ) + val series2 = + makeSeries(name = "joker", libraryId = library.id).also { s -> + seriesLifecycle.createSeries(s) + seriesLifecycle.addBooks(s, booksSeries2) + seriesLifecycle.sortBooks(s) + } makeSeries(name = "joker", libraryId = library.id).also { s -> seriesLifecycle.createSeries(s) } - val request = ReadListRequest( - name = "readlist", - books = listOf( - ReadListRequestBook(series = setOf("tokyo ghost"), number = "1"), - ReadListRequestBook(series = setOf("batman"), number = "3"), - ReadListRequestBook(series = setOf("joker"), number = "2"), - ReadListRequestBook(series = setOf("batman"), number = "2"), - ), - ) + val request = + ReadListRequest( + name = "readlist", + books = + listOf( + ReadListRequestBook(series = setOf("tokyo ghost"), number = "1"), + ReadListRequestBook(series = setOf("batman"), number = "3"), + ReadListRequestBook(series = setOf("joker"), number = "2"), + ReadListRequestBook(series = setOf("batman"), number = "2"), + ), + ) // when val result = readListMatcher.matchReadListRequest(request) diff --git a/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt index f9128fb7b..3ab79aae9 100644 --- a/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/domain/service/SeriesLifecycleTest.kt @@ -44,7 +44,6 @@ class SeriesLifecycleTest( @Autowired private val thumbnailSeriesRepository: ThumbnailSeriesRepository, @Autowired private val thumbnailBookRepository: ThumbnailBookRepository, ) { - @SpykBean private lateinit var bookLifecycle: BookLifecycle @@ -80,17 +79,19 @@ class SeriesLifecycleTest( @Test fun `given series with unordered books when saving then books are ordered with natural sort`() { // given - val books = listOf( - makeBook("book 1", libraryId = library.id), - makeBook("boôk 05", libraryId = library.id), - makeBook(" book 3", libraryId = library.id), - makeBook("book 4 ", libraryId = library.id), - makeBook("book 6", libraryId = library.id), - makeBook("book 002", libraryId = library.id), - ) - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { - seriesLifecycle.createSeries(it) - } + val books = + listOf( + makeBook("book 1", libraryId = library.id), + makeBook("boôk 05", libraryId = library.id), + makeBook(" book 3", libraryId = library.id), + makeBook("book 4 ", libraryId = library.id), + makeBook("book 6", libraryId = library.id), + makeBook("book 002", libraryId = library.id), + ) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { + seriesLifecycle.createSeries(it) + } seriesLifecycle.addBooks(createdSeries, books) // when @@ -108,15 +109,17 @@ class SeriesLifecycleTest( @Test fun `given series when removing a book then remaining books are indexed in sequence`() { // given - val books = listOf( - makeBook("book 1", libraryId = library.id), - makeBook("book 2", libraryId = library.id), - makeBook("book 3", libraryId = library.id), - makeBook("book 4", libraryId = library.id), - ) - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { - seriesLifecycle.createSeries(it) - } + val books = + listOf( + makeBook("book 1", libraryId = library.id), + makeBook("book 2", libraryId = library.id), + makeBook("book 3", libraryId = library.id), + makeBook("book 4", libraryId = library.id), + ) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { + seriesLifecycle.createSeries(it) + } seriesLifecycle.addBooks(createdSeries, books) seriesLifecycle.sortBooks(createdSeries) @@ -137,15 +140,17 @@ class SeriesLifecycleTest( @Test fun `given series when adding a book then all books are indexed in sequence`() { // given - val books = listOf( - makeBook("book 1", libraryId = library.id), - makeBook("book 2", libraryId = library.id), - makeBook("book 4", libraryId = library.id), - makeBook("book 5", libraryId = library.id), - ) - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { - seriesLifecycle.createSeries(it) - } + val books = + listOf( + makeBook("book 1", libraryId = library.id), + makeBook("book 2", libraryId = library.id), + makeBook("book 4", libraryId = library.id), + makeBook("book 5", libraryId = library.id), + ) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { + seriesLifecycle.createSeries(it) + } seriesLifecycle.addBooks(createdSeries, books) seriesLifecycle.sortBooks(createdSeries) @@ -226,12 +231,14 @@ class SeriesLifecycleTest( @Test fun `given series when adding books and an exception occur while saving media then books are not saved`() { - val books = listOf( - makeBook("book 1", libraryId = library.id), - ) - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { - seriesLifecycle.createSeries(it) - } + val books = + listOf( + makeBook("book 1", libraryId = library.id), + ) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { + seriesLifecycle.createSeries(it) + } every { mediaRepository.insert(any>()) } throws DataAccessException("") @@ -247,12 +254,14 @@ class SeriesLifecycleTest( @Test fun `given series when adding books and an exception occur while saving metadata then books are not saved`() { - val books = listOf( - makeBook("book 1", libraryId = library.id), - ) - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { - seriesLifecycle.createSeries(it) - } + val books = + listOf( + makeBook("book 1", libraryId = library.id), + ) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { + seriesLifecycle.createSeries(it) + } every { bookMetadataRepository.insert(any>()) } throws DataAccessException("") @@ -292,10 +301,11 @@ class SeriesLifecycleTest( Files.createFile(bookSidecarPath) val series = makeSeries(name = "series", libraryId = library.id, url = seriesPath.toUri().toURL()) - val books = listOf( - makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), - makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), - ) + val books = + listOf( + makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), + makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), + ) val bookSidecar = ThumbnailBook(bookId = books[0].id, type = ThumbnailBook.Type.SIDECAR, url = bookSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) seriesLifecycle.createSeries(series) @@ -328,10 +338,11 @@ class SeriesLifecycleTest( Files.createFile(seriesSidecarPath) val series = makeSeries(name = "series", libraryId = library.id, url = seriesPath.toUri().toURL()) - val books = listOf( - makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), - makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), - ) + val books = + listOf( + makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), + makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), + ) val bookSidecar = ThumbnailBook(bookId = books[0].id, type = ThumbnailBook.Type.SIDECAR, url = bookSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) val seriesSidecar = ThumbnailSeries(seriesId = series.id, type = ThumbnailSeries.Type.SIDECAR, url = seriesSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) @@ -368,10 +379,11 @@ class SeriesLifecycleTest( Files.createFile(seriesSidecarPath) val series = makeSeries(name = "series", libraryId = library.id, url = seriesPath.toUri().toURL()) - val books = listOf( - makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), - makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), - ) + val books = + listOf( + makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), + makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), + ) val bookSidecar = ThumbnailBook(bookId = books[0].id, type = ThumbnailBook.Type.SIDECAR, url = bookSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) val seriesSidecar = ThumbnailSeries(seriesId = series.id, type = ThumbnailSeries.Type.SIDECAR, url = seriesSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) @@ -423,10 +435,11 @@ class SeriesLifecycleTest( val seriesSidecarPath = seriesPath.resolve("cover.png") val series = makeSeries(name = "series", libraryId = library.id, url = seriesPath.toUri().toURL()) - val books = listOf( - makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), - makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), - ) + val books = + listOf( + makeBook("1", libraryId = library.id, url = book1Path.toUri().toURL()), + makeBook("2", libraryId = library.id, url = book2Path.toUri().toURL()), + ) val bookSidecar = ThumbnailBook(bookId = books[0].id, type = ThumbnailBook.Type.SIDECAR, url = bookSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) val seriesSidecar = ThumbnailSeries(seriesId = series.id, type = ThumbnailSeries.Type.SIDECAR, url = seriesSidecarPath.toUri().toURL(), fileSize = 0, mediaType = "", dimension = Dimension(0, 0)) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDaoTest.kt index 4b8f22080..394ad44c5 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDaoTest.kt @@ -24,7 +24,6 @@ class BookDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() private val series = makeSeries("Series") @@ -49,16 +48,17 @@ class BookDaoTest( @Test fun `given a book when inserting then it is persisted`() { val now = LocalDateTime.now() - val book = Book( - name = "Book", - url = URL("file://book"), - fileLastModified = now, - fileSize = 3, - fileHash = "abc", - seriesId = series.id, - libraryId = library.id, - deletedDate = LocalDateTime.now(), - ) + val book = + Book( + name = "Book", + url = URL("file://book"), + fileLastModified = now, + fileSize = 3, + fileHash = "abc", + seriesId = series.id, + libraryId = library.id, + deletedDate = LocalDateTime.now(), + ) bookDao.insert(book) val created = bookDao.findByIdOrNull(book.id)!! @@ -76,28 +76,30 @@ class BookDaoTest( @Test fun `given existing book when updating then it is persisted`() { - val book = Book( - name = "Book", - url = URL("file://book"), - fileLastModified = LocalDateTime.now(), - fileSize = 3, - seriesId = series.id, - libraryId = library.id, - ) + val book = + Book( + name = "Book", + url = URL("file://book"), + fileLastModified = LocalDateTime.now(), + fileSize = 3, + seriesId = series.id, + libraryId = library.id, + ) bookDao.insert(book) val modificationDate = LocalDateTime.now() - val updated = with(bookDao.findByIdOrNull(book.id)!!) { - copy( - name = "Updated", - url = URL("file://updated"), - fileLastModified = modificationDate, - fileSize = 5, - fileHash = "def", - deletedDate = LocalDateTime.now(), - ) - } + val updated = + with(bookDao.findByIdOrNull(book.id)!!) { + copy( + name = "Updated", + url = URL("file://updated"), + fileLastModified = modificationDate, + fileSize = 5, + fileHash = "def", + deletedDate = LocalDateTime.now(), + ) + } bookDao.update(updated) val modified = bookDao.findByIdOrNull(updated.id)!! @@ -117,14 +119,15 @@ class BookDaoTest( @Test fun `given existing book when finding by id then book is returned`() { - val book = Book( - name = "Book", - url = URL("file://book"), - fileLastModified = LocalDateTime.now(), - fileSize = 3, - seriesId = series.id, - libraryId = library.id, - ) + val book = + Book( + name = "Book", + url = URL("file://book"), + fileLastModified = LocalDateTime.now(), + fileSize = 3, + seriesId = series.id, + libraryId = library.id, + ) bookDao.insert(book) val found = bookDao.findByIdOrNull(book.id) @@ -155,10 +158,11 @@ class BookDaoTest( bookDao.insert(makeBook("1", libraryId = library.id, seriesId = series.id)) bookDao.insert(makeBook("2", libraryId = library.id, seriesId = series.id)) - val search = BookSearch( - libraryIds = listOf(library.id), - seriesIds = listOf(series.id), - ) + val search = + BookSearch( + libraryIds = listOf(library.id), + seriesIds = listOf(series.id), + ) val found = bookDao.findAll(search) assertThat(found).hasSize(2) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDaoTest.kt index 926a4964a..626b20790 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookDtoDaoTest.kt @@ -58,7 +58,6 @@ class BookDtoDaoTest( @Autowired private val userLifecycle: KomgaUserLifecycle, @Autowired private val searchIndexLifecycle: SearchIndexLifecycle, ) { - private val library = makeLibrary() private var series = makeSeries("Series") private val user = KomgaUser("user@example.org", "", false) @@ -132,11 +131,12 @@ class BookDtoDaoTest( } // when - val page = bookDtoDao.findAll( - BookSearchWithReadProgress(tags = setOf("tag1", "tag2")), - user.id, - Pageable.unpaged(), - ) + val page = + bookDtoDao.findAll( + BookSearchWithReadProgress(tags = setOf("tag1", "tag2")), + user.id, + Pageable.unpaged(), + ) // then assertThat(page.totalElements).isEqualTo(2) @@ -165,11 +165,12 @@ class BookDtoDaoTest( } // when - val page = bookDtoDao.findAll( - BookSearchWithReadProgress(authors = listOf(Author("Mark", "writer"), Author("Jim", "inker"))), - user.id, - Pageable.unpaged(), - ) + val page = + bookDtoDao.findAll( + BookSearchWithReadProgress(authors = listOf(Author("Mark", "writer"), Author("Jim", "inker"))), + user.id, + Pageable.unpaged(), + ) // then assertThat(page.totalElements).isEqualTo(2) @@ -186,11 +187,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(1) @@ -204,11 +206,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(1) @@ -222,11 +225,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(1) @@ -240,11 +244,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.UNREAD)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.UNREAD)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(2) @@ -257,11 +262,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(2) @@ -274,11 +280,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(2) @@ -291,11 +298,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS, ReadStatus.READ)), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS, ReadStatus.READ)), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(3) @@ -308,11 +316,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(), - user.id, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(), + user.id, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(3) @@ -328,11 +337,12 @@ class BookDtoDaoTest( setupBooks() // when - val found = bookDtoDao.findAllOnDeck( - user.id, - null, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAllOnDeck( + user.id, + null, + PageRequest.of(0, 20), + ) // then assertThat(found).isEmpty() @@ -349,11 +359,12 @@ class BookDtoDaoTest( ) // when - val found = bookDtoDao.findAllOnDeck( - user.id, - null, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAllOnDeck( + user.id, + null, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(0) @@ -373,11 +384,12 @@ class BookDtoDaoTest( books.elementAt(0).let { readProgressRepository.save(ReadProgress(it.id, user.id, 5, true)) } // when - val found = bookDtoDao.findAllOnDeck( - user.id, - null, - PageRequest.of(0, 20), - ) + val found = + bookDtoDao.findAllOnDeck( + user.id, + null, + PageRequest.of(0, 20), + ) // then assertThat(found).hasSize(1) @@ -404,11 +416,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "batman"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "batman"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(3) @@ -431,28 +444,32 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "book"), - user.id, - UnpagedSorted(Sort.by("name")), - ).content - val pages = (0..2).map { + val found = bookDtoDao.findAll( BookSearchWithReadProgress(searchTerm = "book"), user.id, - PageRequest.of(it, 1, Sort.by("name")), + UnpagedSorted(Sort.by("name")), + ).content + val pages = + (0..2).map { + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "book"), + user.id, + PageRequest.of(it, 1, Sort.by("name")), + ) + } + val page0 = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "book"), + user.id, + PageRequest.of(0, 2, Sort.by("name")), + ) + val page1 = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "book"), + user.id, + PageRequest.of(1, 2, Sort.by("name")), ) - } - val page0 = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "book"), - user.id, - PageRequest.of(0, 2, Sort.by("name")), - ) - val page1 = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "book"), - user.id, - PageRequest.of(1, 2, Sort.by("name")), - ) // then assertThat(found).hasSize(3) @@ -492,11 +509,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "eric"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "eric"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -525,11 +543,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "9782413016878"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "9782413016878"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -555,11 +574,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "tag:tag1"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "tag:tag1"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -585,21 +605,24 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val foundGeneric = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "author:bob"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content - val foundByRole = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "writer:bob"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content - val notFound = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "penciller:bob"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundGeneric = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "author:bob"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content + val foundByRole = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "writer:bob"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content + val notFound = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "penciller:bob"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(foundGeneric).hasSize(1) @@ -623,11 +646,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "release_date:1999"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "release_date:1999"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -652,11 +676,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "release_date:[1990 TO 2010]"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "release_date:[1990 TO 2010]"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(2) @@ -677,11 +702,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "status:error"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "status:error"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -691,8 +717,9 @@ class BookDtoDaoTest( @Test fun `given books when searching by deleted then results are matched`() { // given - val book1 = makeBook("Éric le rouge", seriesId = series.id, libraryId = library.id) - .copy(deletedDate = LocalDateTime.now()) + val book1 = + makeBook("Éric le rouge", seriesId = series.id, libraryId = library.id) + .copy(deletedDate = LocalDateTime.now()) seriesLifecycle.addBooks( series, listOf( @@ -705,11 +732,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "deleted:true"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "deleted:true"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -731,11 +759,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "s.w.o.r.d."), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "s.w.o.r.d."), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -759,11 +788,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "batman robin"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "batman robin"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(2) @@ -786,11 +816,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "x-men"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "x-men"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(2) @@ -801,11 +832,12 @@ class BookDtoDaoTest( fun `when searching by unknown field then empty result are returned and no exception is thrown`() { assertThatCode { // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "publisher:batman"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "publisher:batman"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(0) @@ -826,11 +858,12 @@ class BookDtoDaoTest( Thread.sleep(100) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = bookDtoDao.findAll( - BookSearchWithReadProgress(searchTerm = "不道德"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + bookDtoDao.findAll( + BookSearchWithReadProgress(searchTerm = "不道德"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDaoTest.kt index 8bdd10d3f..8f95f549c 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataAggregationDaoTest.kt @@ -24,7 +24,6 @@ class BookMetadataAggregationDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() @BeforeAll @@ -50,14 +49,15 @@ class BookMetadataAggregationDaoTest( val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val now = LocalDateTime.now() - val metadata = BookMetadataAggregation( - authors = listOf(Author("author", "role")), - tags = setOf("tag1", "tag2"), - releaseDate = LocalDate.now(), - summary = "Summary", - summaryNumber = "1", - seriesId = series.id, - ) + val metadata = + BookMetadataAggregation( + authors = listOf(Author("author", "role")), + tags = setOf("tag1", "tag2"), + releaseDate = LocalDate.now(), + summary = "Summary", + summaryNumber = "1", + seriesId = series.id, + ) bookMetadataAggregationDao.insert(metadata) val created = bookMetadataAggregationDao.findById(metadata.seriesId) @@ -81,9 +81,10 @@ class BookMetadataAggregationDaoTest( val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val now = LocalDateTime.now() - val metadata = BookMetadataAggregation( - seriesId = series.id, - ) + val metadata = + BookMetadataAggregation( + seriesId = series.id, + ) bookMetadataAggregationDao.insert(metadata) val created = bookMetadataAggregationDao.findById(metadata.seriesId) @@ -102,13 +103,14 @@ class BookMetadataAggregationDaoTest( fun `given existing bookMetadataAggregation when finding by id then metadata is returned`() { val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } - val metadata = BookMetadataAggregation( - authors = listOf(Author("author", "role")), - tags = setOf("tag1", "tag2"), - releaseDate = LocalDate.now(), - summary = "Summary", - seriesId = series.id, - ) + val metadata = + BookMetadataAggregation( + authors = listOf(Author("author", "role")), + tags = setOf("tag1", "tag2"), + releaseDate = LocalDate.now(), + summary = "Summary", + seriesId = series.id, + ) bookMetadataAggregationDao.insert(metadata) @@ -136,28 +138,30 @@ class BookMetadataAggregationDaoTest( fun `given a bookMetadataAggregation when updating then it is persisted`() { val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } - val metadata = BookMetadataAggregation( - authors = listOf(Author("author", "role")), - tags = setOf("tag1", "tag2"), - releaseDate = LocalDate.now(), - summary = "Summary", - summaryNumber = "1", - seriesId = series.id, - ) + val metadata = + BookMetadataAggregation( + authors = listOf(Author("author", "role")), + tags = setOf("tag1", "tag2"), + releaseDate = LocalDate.now(), + summary = "Summary", + summaryNumber = "1", + seriesId = series.id, + ) bookMetadataAggregationDao.insert(metadata) val created = bookMetadataAggregationDao.findById(metadata.seriesId) val modificationDate = LocalDateTime.now() - val updated = with(created) { - copy( - releaseDate = LocalDate.now().plusYears(1), - summary = "SummaryUpdated", - summaryNumber = "2", - authors = listOf(Author("authorUpdated", "roleUpdated"), Author("author2", "role2")), - tags = setOf("tag1", "tag2updated"), - ) - } + val updated = + with(created) { + copy( + releaseDate = LocalDate.now().plusYears(1), + summary = "SummaryUpdated", + summaryNumber = "2", + authors = listOf(Author("authorUpdated", "roleUpdated"), Author("author2", "role2")), + tags = setOf("tag1", "tag2updated"), + ) + } bookMetadataAggregationDao.update(updated) val modified = bookMetadataAggregationDao.findById(updated.seriesId) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDaoTest.kt index 415df95b9..f6441a6fc 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/BookMetadataDaoTest.kt @@ -59,26 +59,27 @@ class BookMetadataDaoTest( @Test fun `given a metadata when inserting then it is persisted`() { val now = LocalDateTime.now() - val metadata = BookMetadata( - title = "Book", - summary = "Summary", - number = "1", - numberSort = 1F, - releaseDate = LocalDate.now(), - authors = listOf(Author("author", "role")), - tags = setOf("tag", "another"), - isbn = "987654321", - links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange-30-a-gathering-of-fear/4000-18731/"))), - bookId = book.id, - titleLock = true, - summaryLock = true, - numberLock = true, - numberSortLock = true, - releaseDateLock = true, - authorsLock = true, - tagsLock = true, - isbnLock = true, - ) + val metadata = + BookMetadata( + title = "Book", + summary = "Summary", + number = "1", + numberSort = 1F, + releaseDate = LocalDate.now(), + authors = listOf(Author("author", "role")), + tags = setOf("tag", "another"), + isbn = "987654321", + links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange-30-a-gathering-of-fear/4000-18731/"))), + bookId = book.id, + titleLock = true, + summaryLock = true, + numberLock = true, + numberSortLock = true, + releaseDateLock = true, + authorsLock = true, + tagsLock = true, + isbnLock = true, + ) bookMetadataDao.insert(metadata) val created = bookMetadataDao.findById(metadata.bookId) @@ -117,12 +118,13 @@ class BookMetadataDaoTest( @Test fun `given a minimum metadata when inserting then it is persisted`() { - val metadata = BookMetadata( - title = "Book", - number = "1", - numberSort = 1F, - bookId = book.id, - ) + val metadata = + BookMetadata( + title = "Book", + number = "1", + numberSort = 1F, + bookId = book.id, + ) bookMetadataDao.insert(metadata) val created = bookMetadataDao.findById(metadata.bookId) @@ -152,42 +154,44 @@ class BookMetadataDaoTest( @Test fun `given existing metadata when updating then it is persisted`() { - val metadata = BookMetadata( - title = "Book", - summary = "Summary", - number = "1", - numberSort = 1F, - releaseDate = LocalDate.now(), - authors = listOf(Author("author", "role")), - tags = setOf("tag"), - links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange-30-a-gathering-of-fear/4000-18731/"))), - bookId = book.id, - ) + val metadata = + BookMetadata( + title = "Book", + summary = "Summary", + number = "1", + numberSort = 1F, + releaseDate = LocalDate.now(), + authors = listOf(Author("author", "role")), + tags = setOf("tag"), + links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange-30-a-gathering-of-fear/4000-18731/"))), + bookId = book.id, + ) bookMetadataDao.insert(metadata) val modificationDate = LocalDateTime.now() - val updated = with(bookMetadataDao.findById(metadata.bookId)) { - copy( - title = "BookUpdated", - summary = "SummaryUpdated", - number = "2", - numberSort = 2F, - releaseDate = LocalDate.now(), - authors = listOf(Author("author2", "role2")), - tags = setOf("another"), - isbn = "987654321", - links = listOf(WebLink("Bedetheque", URI("https://www.bedetheque.com/BD-AD-Grand-Riviere-Tome-1-Terre-d-election-12596.html"))), - titleLock = true, - summaryLock = true, - numberLock = true, - numberSortLock = true, - releaseDateLock = true, - authorsLock = true, - tagsLock = true, - isbnLock = true, - linksLock = true, - ) - } + val updated = + with(bookMetadataDao.findById(metadata.bookId)) { + copy( + title = "BookUpdated", + summary = "SummaryUpdated", + number = "2", + numberSort = 2F, + releaseDate = LocalDate.now(), + authors = listOf(Author("author2", "role2")), + tags = setOf("another"), + isbn = "987654321", + links = listOf(WebLink("Bedetheque", URI("https://www.bedetheque.com/BD-AD-Grand-Riviere-Tome-1-Terre-d-election-12596.html"))), + titleLock = true, + summaryLock = true, + numberLock = true, + numberSortLock = true, + releaseDateLock = true, + authorsLock = true, + tagsLock = true, + isbnLock = true, + linksLock = true, + ) + } bookMetadataDao.update(updated) val modified = bookMetadataDao.findById(updated.bookId) @@ -223,15 +227,16 @@ class BookMetadataDaoTest( @Test fun `given existing metadata when finding by id then metadata is returned`() { - val metadata = BookMetadata( - title = "Book", - summary = "Summary", - number = "1", - numberSort = 1F, - releaseDate = LocalDate.now(), - authors = listOf(Author("author", "role")), - bookId = book.id, - ) + val metadata = + BookMetadata( + title = "Book", + summary = "Summary", + number = "1", + numberSort = 1F, + releaseDate = LocalDate.now(), + authors = listOf(Author("author", "role")), + bookId = book.id, + ) bookMetadataDao.insert(metadata) val found = catchThrowable { bookMetadataDao.findById(metadata.bookId) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDaoTest.kt index 5f025dd11..85968ffe3 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/KomgaUserDaoTest.kt @@ -21,7 +21,6 @@ class KomgaUserDaoTest( @Autowired private val komgaUserDao: KomgaUserDao, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() @BeforeAll @@ -43,13 +42,14 @@ class KomgaUserDaoTest( @Test fun `given a user when saving it then it is persisted`() { val now = LocalDateTime.now() - val user = KomgaUser( - email = "user@example.org", - password = "password", - roleAdmin = false, - sharedLibrariesIds = setOf(library.id), - sharedAllLibraries = false, - ) + val user = + KomgaUser( + email = "user@example.org", + password = "password", + roleAdmin = false, + sharedLibrariesIds = setOf(library.id), + sharedAllLibraries = false, + ) komgaUserDao.insert(user) val created = komgaUserDao.findByIdOrNull(user.id)!! @@ -71,18 +71,20 @@ class KomgaUserDaoTest( @Test fun `given existing user when modifying and saving it then it is persisted`() { - val user = KomgaUser( - email = "user@example.org", - password = "password", - roleAdmin = false, - sharedLibrariesIds = setOf(library.id), - sharedAllLibraries = false, - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), - labelsAllow = setOf("allow"), - labelsExclude = setOf("exclude"), - ), - ) + val user = + KomgaUser( + email = "user@example.org", + password = "password", + roleAdmin = false, + sharedLibrariesIds = setOf(library.id), + sharedAllLibraries = false, + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(10, AllowExclude.ALLOW_ONLY), + labelsAllow = setOf("allow"), + labelsExclude = setOf("exclude"), + ), + ) komgaUserDao.insert(user) val created = komgaUserDao.findByIdOrNull(user.id)!! @@ -94,18 +96,20 @@ class KomgaUserDaoTest( assertThat(restrictions.labelsExclude).containsExactly("exclude") } - val modified = created.copy( - email = "user2@example.org", - password = "password2", - roleAdmin = true, - sharedLibrariesIds = emptySet(), - sharedAllLibraries = true, - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(16, AllowExclude.EXCLUDE), - labelsAllow = setOf("allow2"), - labelsExclude = setOf("exclude2"), - ), - ) + val modified = + created.copy( + email = "user2@example.org", + password = "password2", + roleAdmin = true, + sharedLibrariesIds = emptySet(), + sharedAllLibraries = true, + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(16, AllowExclude.EXCLUDE), + labelsAllow = setOf("allow2"), + labelsExclude = setOf("exclude2"), + ), + ) val modifiedDate = LocalDateTime.now() komgaUserDao.update(modified) val modifiedSaved = komgaUserDao.findByIdOrNull(modified.id)!! diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDaoTest.kt index b56823650..144865436 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/LibraryDaoTest.kt @@ -14,7 +14,6 @@ import java.time.LocalDateTime class LibraryDaoTest( @Autowired private val libraryDao: LibraryDao, ) { - @AfterEach fun deleteLibraries() { libraryDao.deleteAll() @@ -24,10 +23,11 @@ class LibraryDaoTest( @Test fun `given a library when inserting then it is persisted`() { val now = LocalDateTime.now() - val library = Library( - name = "Library", - root = URL("file://library"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) libraryDao.insert(library) val created = libraryDao.findById(library.id) @@ -40,44 +40,46 @@ class LibraryDaoTest( @Test fun `given existing library when updating then it is persisted`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) libraryDao.insert(library) val modificationDate = LocalDateTime.now() - val updated = with(libraryDao.findById(library.id)) { - copy( - name = "LibraryUpdated", - root = URL("file://library2"), - importEpubSeries = false, - importEpubBook = false, - importComicInfoCollection = false, - importComicInfoSeries = false, - importComicInfoBook = false, - importComicInfoReadList = false, - importComicInfoSeriesAppendVolume = false, - importMylarSeries = false, - importBarcodeIsbn = false, - importLocalArtwork = false, - repairExtensions = true, - convertToCbz = true, - emptyTrashAfterScan = true, - seriesCover = Library.SeriesCover.LAST, - hashFiles = false, - hashPages = true, - analyzeDimensions = false, - scanForceModifiedTime = true, - scanCbx = false, - scanEpub = false, - scanPdf = false, - scanInterval = Library.ScanInterval.DAILY, - scanOnStartup = true, - scanDirectoryExclusions = setOf("a", "b"), - ) - } + val updated = + with(libraryDao.findById(library.id)) { + copy( + name = "LibraryUpdated", + root = URL("file://library2"), + importEpubSeries = false, + importEpubBook = false, + importComicInfoCollection = false, + importComicInfoSeries = false, + importComicInfoBook = false, + importComicInfoReadList = false, + importComicInfoSeriesAppendVolume = false, + importMylarSeries = false, + importBarcodeIsbn = false, + importLocalArtwork = false, + repairExtensions = true, + convertToCbz = true, + emptyTrashAfterScan = true, + seriesCover = Library.SeriesCover.LAST, + hashFiles = false, + hashPages = true, + analyzeDimensions = false, + scanForceModifiedTime = true, + scanCbx = false, + scanEpub = false, + scanPdf = false, + scanInterval = Library.ScanInterval.DAILY, + scanOnStartup = true, + scanDirectoryExclusions = setOf("a", "b"), + ) + } libraryDao.update(updated) val modified = libraryDao.findById(updated.id) @@ -118,10 +120,11 @@ class LibraryDaoTest( @Test fun `given a library when deleting then it is deleted`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) libraryDao.insert(library) assertThat(libraryDao.count()).isEqualTo(1) @@ -133,14 +136,16 @@ class LibraryDaoTest( @Test fun `given libraries when deleting all then all are deleted`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) - val library2 = Library( - name = "Library2", - root = URL("file://library2"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) + val library2 = + Library( + name = "Library2", + root = URL("file://library2"), + ) libraryDao.insert(library) libraryDao.insert(library2) @@ -153,14 +158,16 @@ class LibraryDaoTest( @Test fun `given libraries when finding all then all are returned`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) - val library2 = Library( - name = "Library2", - root = URL("file://library2"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) + val library2 = + Library( + name = "Library2", + root = URL("file://library2"), + ) libraryDao.insert(library) libraryDao.insert(library2) @@ -173,14 +180,16 @@ class LibraryDaoTest( @Test fun `given libraries when finding all by id then all are returned`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) - val library2 = Library( - name = "Library2", - root = URL("file://library2"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) + val library2 = + Library( + name = "Library2", + root = URL("file://library2"), + ) libraryDao.insert(library) libraryDao.insert(library2) @@ -193,10 +202,11 @@ class LibraryDaoTest( @Test fun `given existing library when finding by id then library is returned`() { - val library = Library( - name = "Library", - root = URL("file://library"), - ) + val library = + Library( + name = "Library", + root = URL("file://library"), + ) libraryDao.insert(library) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDaoTest.kt index b08b21bd5..4edb42250 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/MediaDaoTest.kt @@ -65,22 +65,24 @@ class MediaDaoTest( @Test fun `given a media when inserting then it is persisted`() { val now = LocalDateTime.now() - val media = Media( - status = Media.Status.READY, - mediaType = "application/zip", - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - dimension = Dimension(10, 10), - fileHash = "hashed", - fileSize = 10, - ), - ), - files = listOf(MediaFile("ComicInfo.xml", "application/xml", MediaFile.SubType.EPUB_ASSET, 3)), - comment = "comment", - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/zip", + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + dimension = Dimension(10, 10), + fileHash = "hashed", + fileSize = 10, + ), + ), + files = listOf(MediaFile("ComicInfo.xml", "application/xml", MediaFile.SubType.EPUB_ASSET, 3)), + comment = "comment", + bookId = book.id, + ) mediaDao.insert(media) val created = mediaDao.findById(media.bookId) @@ -126,40 +128,44 @@ class MediaDaoTest( @Test fun `given existing media when updating then it is persisted`() { - val media = Media( - status = Media.Status.READY, - mediaType = "application/zip", - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - ), - ), - files = listOf(MediaFile("ComicInfo.xml", "application/xml", MediaFile.SubType.EPUB_ASSET, 5)), - comment = "comment", - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/zip", + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + ), + ), + files = listOf(MediaFile("ComicInfo.xml", "application/xml", MediaFile.SubType.EPUB_ASSET, 5)), + comment = "comment", + bookId = book.id, + ) mediaDao.insert(media) val modificationDate = LocalDateTime.now() - val updated = with(mediaDao.findById(media.bookId)) { - copy( - status = Media.Status.ERROR, - mediaType = "application/rar", - pages = listOf( - BookPage( - fileName = "2.png", - mediaType = "image/png", - dimension = Dimension(10, 10), - fileHash = "hashed", - fileSize = 10, - ), - ), - files = listOf(MediaFile("id.txt")), - comment = "comment2", - ) - } + val updated = + with(mediaDao.findById(media.bookId)) { + copy( + status = Media.Status.ERROR, + mediaType = "application/rar", + pages = + listOf( + BookPage( + fileName = "2.png", + mediaType = "image/png", + dimension = Dimension(10, 10), + fileHash = "hashed", + fileSize = 10, + ), + ), + files = listOf(MediaFile("id.txt")), + comment = "comment2", + ) + } mediaDao.update(updated) val modified = mediaDao.findById(updated.bookId) @@ -185,19 +191,21 @@ class MediaDaoTest( @Test fun `given existing media when finding by id then media is returned`() { - val media = Media( - status = Media.Status.READY, - mediaType = "application/zip", - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - ), - ), - files = listOf(MediaFile("ComicInfo.xml")), - comment = "comment", - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/zip", + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + ), + ), + files = listOf(MediaFile("ComicInfo.xml")), + comment = "comment", + bookId = book.id, + ) mediaDao.insert(media) val found = catchThrowable { mediaDao.findById(media.bookId) } @@ -216,15 +224,17 @@ class MediaDaoTest( inner class MediaExtension { @Test fun `given a media with extension when inserting then it is persisted`() { - val media = Media( - status = Media.Status.READY, - mediaType = "application/epub+zip", - extension = MediaExtensionEpub( - toc = listOf(EpubTocEntry("title", "href", listOf(EpubTocEntry("subtitle", "subhref")))), - landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), - ), - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/epub+zip", + extension = + MediaExtensionEpub( + toc = listOf(EpubTocEntry("title", "href", listOf(EpubTocEntry("subtitle", "subhref")))), + landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), + ), + bookId = book.id, + ) mediaDao.insert(media) val created = mediaDao.findById(media.bookId) @@ -241,23 +251,27 @@ class MediaDaoTest( @Test fun `given existing media with extension when updating then it is persisted`() { - val media = Media( - status = Media.Status.READY, - mediaType = "application/epub+zip", - extension = MediaExtensionEpub( - landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), - ), - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/epub+zip", + extension = + MediaExtensionEpub( + landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), + ), + bookId = book.id, + ) mediaDao.insert(media) - val updated = with(mediaDao.findById(media.bookId)) { - copy( - extension = MediaExtensionEpub( - toc = listOf(EpubTocEntry("title", "href", listOf(EpubTocEntry("subtitle", "subhref")))), - ), - ) - } + val updated = + with(mediaDao.findById(media.bookId)) { + copy( + extension = + MediaExtensionEpub( + toc = listOf(EpubTocEntry("title", "href", listOf(EpubTocEntry("subtitle", "subhref")))), + ), + ) + } mediaDao.update(updated) val modified = mediaDao.findById(updated.bookId) @@ -274,14 +288,16 @@ class MediaDaoTest( @Test fun `given existing media with proxy extension when updating then it is kept as-is`() { - val media = Media( - status = Media.Status.READY, - mediaType = "application/epub+zip", - extension = MediaExtensionEpub( - landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), - ), - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/epub+zip", + extension = + MediaExtensionEpub( + landmarks = listOf(EpubTocEntry("title2", "href2", listOf(EpubTocEntry("subtitle2", "subhref2")))), + ), + bookId = book.id, + ) mediaDao.insert(media) val updated = mediaDao.findById(media.bookId).copy(comment = "updated") @@ -306,20 +322,21 @@ class MediaDaoTest( @Nested inner class MissingPageHash { - @Test fun `given media with single page not hashed when finding for missing page hash then it is returned`() { - val media = Media( - status = Media.Status.READY, - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - ), - ), - mediaType = MediaType.ZIP.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + ), + ), + mediaType = MediaType.ZIP.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) @@ -329,17 +346,19 @@ class MediaDaoTest( @Test fun `given non-convertible media not hashed when finding for missing page hash then it is returned`() { - val media = Media( - status = Media.Status.READY, - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - ), - ), - mediaType = MediaType.RAR_4.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + ), + ), + mediaType = MediaType.RAR_4.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) @@ -349,17 +368,19 @@ class MediaDaoTest( @Test fun `given media with no pages hashed when finding for missing page hash then it is not returned`() { - val media = Media( - status = Media.Status.READY, - pages = (1..12).map { - BookPage( - fileName = "$it.jpg", - mediaType = "image/jpeg", - ) - }, - mediaType = MediaType.ZIP.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + (1..12).map { + BookPage( + fileName = "$it.jpg", + mediaType = "image/jpeg", + ) + }, + mediaType = MediaType.ZIP.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) @@ -369,18 +390,20 @@ class MediaDaoTest( @Test fun `given media with single page hashed when finding for missing page hash then it is not returned`() { - val media = Media( - status = Media.Status.READY, - pages = listOf( - BookPage( - fileName = "1.jpg", - mediaType = "image/jpeg", - fileHash = "hashed", - ), - ), - mediaType = MediaType.ZIP.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + listOf( + BookPage( + fileName = "1.jpg", + mediaType = "image/jpeg", + fileHash = "hashed", + ), + ), + mediaType = MediaType.ZIP.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) @@ -390,18 +413,20 @@ class MediaDaoTest( @Test fun `given media with required pages hashed when finding for missing page hash then it is not returned`() { - val media = Media( - status = Media.Status.READY, - pages = (1..12).map { - BookPage( - fileName = "$it.jpg", - mediaType = "image/jpeg", - fileHash = if (it <= 3 || it >= 9) "hashed" else "", - ) - }, - mediaType = MediaType.ZIP.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + (1..12).map { + BookPage( + fileName = "$it.jpg", + mediaType = "image/jpeg", + fileHash = if (it <= 3 || it >= 9) "hashed" else "", + ) + }, + mediaType = MediaType.ZIP.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) @@ -411,18 +436,20 @@ class MediaDaoTest( @Test fun `given media with more pages hashed than required when finding for missing page hash then it is not returned`() { - val media = Media( - status = Media.Status.READY, - pages = (1..12).map { - BookPage( - fileName = "$it.jpg", - mediaType = "image/jpeg", - fileHash = "hashed", - ) - }, - mediaType = MediaType.ZIP.type, - bookId = book.id, - ) + val media = + Media( + status = Media.Status.READY, + pages = + (1..12).map { + BookPage( + fileName = "$it.jpg", + mediaType = "image/jpeg", + fileHash = "hashed", + ) + }, + mediaType = MediaType.ZIP.type, + bookId = book.id, + ) mediaDao.insert(media) val found = mediaDao.findAllBookIdsByLibraryIdAndMediaTypeAndWithMissingPageHash(book.libraryId, listOf(MediaType.ZIP.type), komgaProperties.pageHashing) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDaoTest.kt index 0b29327ef..9df45f232 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/PageHashDaoTest.kt @@ -31,10 +31,11 @@ class PageHashDaoTest( ) { private val library = makeLibrary() private val series = makeSeries("Series", libraryId = library.id) - private val books = listOf( - makeBook("Book", libraryId = library.id, seriesId = series.id), - makeBook("Book2", libraryId = library.id, seriesId = series.id), - ) + private val books = + listOf( + makeBook("Book", libraryId = library.id, seriesId = series.id), + makeBook("Book2", libraryId = library.id, seriesId = series.id), + ) @BeforeAll fun setup() { @@ -62,11 +63,12 @@ class PageHashDaoTest( @Test fun `given a known page hash when inserting then it is persisted`() { val now = LocalDateTime.now() - val pageHash = PageHashKnown( - hash = "hashed", - size = 10, - action = PageHashKnown.Action.IGNORE, - ) + val pageHash = + PageHashKnown( + hash = "hashed", + size = 10, + action = PageHashKnown.Action.IGNORE, + ) pageHashDao.insert(pageHash, null) val known = pageHashDao.findKnown(pageHash.hash)!! @@ -102,20 +104,22 @@ class PageHashDaoTest( null, ) - val media = Media( - status = Media.Status.READY, - mediaType = "application/zip", - pages = (1..10).map { - BookPage( - fileName = "$it.jpg", - mediaType = "image/jpeg", - fileHash = "hash-$it", - fileSize = it.toLong(), - ) - }, - comment = "comment", - bookId = books.first().id, - ) + val media = + Media( + status = Media.Status.READY, + mediaType = "application/zip", + pages = + (1..10).map { + BookPage( + fileName = "$it.jpg", + mediaType = "image/jpeg", + fileHash = "hash-$it", + fileSize = it.toLong(), + ) + }, + comment = "comment", + bookId = books.first().id, + ) mediaDao.insert(media) mediaDao.insert(media.copy(bookId = books.last().id)) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDaoTest.kt index 5dab1d296..f41fd3149 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/ReadListDaoTest.kt @@ -26,7 +26,6 @@ class ReadListDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() private val library2 = makeLibrary("library2") @@ -56,11 +55,12 @@ class ReadListDaoTest( val books = (1..10).map { makeBook("Book $it", libraryId = library.id, seriesId = series.id) } books.forEach { bookRepository.insert(it) } - val readList = ReadList( - name = "MyReadList", - summary = "summary", - bookIds = books.map { it.id }.toIndexedMap(), - ) + val readList = + ReadList( + name = "MyReadList", + summary = "summary", + bookIds = books.map { it.id }.toIndexedMap(), + ) // when val now = LocalDateTime.now() @@ -85,19 +85,21 @@ class ReadListDaoTest( val books = (1..10).map { makeBook("Book $it", libraryId = library.id, seriesId = series.id) } books.forEach { bookRepository.insert(it) } - val readList = ReadList( - name = "MyReadList", - bookIds = books.map { it.id }.toIndexedMap(), - ) + val readList = + ReadList( + name = "MyReadList", + bookIds = books.map { it.id }.toIndexedMap(), + ) readListDao.insert(readList) // when - val updatedReadList = readList.copy( - name = "UpdatedReadList", - summary = "summary", - bookIds = readList.bookIds.values.take(5).toIndexedMap(), - ) + val updatedReadList = + readList.copy( + name = "UpdatedReadList", + summary = "summary", + bookIds = readList.bookIds.values.take(5).toIndexedMap(), + ) val now = LocalDateTime.now() readListDao.update(updatedReadList) @@ -121,16 +123,18 @@ class ReadListDaoTest( val books = (1..10).map { makeBook("Book $it", libraryId = library.id, seriesId = series.id) } books.forEach { bookRepository.insert(it) } - val readList1 = ReadList( - name = "MyReadList", - bookIds = books.map { it.id }.toIndexedMap(), - ) + val readList1 = + ReadList( + name = "MyReadList", + bookIds = books.map { it.id }.toIndexedMap(), + ) readListDao.insert(readList1) - val readList2 = ReadList( - name = "MyReadList", - bookIds = books.map { it.id }.take(5).toIndexedMap(), - ) + val readList2 = + ReadList( + name = "MyReadList", + bookIds = books.map { it.id }.take(5).toIndexedMap(), + ) readListDao.insert(readList2) // when diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDaoTest.kt index c07fbe6e2..1c88911ee 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesCollectionDaoTest.kt @@ -22,7 +22,6 @@ class SeriesCollectionDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() private val library2 = makeLibrary("library2") @@ -49,10 +48,11 @@ class SeriesCollectionDaoTest( val series = (1..10).map { makeSeries("Series $it", library.id) } series.forEach { seriesRepository.insert(it) } - val collection = SeriesCollection( - name = "MyCollection", - seriesIds = series.map { it.id }, - ) + val collection = + SeriesCollection( + name = "MyCollection", + seriesIds = series.map { it.id }, + ) // when val now = LocalDateTime.now() @@ -75,19 +75,21 @@ class SeriesCollectionDaoTest( val series = (1..10).map { makeSeries("Series $it", library.id) } series.forEach { seriesRepository.insert(it) } - val collection = SeriesCollection( - name = "MyCollection", - seriesIds = series.map { it.id }, - ) + val collection = + SeriesCollection( + name = "MyCollection", + seriesIds = series.map { it.id }, + ) collectionDao.insert(collection) // when - val updatedCollection = collection.copy( - name = "UpdatedCollection", - ordered = true, - seriesIds = collection.seriesIds.take(5), - ) + val updatedCollection = + collection.copy( + name = "UpdatedCollection", + ordered = true, + seriesIds = collection.seriesIds.take(5), + ) val now = LocalDateTime.now() collectionDao.update(updatedCollection) @@ -109,16 +111,18 @@ class SeriesCollectionDaoTest( val series = (1..10).map { makeSeries("Series $it", library.id) } series.forEach { seriesRepository.insert(it) } - val collection1 = SeriesCollection( - name = "MyCollection", - seriesIds = series.map { it.id }, - ) + val collection1 = + SeriesCollection( + name = "MyCollection", + seriesIds = series.map { it.id }, + ) collectionDao.insert(collection1) - val collection2 = SeriesCollection( - name = "MyCollection2", - seriesIds = series.map { it.id }.take(5), - ) + val collection2 = + SeriesCollection( + name = "MyCollection2", + seriesIds = series.map { it.id }.take(5), + ) collectionDao.insert(collection2) // when diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDaoTest.kt index b4a7231dc..5ad59ee4b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDaoTest.kt @@ -20,7 +20,6 @@ class SeriesDaoTest( @Autowired private val seriesDao: SeriesDao, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() @BeforeAll @@ -42,13 +41,14 @@ class SeriesDaoTest( @Test fun `given a series when inserting then it is persisted`() { val now = LocalDateTime.now() - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = now, - libraryId = library.id, - deletedDate = now, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = now, + libraryId = library.id, + deletedDate = now, + ) seriesDao.insert(series) val created = seriesDao.findByIdOrNull(series.id)!! @@ -65,24 +65,26 @@ class SeriesDaoTest( @Test fun `given a series when updating then it is persisted`() { val now = LocalDateTime.now() - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = now, - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = now, + libraryId = library.id, + ) seriesDao.insert(series) val modificationDate = LocalDateTime.now() - val updated = seriesDao.findByIdOrNull(series.id)!!.copy( - name = "Updated", - url = URL("file://updated"), - fileLastModified = modificationDate, - bookCount = 5, - deletedDate = LocalDateTime.now(), - ) + val updated = + seriesDao.findByIdOrNull(series.id)!!.copy( + name = "Updated", + url = URL("file://updated"), + fileLastModified = modificationDate, + bookCount = 5, + deletedDate = LocalDateTime.now(), + ) seriesDao.update(updated) val modified = seriesDao.findByIdOrNull(updated.id)!! @@ -101,12 +103,13 @@ class SeriesDaoTest( @Test fun `given a series when deleting then it is deleted`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) assertThat(seriesDao.count()).isEqualTo(1) @@ -119,19 +122,21 @@ class SeriesDaoTest( @Test fun `given series when deleting all then all are deleted`() { val now = LocalDateTime.now() - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = now, - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = now, + libraryId = library.id, + ) - val series2 = Series( - name = "Series2", - url = URL("file://series2"), - fileLastModified = now, - libraryId = library.id, - ) + val series2 = + Series( + name = "Series2", + url = URL("file://series2"), + fileLastModified = now, + libraryId = library.id, + ) seriesDao.insert(series) seriesDao.insert(series2) @@ -145,19 +150,21 @@ class SeriesDaoTest( @Test fun `given series when finding all then all are returned`() { val now = LocalDateTime.now() - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = now, - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = now, + libraryId = library.id, + ) - val series2 = Series( - name = "Series2", - url = URL("file://series2"), - fileLastModified = now, - libraryId = library.id, - ) + val series2 = + Series( + name = "Series2", + url = URL("file://series2"), + fileLastModified = now, + libraryId = library.id, + ) seriesDao.insert(series) seriesDao.insert(series2) @@ -170,12 +177,13 @@ class SeriesDaoTest( @Test fun `given existing series when finding by id then series is returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) @@ -194,18 +202,20 @@ class SeriesDaoTest( @Test fun `given existing series when searching then result is returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) - val search = SeriesSearch( - libraryIds = listOf(library.id), - ) + val search = + SeriesSearch( + libraryIds = listOf(library.id), + ) val found = seriesDao.findAll(search) assertThat(found).hasSize(1) @@ -213,12 +223,13 @@ class SeriesDaoTest( @Test fun `given existing series when searching by regex then result is returned`() { - val series = Series( - name = "my Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "my Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) assertThat(seriesDao.findAll(SeriesSearch(searchRegex = Pair("^my", SeriesSearch.SearchField.NAME)))).hasSize(1) @@ -228,12 +239,13 @@ class SeriesDaoTest( @Test fun `given existing series when finding by libraryId then series are returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) val found = seriesDao.findAllByLibraryId(library.id) @@ -244,12 +256,13 @@ class SeriesDaoTest( @Test fun `given existing series when finding by other libraryId then empty list is returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) val found = seriesDao.findAllByLibraryId(library.id + 1) @@ -259,12 +272,13 @@ class SeriesDaoTest( @Test fun `given existing series when finding by libraryId and Url not in list then results are returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) val found = seriesDao.findAllNotDeletedByLibraryIdAndUrlNotIn(library.id, listOf(URL("file://series2"))) @@ -278,12 +292,13 @@ class SeriesDaoTest( @Test fun `given existing series when finding by libraryId and Url in list then results are returned`() { - val series = Series( - name = "Series", - url = URL("file://series"), - fileLastModified = LocalDateTime.now(), - libraryId = library.id, - ) + val series = + Series( + name = "Series", + url = URL("file://series"), + fileLastModified = LocalDateTime.now(), + libraryId = library.id, + ) seriesDao.insert(series) val found = seriesDao.findNotDeletedByLibraryIdAndUrlOrNull(library.id, URL("file://series")) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDaoTest.kt index 32520d25e..aa23e7c99 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesDtoDaoTest.kt @@ -57,7 +57,6 @@ class SeriesDtoDaoTest( @Autowired private val userLifecycle: KomgaUserLifecycle, @Autowired private val searchIndexLifecycle: SearchIndexLifecycle, ) { - private val library = makeLibrary() private val user = KomgaUser("user@example.org", "", false) @@ -132,11 +131,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(1) @@ -151,11 +151,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(1) @@ -170,11 +171,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(2) @@ -192,11 +194,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.UNREAD)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.UNREAD)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(2) @@ -209,11 +212,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(3) @@ -226,11 +230,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.UNREAD, ReadStatus.IN_PROGRESS)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(3) @@ -243,11 +248,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS, ReadStatus.UNREAD)), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(readStatus = listOf(ReadStatus.READ, ReadStatus.IN_PROGRESS, ReadStatus.UNREAD)), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(4) @@ -260,11 +266,12 @@ class SeriesDtoDaoTest( setupSeries() // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(), - user.id, - PageRequest.of(0, 20), - ).sortedBy { it.name } + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(), + user.id, + PageRequest.of(0, 20), + ).sortedBy { it.name } // then assertThat(found).hasSize(4) @@ -285,11 +292,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "batman"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "batman"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(3) @@ -310,11 +318,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "publisher:vertigo"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "publisher:vertigo"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -335,11 +344,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "status:hiatus"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "status:hiatus"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -360,11 +370,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "reading_direction:left_to_right"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "reading_direction:left_to_right"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -385,11 +396,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "age_rating:12"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "age_rating:12"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -410,11 +422,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "language:en-us"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "language:en-us"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -441,41 +454,47 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val foundByBookTag = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "book_tag:booktag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundByBookTag = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "book_tag:booktag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val notFoundByBookTag = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "book_tag:seriestag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val notFoundByBookTag = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "book_tag:seriestag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val foundBySeriesTag = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "series_tag:seriestag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundBySeriesTag = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "series_tag:seriestag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val notFoundBySeriesTag = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "series_tag:booktag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val notFoundBySeriesTag = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "series_tag:booktag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val foundByTagFromBook = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "tag:booktag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundByTagFromBook = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "tag:booktag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val foundByTagFromSeries = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "tag:seriestag"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundByTagFromSeries = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "tag:seriestag"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(foundByBookTag).hasSize(1) @@ -509,11 +528,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "genre:action"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "genre:action"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -534,11 +554,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "total_book_count:5"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "total_book_count:5"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -567,11 +588,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "book_count:2"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "book_count:2"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -589,9 +611,10 @@ class SeriesDtoDaoTest( bookMetadataRepository.findById(book.id).let { bookMetadataRepository.update( it.copy( - authors = listOf( - Author("David", "penciller"), - ), + authors = + listOf( + Author("David", "penciller"), + ), ), ) } @@ -601,23 +624,26 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val foundGeneric = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "author:david"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundGeneric = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "author:david"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val foundByRole = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "penciller:david"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val foundByRole = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "penciller:david"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content - val notFoundByRole = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "writer:david"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val notFoundByRole = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "writer:david"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(foundGeneric).hasSize(1) @@ -646,11 +672,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "release_date:1999"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "release_date:1999"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) @@ -667,11 +694,12 @@ class SeriesDtoDaoTest( Thread.sleep(500) // index rebuild is done asynchronously, and need a slight delay to be updated // when - val found = seriesDtoDao.findAll( - SeriesSearchWithReadProgress(searchTerm = "deleted:true"), - user.id, - UnpagedSorted(Sort.by("relevance")), - ).content + val found = + seriesDtoDao.findAll( + SeriesSearchWithReadProgress(searchTerm = "deleted:true"), + user.id, + UnpagedSorted(Sort.by("relevance")), + ).content // then assertThat(found).hasSize(1) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDaoTest.kt index 8492c4060..3490e88d8 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/main/SeriesMetadataDaoTest.kt @@ -25,7 +25,6 @@ class SeriesMetadataDaoTest( @Autowired private val seriesRepository: SeriesRepository, @Autowired private val libraryRepository: LibraryRepository, ) { - private val library = makeLibrary() @BeforeAll @@ -51,36 +50,37 @@ class SeriesMetadataDaoTest( val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val now = LocalDateTime.now() - val metadata = SeriesMetadata( - status = SeriesMetadata.Status.ENDED, - title = "Series", - titleSort = "Series, The", - summary = "Summary", - readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, - publisher = "publisher", - ageRating = 18, - genres = setOf("Action", "Adventure"), - tags = setOf("tag", "another"), - language = "en", - totalBookCount = 5, - sharingLabels = setOf("kids"), - links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))), - alternateTitles = listOf(AlternateTitle("fr", "La Series")), - titleLock = true, - titleSortLock = true, - summaryLock = true, - readingDirectionLock = true, - publisherLock = true, - ageRatingLock = true, - genresLock = true, - languageLock = true, - tagsLock = true, - totalBookCountLock = true, - sharingLabelsLock = true, - linksLock = true, - alternateTitlesLock = true, - seriesId = series.id, - ) + val metadata = + SeriesMetadata( + status = SeriesMetadata.Status.ENDED, + title = "Series", + titleSort = "Series, The", + summary = "Summary", + readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, + publisher = "publisher", + ageRating = 18, + genres = setOf("Action", "Adventure"), + tags = setOf("tag", "another"), + language = "en", + totalBookCount = 5, + sharingLabels = setOf("kids"), + links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))), + alternateTitles = listOf(AlternateTitle("fr", "La Series")), + titleLock = true, + titleSortLock = true, + summaryLock = true, + readingDirectionLock = true, + publisherLock = true, + ageRatingLock = true, + genresLock = true, + languageLock = true, + tagsLock = true, + totalBookCountLock = true, + sharingLabelsLock = true, + linksLock = true, + alternateTitlesLock = true, + seriesId = series.id, + ) seriesMetadataDao.insert(metadata) val created = seriesMetadataDao.findById(metadata.seriesId) @@ -131,10 +131,11 @@ class SeriesMetadataDaoTest( val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } val now = LocalDateTime.now() - val metadata = SeriesMetadata( - title = "Series", - seriesId = series.id, - ) + val metadata = + SeriesMetadata( + title = "Series", + seriesId = series.id, + ) seriesMetadataDao.insert(metadata) val created = seriesMetadataDao.findById(metadata.seriesId) @@ -178,12 +179,13 @@ class SeriesMetadataDaoTest( fun `given existing seriesMetadata when finding by id then metadata is returned`() { val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } - val metadata = SeriesMetadata( - status = SeriesMetadata.Status.ENDED, - title = "Series", - titleSort = "Series, The", - seriesId = series.id, - ) + val metadata = + SeriesMetadata( + status = SeriesMetadata.Status.ENDED, + title = "Series", + titleSort = "Series, The", + seriesId = series.id, + ) seriesMetadataDao.insert(metadata) @@ -211,60 +213,62 @@ class SeriesMetadataDaoTest( fun `given a seriesMetadata when updating then it is persisted`() { val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) } - val metadata = SeriesMetadata( - status = SeriesMetadata.Status.ENDED, - title = "Series", - titleSort = "Series, The", - summary = "Summary", - readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, - publisher = "publisher", - ageRating = 18, - language = "en", - genres = setOf("Action"), - tags = setOf("tag"), - totalBookCount = 3, - sharingLabels = setOf("kids"), - links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))), - alternateTitles = listOf(AlternateTitle("fr", "La Series")), - seriesId = series.id, - ) + val metadata = + SeriesMetadata( + status = SeriesMetadata.Status.ENDED, + title = "Series", + titleSort = "Series, The", + summary = "Summary", + readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, + publisher = "publisher", + ageRating = 18, + language = "en", + genres = setOf("Action"), + tags = setOf("tag"), + totalBookCount = 3, + sharingLabels = setOf("kids"), + links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))), + alternateTitles = listOf(AlternateTitle("fr", "La Series")), + seriesId = series.id, + ) seriesMetadataDao.insert(metadata) val created = seriesMetadataDao.findById(metadata.seriesId) val modificationDate = LocalDateTime.now() - val updated = with(created) { - copy( - status = SeriesMetadata.Status.HIATUS, - title = "Changed", - titleSort = "Changed, The", - summary = "SummaryUpdated", - readingDirection = SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT, - publisher = "publisher2", - ageRating = 15, - language = "jp", - genres = setOf("Adventure"), - tags = setOf("Another"), - totalBookCount = 8, - sharingLabels = setOf("adult"), - links = emptyList(), - alternateTitles = emptyList(), - statusLock = true, - titleLock = true, - titleSortLock = true, - summaryLock = true, - readingDirectionLock = true, - publisherLock = true, - ageRatingLock = true, - languageLock = true, - genresLock = true, - tagsLock = true, - totalBookCountLock = true, - sharingLabelsLock = true, - linksLock = true, - alternateTitlesLock = true, - ) - } + val updated = + with(created) { + copy( + status = SeriesMetadata.Status.HIATUS, + title = "Changed", + titleSort = "Changed, The", + summary = "SummaryUpdated", + readingDirection = SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT, + publisher = "publisher2", + ageRating = 15, + language = "jp", + genres = setOf("Adventure"), + tags = setOf("Another"), + totalBookCount = 8, + sharingLabels = setOf("adult"), + links = emptyList(), + alternateTitles = emptyList(), + statusLock = true, + titleLock = true, + titleSortLock = true, + summaryLock = true, + readingDirectionLock = true, + publisherLock = true, + ageRatingLock = true, + languageLock = true, + genresLock = true, + tagsLock = true, + totalBookCountLock = true, + sharingLabelsLock = true, + linksLock = true, + alternateTitlesLock = true, + ) + } seriesMetadataDao.update(updated) val modified = seriesMetadataDao.findById(updated.seriesId) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDaoTest.kt index ea9ddd4d1..70c9f7f71 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/tasks/TasksDaoTest.kt @@ -259,8 +259,9 @@ class TasksDaoTest( // when val disownCount = tasksDao.disown() - val disownedTasks = tasksDao.findAll() - .filter { task -> ownedTasks.map { it.uniqueId }.contains(task.uniqueId) } + val disownedTasks = + tasksDao.findAll() + .filter { task -> ownedTasks.map { it.uniqueId }.contains(task.uniqueId) } val groupedByOwner = tasksDao.findAllGroupedByOwner() diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractorTest.kt index a45ed6075..432e1ecf1 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/RarExtractorTest.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test import org.springframework.core.io.ClassPathResource class RarExtractorTest { - private val contentDetector = ContentDetector(TikaConfig()) private val imageAnalyzer = ImageAnalyzer() private val rarExtractor = RarExtractor(contentDetector, imageAnalyzer) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractorTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractorTest.kt index f6abda4a2..a5123dd6c 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/divina/ZipExtractorTest.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test import org.springframework.core.io.ClassPathResource class ZipExtractorTest { - private val contentDetector = ContentDetector(TikaConfig()) private val imageAnalyzer = ImageAnalyzer() private val zipExtractor = ZipExtractor(contentDetector, imageAnalyzer) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NavTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NavTest.kt index 562705dc0..2de79d31b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NavTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NavTest.kt @@ -12,7 +12,11 @@ import kotlin.io.path.Path class NavTest { @ParameterizedTest @MethodSource("paramSource") - fun `given nav document when parsing nav section then nav entries are valid`(navType: Epub3Nav, prefix: String?, expectedProvider: (String) -> List) { + fun `given nav document when parsing nav section then nav entries are valid`( + navType: Epub3Nav, + prefix: String?, + expectedProvider: (String) -> List, + ) { // given val navResource = ClassPathResource("epub/nav.xhtml") val navString = navResource.inputStream.readAllBytes().decodeToString() @@ -37,38 +41,42 @@ class NavTest { ) } - private fun getExpectedNavToc(prefix: String = "") = listOf( - EpubTocEntry("Cover", "${prefix}cover.xhtml"), - EpubTocEntry("Title Page", "${prefix}titlepage.xhtml"), - EpubTocEntry("Copyright", "${prefix}copyright.xhtml"), - EpubTocEntry("Table of Contents", "${prefix}toc.xhtml"), - EpubTocEntry("An unlinked heading", null), - EpubTocEntry( - "Introduction", - "${prefix}introduction.xhtml", - children = listOf( - EpubTocEntry("Spring", "${prefix}chapter 001.xhtml"), - EpubTocEntry("Summer", "${prefix}chapter 027.xhtml"), - EpubTocEntry("Fall", "${prefix}chapter053.xhtml"), - EpubTocEntry("Winter", "${prefix}chapter079.xhtml"), + private fun getExpectedNavToc(prefix: String = "") = + listOf( + EpubTocEntry("Cover", "${prefix}cover.xhtml"), + EpubTocEntry("Title Page", "${prefix}titlepage.xhtml"), + EpubTocEntry("Copyright", "${prefix}copyright.xhtml"), + EpubTocEntry("Table of Contents", "${prefix}toc.xhtml"), + EpubTocEntry("An unlinked heading", null), + EpubTocEntry( + "Introduction", + "${prefix}introduction.xhtml", + children = + listOf( + EpubTocEntry("Spring", "${prefix}chapter 001.xhtml"), + EpubTocEntry("Summer", "${prefix}chapter 027.xhtml"), + EpubTocEntry("Fall", "${prefix}chapter053.xhtml"), + EpubTocEntry("Winter", "${prefix}chapter079.xhtml"), + ), ), - ), - EpubTocEntry("Acknowledgments", "${prefix}acknowledgements.xhtml"), - ) + EpubTocEntry("Acknowledgments", "${prefix}acknowledgements.xhtml"), + ) - private fun getExpectedNavLandmarks(prefix: String = "") = listOf( - EpubTocEntry("Begin Reading", "${prefix}cover.xhtml#coverimage"), - EpubTocEntry("Table of Contents", "${prefix}toc.xhtml"), - ) + private fun getExpectedNavLandmarks(prefix: String = "") = + listOf( + EpubTocEntry("Begin Reading", "${prefix}cover.xhtml#coverimage"), + EpubTocEntry("Table of Contents", "${prefix}toc.xhtml"), + ) - private fun getExpectedNavPageList(prefix: String = "") = listOf( - EpubTocEntry("Cover Page", "${prefix}xhtml/cover.xhtml"), - EpubTocEntry("iii", "${prefix}xhtml/title.xhtml#pg_iii"), - EpubTocEntry("1", "${prefix}xhtml/chapter1.xhtml#pg_1"), - EpubTocEntry("2", "${prefix}xhtml/chapter1.xhtml#pg_2"), - EpubTocEntry("107", "${prefix}xhtml/acknowledgments.xhtml#pg_107"), - EpubTocEntry("ii", "${prefix}xhtml/adcard.xhtml#pg_ii"), - EpubTocEntry("109", "${prefix}xhtml/abouttheauthor.xhtml#pg_109"), - EpubTocEntry("iv", "${prefix}xhtml/copyright.xhtml#pg_iv"), - ) + private fun getExpectedNavPageList(prefix: String = "") = + listOf( + EpubTocEntry("Cover Page", "${prefix}xhtml/cover.xhtml"), + EpubTocEntry("iii", "${prefix}xhtml/title.xhtml#pg_iii"), + EpubTocEntry("1", "${prefix}xhtml/chapter1.xhtml#pg_1"), + EpubTocEntry("2", "${prefix}xhtml/chapter1.xhtml#pg_2"), + EpubTocEntry("107", "${prefix}xhtml/acknowledgments.xhtml#pg_107"), + EpubTocEntry("ii", "${prefix}xhtml/adcard.xhtml#pg_ii"), + EpubTocEntry("109", "${prefix}xhtml/abouttheauthor.xhtml#pg_109"), + EpubTocEntry("iv", "${prefix}xhtml/copyright.xhtml#pg_iv"), + ) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NcxTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NcxTest.kt index ebb549ab4..e7698cacb 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NcxTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/NcxTest.kt @@ -12,7 +12,11 @@ import kotlin.io.path.Path class NcxTest { @ParameterizedTest @MethodSource("paramSource") - fun `given ncx document when parsing nav then nav entries are valid`(navType: Epub2Nav, prefix: String?, expectedProvider: (String) -> List) { + fun `given ncx document when parsing nav then nav entries are valid`( + navType: Epub2Nav, + prefix: String?, + expectedProvider: (String) -> List, + ) { // given val ncxResource = ClassPathResource("epub/toc.ncx") val ncxString = ncxResource.inputStream.readAllBytes().decodeToString() @@ -35,42 +39,46 @@ class NcxTest { ) } - private fun getExpectedNcxToc(prefix: String = "") = listOf( - EpubTocEntry("COVER", "${prefix}Text/Mart_9780553897852_epub_cvi_r1.htm#b02-cvi"), - EpubTocEntry("BRAN", "${prefix}Text/Mart_9780553897852_epub_c69_r1.htm"), - EpubTocEntry( - "APPENDIX", - "${prefix}Text/Mart_9780553897852_epub_app_r1.htm", - children = listOf( - EpubTocEntry("THE KINGS AND THEIR COURTS", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.00"), - EpubTocEntry("THE KING ON THE IRON THRONE", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.01"), - EpubTocEntry("THE KING IN THE NARROW SEA", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.02"), - EpubTocEntry("THE KING IN HIGHGARDEN", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.03"), - EpubTocEntry("THE KING IN THE NORTH", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.04"), - EpubTocEntry( - "THE QUEEN ACROSS THE WATER", - "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.05", - children = listOf( - EpubTocEntry("Another level", "${prefix}Text/Mart_9780553897852 epub_app_r1.htm#apps01.06"), - EpubTocEntry("Yet another level", "${prefix}Text/Mart_9780553897852 epub_app_r1.htm#apps01.07"), + private fun getExpectedNcxToc(prefix: String = "") = + listOf( + EpubTocEntry("COVER", "${prefix}Text/Mart_9780553897852_epub_cvi_r1.htm#b02-cvi"), + EpubTocEntry("BRAN", "${prefix}Text/Mart_9780553897852_epub_c69_r1.htm"), + EpubTocEntry( + "APPENDIX", + "${prefix}Text/Mart_9780553897852_epub_app_r1.htm", + children = + listOf( + EpubTocEntry("THE KINGS AND THEIR COURTS", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.00"), + EpubTocEntry("THE KING ON THE IRON THRONE", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.01"), + EpubTocEntry("THE KING IN THE NARROW SEA", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.02"), + EpubTocEntry("THE KING IN HIGHGARDEN", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.03"), + EpubTocEntry("THE KING IN THE NORTH", "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.04"), + EpubTocEntry( + "THE QUEEN ACROSS THE WATER", + "${prefix}Text/Mart_9780553897852_epub_app_r1.htm#apps01.05", + children = + listOf( + EpubTocEntry("Another level", "${prefix}Text/Mart_9780553897852 epub_app_r1.htm#apps01.06"), + EpubTocEntry("Yet another level", "${prefix}Text/Mart_9780553897852 epub_app_r1.htm#apps01.07"), + ), + ), ), - ), ), - ), - EpubTocEntry("ACKNOWLEDGMENTS", "${prefix}Text/Mart_9780553897852_epub_ack_r1.htm"), - ) + EpubTocEntry("ACKNOWLEDGMENTS", "${prefix}Text/Mart_9780553897852_epub_ack_r1.htm"), + ) - private fun getExpectedNcxPageList(prefix: String = "") = listOf( - EpubTocEntry("Cover Page", "${prefix}xhtml/cover.xhtml"), - EpubTocEntry("iii", "${prefix}xhtml/title.xhtml#pg_iii"), - EpubTocEntry("v", "${prefix}xhtml/dedication.xhtml#pg_v"), - EpubTocEntry("vii", "${prefix}xhtml/formoreinformation.xhtml#pg_vii"), - EpubTocEntry("viii", "${prefix}xhtml/formoreinformation.xhtml#pg_viii"), - EpubTocEntry("ix", "${prefix}xhtml/formoreinformation.xhtml#pg_ix"), - EpubTocEntry("x", "${prefix}xhtml/formoreinformation.xhtml#pg_x"), - EpubTocEntry("xi", "${prefix}xhtml/formoreinformation.xhtml#pg_xi"), - EpubTocEntry("1", "${prefix}xhtml/chapter1.xhtml#pg_1"), - EpubTocEntry("2", "${prefix}xhtml/chapter1.xhtml#pg_2"), - EpubTocEntry("3", "${prefix}xhtml/chapter1.xhtml#pg_3"), - ) + private fun getExpectedNcxPageList(prefix: String = "") = + listOf( + EpubTocEntry("Cover Page", "${prefix}xhtml/cover.xhtml"), + EpubTocEntry("iii", "${prefix}xhtml/title.xhtml#pg_iii"), + EpubTocEntry("v", "${prefix}xhtml/dedication.xhtml#pg_v"), + EpubTocEntry("vii", "${prefix}xhtml/formoreinformation.xhtml#pg_vii"), + EpubTocEntry("viii", "${prefix}xhtml/formoreinformation.xhtml#pg_viii"), + EpubTocEntry("ix", "${prefix}xhtml/formoreinformation.xhtml#pg_ix"), + EpubTocEntry("x", "${prefix}xhtml/formoreinformation.xhtml#pg_x"), + EpubTocEntry("xi", "${prefix}xhtml/formoreinformation.xhtml#pg_xi"), + EpubTocEntry("1", "${prefix}xhtml/chapter1.xhtml#pg_1"), + EpubTocEntry("2", "${prefix}xhtml/chapter1.xhtml#pg_2"), + EpubTocEntry("3", "${prefix}xhtml/chapter1.xhtml#pg_3"), + ) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/OpfTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/OpfTest.kt index 00b44e452..11c0a7759 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/OpfTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/mediacontainer/epub/OpfTest.kt @@ -26,9 +26,10 @@ class OpfTest { assertThat(opfLandmarks).isEqualTo(expectedToc) } - private fun getExpectedOpfLandmarks(prefix: String = "") = listOf( - EpubTocEntry("Table Of Contents", "${prefix}Text/Mart_9780553897852_epub_toc_r1.htm"), - EpubTocEntry("Text", "${prefix}Text/Mart_9780553897852_epub_prl_r1.htm"), - EpubTocEntry("Cover", "${prefix}Text/Mart_9780553897852_epub_cvi_r1.htm"), - ) + private fun getExpectedOpfLandmarks(prefix: String = "") = + listOf( + EpubTocEntry("Table Of Contents", "${prefix}Text/Mart_9780553897852_epub_toc_r1.htm"), + EpubTocEntry("Text", "${prefix}Text/Mart_9780553897852_epub_prl_r1.htm"), + EpubTocEntry("Cover", "${prefix}Text/Mart_9780553897852_epub_cvi_r1.htm"), + ) } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProviderTest.kt index c7092183d..03d4b1c25 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ComicInfoProviderTest.kt @@ -26,40 +26,41 @@ import java.time.LocalDate import java.util.stream.Stream class ComicInfoProviderTest { - private val mockMapper = mockk() - private val mockAnalyzer = mockk().also { - every { it.getFileContent(any(), "ComicInfo.xml") } returns ByteArray(0) - } + private val mockAnalyzer = + mockk().also { + every { it.getFileContent(any(), "ComicInfo.xml") } returns ByteArray(0) + } private val isbnValidator = ISBNValidator(true) private val comicInfoProvider = ComicInfoProvider(mockMapper, mockAnalyzer, isbnValidator) private val book = makeBook("book") - private val media = Media( - status = Media.Status.READY, - mediaType = "application/zip", - files = listOf(MediaFile("ComicInfo.xml")), - ) + private val media = + Media( + status = Media.Status.READY, + mediaType = "application/zip", + files = listOf(MediaFile("ComicInfo.xml")), + ) @Nested inner class Book { - @Test fun `given comicInfo when getting book metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - title = "title" - summary = "summary" - number = "010" - year = 2020 - month = 2 - alternateSeries = "story arc" - alternateNumber = "5" - storyArc = "one, two, three" - web = "https://www.comixology.com/Sandman/digital-comic/727888" - tags = "dark, Occult" - gtin = "9783440077894" - } + val comicInfo = + ComicInfo().apply { + title = "title" + summary = "summary" + number = "010" + year = 2020 + month = 2 + alternateSeries = "story arc" + alternateNumber = "5" + storyArc = "one, two, three" + web = "https://www.comixology.com/Sandman/digital-comic/727888" + tags = "dark, Occult" + gtin = "9783440077894" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -96,10 +97,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with StoryArcNumber when getting book metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - storyArc = "one" - storyArcNumber = "6" - } + val comicInfo = + ComicInfo().apply { + storyArc = "one" + storyArcNumber = "6" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -115,12 +117,13 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with multiple StoryArcNumber when getting book metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - alternateSeries = "story arc" - alternateNumber = "5" - storyArc = "one, two, three" - storyArcNumber = "6, 7, 8" - } + val comicInfo = + ComicInfo().apply { + alternateSeries = "story arc" + alternateNumber = "5" + storyArc = "one, two, three" + storyArcNumber = "6, 7, 8" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -139,10 +142,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with uneven StoryArcNumber when getting book metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - storyArc = "one, two" - storyArcNumber = "6, 7, 8" - } + val comicInfo = + ComicInfo().apply { + storyArc = "one, two" + storyArcNumber = "6, 7, 8" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -159,10 +163,11 @@ class ComicInfoProviderTest { @Test fun `given another comicInfo with uneven StoryArcNumber when getting book metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - storyArc = "one, two, three" - storyArcNumber = "6, 7" - } + val comicInfo = + ComicInfo().apply { + storyArc = "one, two, three" + storyArcNumber = "6, 7" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -179,10 +184,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with invalid StoryArcNumber when getting book metadata then invalid pairs are omitted`() { - val comicInfo = ComicInfo().apply { - storyArc = "one, two, three" - storyArcNumber = "6, x, 8" - } + val comicInfo = + ComicInfo().apply { + storyArc = "one, two, three" + storyArcNumber = "6, x, 8" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -199,10 +205,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with invalid StoryArc when getting book metadata then invalid pairs are omitted`() { - val comicInfo = ComicInfo().apply { - storyArc = "one, , three" - storyArcNumber = "6, 7, 8" - } + val comicInfo = + ComicInfo().apply { + storyArc = "one, , three" + storyArcNumber = "6, 7, 8" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -219,16 +226,17 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with blank values when getting series metadata then blank values are omitted`() { - val comicInfo = ComicInfo().apply { - title = "" - summary = "" - number = "" - alternateSeries = "" - alternateNumber = "" - storyArc = "" - penciller = "" - gtin = "" - } + val comicInfo = + ComicInfo().apply { + title = "" + summary = "" + number = "" + alternateSeries = "" + alternateNumber = "" + storyArc = "" + penciller = "" + gtin = "" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -247,9 +255,10 @@ class ComicInfoProviderTest { @Test fun `given comicInfo without year when getting book metadata then release date is null`() { - val comicInfo = ComicInfo().apply { - month = 2 - } + val comicInfo = + ComicInfo().apply { + month = 2 + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -262,9 +271,10 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with year but without month when getting book metadata then release date is set`() { - val comicInfo = ComicInfo().apply { - year = 2020 - } + val comicInfo = + ComicInfo().apply { + year = 2020 + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -277,16 +287,17 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with authors when getting book metadata then authors are set`() { - val comicInfo = ComicInfo().apply { - writer = "writer" - penciller = "penciller" - inker = "inker" - colorist = "colorist" - editor = "editor" - translator = "translator" - letterer = "letterer" - coverArtist = "coverArtist" - } + val comicInfo = + ComicInfo().apply { + writer = "writer" + penciller = "penciller" + inker = "inker" + colorist = "colorist" + editor = "editor" + translator = "translator" + letterer = "letterer" + coverArtist = "coverArtist" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -301,16 +312,17 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with multiple authors when getting book metadata then authors are set`() { - val comicInfo = ComicInfo().apply { - writer = "writer, writer2" - penciller = "penciller, penciller2" - inker = "inker, inker2" - colorist = "colorist, colorist2" - editor = "editor, editor2" - translator = "translator, translator2" - letterer = "letterer, letterer2" - coverArtist = "coverArtist, coverArtist2" - } + val comicInfo = + ComicInfo().apply { + writer = "writer, writer2" + penciller = "penciller, penciller2" + inker = "inker, inker2" + colorist = "colorist, colorist2" + editor = "editor, editor2" + translator = "translator, translator2" + letterer = "letterer, letterer2" + coverArtist = "coverArtist, coverArtist2" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -336,19 +348,19 @@ class ComicInfoProviderTest { @Nested inner class Series { - @Test fun `given comicInfo when getting series metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - series = "séries" - seriesGroup = "multiple,collections" - publisher = "publisher" - ageRating = AgeRating.MA_15 - manga = Manga.YES_AND_RIGHT_TO_LEFT - languageISO = "en" - count = 10 - genre = "Action, Adventure" - } + val comicInfo = + ComicInfo().apply { + series = "séries" + seriesGroup = "multiple,collections" + publisher = "publisher" + ageRating = AgeRating.MA_15 + manga = Manga.YES_AND_RIGHT_TO_LEFT + languageISO = "en" + count = 10 + genre = "Action, Adventure" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -371,10 +383,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with volume when getting series metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - series = "series" - volume = 2020 - } + val comicInfo = + ComicInfo().apply { + series = "series" + volume = 2020 + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -393,10 +406,11 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with volume as 1 when getting series metadata then metadata title omits volume`() { - val comicInfo = ComicInfo().apply { - series = "series" - volume = 1 - } + val comicInfo = + ComicInfo().apply { + series = "series" + volume = 1 + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -415,9 +429,10 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with incorrect values when getting series metadata then metadata patch is valid`() { - val comicInfo = ComicInfo().apply { - languageISO = "japanese" - } + val comicInfo = + ComicInfo().apply { + languageISO = "japanese" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -430,10 +445,14 @@ class ComicInfoProviderTest { @ParameterizedTest @MethodSource("languagesSource") - fun `given comicInfo with malformed BCP-47 language when getting series metadata then patch language is normalized`(source: String, expected: String) { - val comicInfo = ComicInfo().apply { - languageISO = source - } + fun `given comicInfo with malformed BCP-47 language when getting series metadata then patch language is normalized`( + source: String, + expected: String, + ) { + val comicInfo = + ComicInfo().apply { + languageISO = source + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -454,14 +473,15 @@ class ComicInfoProviderTest { @Test fun `given comicInfo with blank values when getting series metadata then blank values are omitted`() { - val comicInfo = ComicInfo().apply { - title = "" - storyArc = "" - genre = "" - languageISO = "" - publisher = "" - seriesGroup = "" - } + val comicInfo = + ComicInfo().apply { + title = "" + storyArc = "" + genre = "" + languageISO = "" + publisher = "" + seriesGroup = "" + } every { mockMapper.readValue(any(), ComicInfo::class.java) } returns comicInfo @@ -480,18 +500,23 @@ class ComicInfoProviderTest { companion object { @JvmStatic - fun computeSeriesFromSeriesAndVolumeArguments(): Stream = 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)"), - ) + fun computeSeriesFromSeriesAndVolumeArguments(): Stream = + 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?) { + 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) } } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProviderTest.kt index dc6e1f591..ee71d6b18 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/ReadListProviderTest.kt @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class ReadListProviderTest { - private val mockMapper = mockk() private val readListProvider = ReadListProvider(mockMapper) @@ -21,20 +20,22 @@ class ReadListProviderTest { @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" - }, - ) - } + 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(), ReadingList::class.java) } returns cbl @@ -61,24 +62,26 @@ class ReadListProviderTest { @Test fun `given CBL list with invalid books when getting ReadListRequest then exception is thrown`() { // 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 - }, - ) - } + 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(), ReadingList::class.java) } returns cbl @@ -93,10 +96,11 @@ class ReadListProviderTest { @Test fun `given CBL list without books when getting ReadListRequest then exception is thrown`() { // given - val cbl = ReadingList().apply { - name = "my read list" - books = emptyList() - } + val cbl = + ReadingList().apply { + name = "my read list" + books = emptyList() + } every { mockMapper.readValue(any(), ReadingList::class.java) } returns cbl @@ -111,20 +115,22 @@ class ReadListProviderTest { @Test fun `given CBL list without name when getting ReadListRequest then exception is thrown`() { // 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" - }, - ) - } + 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(), ReadingList::class.java) } returns cbl @@ -139,20 +145,22 @@ class ReadListProviderTest { @Test fun `given CBL list with blank name when getting ReadListRequest then exception is thrown`() { // given - val cbl = ReadingList().apply { - name = " " - books = listOf( - Book().apply { - series = "series 1" - number = "4" - volume = 2005 - }, - Book().apply { - series = "series 2" - number = "1" - }, - ) - } + 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(), ReadingList::class.java) } returns cbl diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfoTest.kt index 45aba7095..3c0c20074 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ComicInfoTest.kt @@ -6,13 +6,13 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class ComicInfoTest { - private val mapper = XmlMapper() @Test fun `given valid xml file when deserializing then properties are available`() { // language=XML - val xml = """ + val xml = + """ v01 - Preludes & Nocturnes - 30th Anniversary Edition @@ -51,7 +51,7 @@ class ComicInfoTest { ABC123 - """.trimIndent() + """.trimIndent() val comicInfo = mapper.readValue(xml) with(comicInfo) { @@ -80,7 +80,8 @@ class ComicInfoTest { @Test fun `given another valid xml file when deserializing then properties are available`() { // language=XML - val xml = """ + val xml = + """ v01 - Preludes & Nocturnes - 30th Anniversary Edition @@ -119,7 +120,7 @@ class ComicInfoTest { Sandman - """.trimIndent() + """.trimIndent() val comicInfo = mapper.readValue(xml) with(comicInfo) { @@ -148,14 +149,15 @@ class ComicInfoTest { @Test fun `given incorrect enum values when deserializing then it is ignored`() { // language=XML - val xml = """ + val xml = + """ Non existent Non existent Non existent - """.trimIndent() + """.trimIndent() val comicInfo = mapper.readValue(xml) with(comicInfo) { @@ -168,13 +170,14 @@ class ComicInfoTest { @Test fun `given valid xml file with StoryArc fields when deserializing then properties are available`() { // language=XML - val xml = """ + val xml = + """ Arc 2 - """.trimIndent() + """.trimIndent() val comicInfo = mapper.readValue(xml) with(comicInfo) { diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingListTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingListTest.kt index a9713a6e7..7e9b89cec 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingListTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/comicrack/dto/ReadingListTest.kt @@ -6,13 +6,13 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class ReadingListTest { - private val mapper = XmlMapper() @Test fun `given valid xml file when deserializing then properties are available`() { // language=XML - val cbl = """ + val cbl = + """ Civil War @@ -32,7 +32,7 @@ class ReadingListTest { - """.trimIndent() + """.trimIndent() val readingList = mapper.readValue(cbl) with(readingList) { @@ -68,7 +68,8 @@ class ReadingListTest { @Test fun `given valid xml file for smart list when deserializing then properties are available`() { // language=XML - val cbl = """ + val cbl = + """ Golden Age @@ -82,7 +83,7 @@ class ReadingListTest { - """.trimIndent() + """.trimIndent() val readingList = mapper.readValue(cbl) with(readingList) { diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProviderTest.kt index dd6a0c69c..dc0a6a2b7 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/epub/EpubMetadataProviderTest.kt @@ -18,17 +18,17 @@ import org.springframework.core.io.ClassPathResource import java.time.LocalDate class EpubMetadataProviderTest { - private val isbnValidator = ISBNValidator(true) private val epubMetadataProvider = EpubMetadataProvider(isbnValidator) private val epubMetadataProviderProper = EpubMetadataProvider(ISBNValidator(true)) private val book = makeBook("book") - private val media = Media( - status = Media.Status.READY, - mediaType = "application/epub+zip", - ) + private val media = + Media( + status = Media.Status.READY, + mediaType = "application/epub+zip", + ) @AfterEach fun cleanup() { @@ -37,7 +37,6 @@ class EpubMetadataProviderTest { @Nested inner class Book { - @Test fun `given epub 3 opf when getting book metadata then metadata patch is valid`() { val opf = ClassPathResource("epub/Panik im Paradies.opf") @@ -81,10 +80,11 @@ class EpubMetadataProviderTest { @Test fun `given real epub 3 when getting book metadata then metadata patch is valid`() { val epubResource = ClassPathResource("epub/The Incomplete Theft - Ralph Burke.epub") - val epubBook = BookWithMedia( - makeBook("Epub", url = epubResource.url), - media, - ) + val epubBook = + BookWithMedia( + makeBook("Epub", url = epubResource.url), + media, + ) val patch = epubMetadataProviderProper.getBookMetadataFromBook(epubBook) @@ -120,7 +120,6 @@ class EpubMetadataProviderTest { @Nested inner class Series { - @Test fun `given epub 3 opf when getting series metadata then metadata patch is valid`() { val opf = ClassPathResource("epub/Panik im Paradies.opf") diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProviderTest.kt index 80b6c8d90..f6a36ff55 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/localartwork/LocalArtworkProviderTest.kt @@ -18,19 +18,19 @@ import java.time.LocalDateTime import kotlin.io.path.extension class LocalArtworkProviderTest { - - private val contentDetector = spyk(ContentDetector(TikaConfiguration().tika())).also { - every { it.detectMediaType(any()) } answers { - when (firstArg().extension.lowercase()) { - "jpg", "jpeg", "tbn" -> "image/jpeg" - "png" -> "image/png" - "webp" -> "image/webp" - "avif" -> "image/avif" - "jxl" -> "image/jxl" - else -> "application/octet-stream" + private val contentDetector = + spyk(ContentDetector(TikaConfiguration().tika())).also { + every { it.detectMediaType(any()) } answers { + when (firstArg().extension.lowercase()) { + "jpg", "jpeg", "tbn" -> "image/jpeg" + "png" -> "image/png" + "webp" -> "image/webp" + "avif" -> "image/avif" + "jxl" -> "image/jxl" + else -> "application/octet-stream" + } } } - } private val localMediaAssetsProvider = LocalArtworkProvider(contentDetector, ImageAnalyzer()) @@ -48,13 +48,14 @@ class LocalArtworkProviderTest { (thumbsFiles + thumbsDashFiles + invalidFiles).forEach { Files.createFile(root.resolve(it)) } - val book = spyk( - Book( - name = "Book", - url = bookFile.toUri().toURL(), - fileLastModified = LocalDateTime.now(), - ), - ) + val book = + spyk( + Book( + name = "Book", + url = bookFile.toUri().toURL(), + fileLastModified = LocalDateTime.now(), + ), + ) every { book.path } returns bookFile // when @@ -82,13 +83,14 @@ class LocalArtworkProviderTest { (thumbsFiles + invalidFiles).forEach { Files.createFile(seriesPath.resolve(it)) } - val series = spyk( - Series( - name = "Series", - url = seriesFile.toUri().toURL(), - fileLastModified = LocalDateTime.now(), - ), - ) + val series = + spyk( + Series( + name = "Series", + url = seriesFile.toUri().toURL(), + fileLastModified = LocalDateTime.now(), + ), + ) every { series.path } returns seriesFile // when diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProviderTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProviderTest.kt index c821000c3..8a3582204 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProviderTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/MylarSeriesProviderTest.kt @@ -18,7 +18,6 @@ import java.nio.file.Files import java.nio.file.Path class MylarSeriesProviderTest { - private val mockMapper = mockk() private val mylarSeriesProvider = MylarSeriesProvider(mockMapper) @@ -26,30 +25,33 @@ class MylarSeriesProviderTest { private lateinit var series: org.gotson.komga.domain.model.Series @BeforeAll - fun setupSeries(@TempDir dir: Path) { + fun setupSeries( + @TempDir dir: Path, + ) { Files.createFile(dir.resolve("series.json")) series = makeSeries("series", url = dir.toUri().toURL()) } @Test fun `given seriesJson when getting series metadata then metadata patch is valid`() { - val metadata = MylarMetadata( - type = "comicSeries", - publisher = "DC", - imprint = "Vertigo", - name = "Sàndman", - comicid = "12345", - year = 1990, - descriptionText = "Sandman comics", - descriptionFormatted = "Sandman comics formatted", - volume = null, - bookType = "TPB", - ageRating = AgeRating.ADULT, - comicImage = "unused", - totalIssues = 2, - publicationRun = "unused", - status = Status.Ended, - ) + val metadata = + MylarMetadata( + type = "comicSeries", + publisher = "DC", + imprint = "Vertigo", + name = "Sàndman", + comicid = "12345", + year = 1990, + descriptionText = "Sandman comics", + descriptionFormatted = "Sandman comics formatted", + volume = null, + bookType = "TPB", + ageRating = AgeRating.ADULT, + comicImage = "unused", + totalIssues = 2, + publicationRun = "unused", + status = Status.Ended, + ) val root = Series(metadata) every { mockMapper.readValue(any(), Series::class.java) } returns root @@ -73,23 +75,24 @@ class MylarSeriesProviderTest { @Test fun `given another seriesJson when getting series metadata then metadata patch is valid`() { - val metadata = MylarMetadata( - type = "comicSeries", - publisher = "DC", - imprint = "Vertigo", - name = "Sandman", - comicid = "12345", - year = 1990, - descriptionText = "Sandman comics", - descriptionFormatted = null, - volume = null, - bookType = "TPB", - ageRating = null, - comicImage = "unused", - totalIssues = 2, - publicationRun = "unused", - status = Status.Continuing, - ) + val metadata = + MylarMetadata( + type = "comicSeries", + publisher = "DC", + imprint = "Vertigo", + name = "Sandman", + comicid = "12345", + year = 1990, + descriptionText = "Sandman comics", + descriptionFormatted = null, + volume = null, + bookType = "TPB", + ageRating = null, + comicImage = "unused", + totalIssues = 2, + publicationRun = "unused", + status = Status.Continuing, + ) val root = Series(metadata) every { mockMapper.readValue(any(), Series::class.java) } returns root @@ -113,23 +116,24 @@ class MylarSeriesProviderTest { @Test fun `given seriesJson with volume != 1 and year when getting series metadata then metadata patch has title containing the year`() { - val metadata = MylarMetadata( - type = "comicSeries", - publisher = "DC", - imprint = "Vertigo", - name = "Sandman", - comicid = "12345", - year = 1990, - descriptionText = "Sandman comics", - descriptionFormatted = "Sandman comics formatted", - volume = 2, - bookType = "TPB", - ageRating = AgeRating.ADULT, - comicImage = "unused", - totalIssues = 2, - publicationRun = "unused", - status = Status.Ended, - ) + val metadata = + MylarMetadata( + type = "comicSeries", + publisher = "DC", + imprint = "Vertigo", + name = "Sandman", + comicid = "12345", + year = 1990, + descriptionText = "Sandman comics", + descriptionFormatted = "Sandman comics formatted", + volume = 2, + bookType = "TPB", + ageRating = AgeRating.ADULT, + comicImage = "unused", + totalIssues = 2, + publicationRun = "unused", + status = Status.Ended, + ) val root = Series(metadata) every { mockMapper.readValue(any(), Series::class.java) } returns root @@ -144,23 +148,24 @@ class MylarSeriesProviderTest { @Test fun `given seriesJson with volume == 1 and year when getting series metadata then metadata patch has title not containing the year`() { - val metadata = MylarMetadata( - type = "comicSeries", - publisher = "DC", - imprint = "Vertigo", - name = "Sandman", - comicid = "12345", - year = 1990, - descriptionText = "Sandman comics", - descriptionFormatted = "Sandman comics formatted", - volume = 1, - bookType = "TPB", - ageRating = AgeRating.ADULT, - comicImage = "unused", - totalIssues = 2, - publicationRun = "unused", - status = Status.Ended, - ) + val metadata = + MylarMetadata( + type = "comicSeries", + publisher = "DC", + imprint = "Vertigo", + name = "Sandman", + comicid = "12345", + year = 1990, + descriptionText = "Sandman comics", + descriptionFormatted = "Sandman comics formatted", + volume = 1, + bookType = "TPB", + ageRating = AgeRating.ADULT, + comicImage = "unused", + totalIssues = 2, + publicationRun = "unused", + status = Status.Ended, + ) val root = Series(metadata) every { mockMapper.readValue(any(), Series::class.java) } returns root @@ -175,23 +180,24 @@ class MylarSeriesProviderTest { @Test fun `given seriesJson with volume == null and year when getting series metadata then metadata patch has title not containing the year`() { - val metadata = MylarMetadata( - type = "comicSeries", - publisher = "DC", - imprint = "Vertigo", - name = "Sandman", - comicid = "12345", - year = 1990, - descriptionText = "Sandman comics", - descriptionFormatted = "Sandman comics formatted", - volume = null, - bookType = "TPB", - ageRating = AgeRating.ADULT, - comicImage = "unused", - totalIssues = 2, - publicationRun = "unused", - status = Status.Ended, - ) + val metadata = + MylarMetadata( + type = "comicSeries", + publisher = "DC", + imprint = "Vertigo", + name = "Sandman", + comicid = "12345", + year = 1990, + descriptionText = "Sandman comics", + descriptionFormatted = "Sandman comics formatted", + volume = null, + bookType = "TPB", + ageRating = AgeRating.ADULT, + comicImage = "unused", + totalIssues = 2, + publicationRun = "unused", + status = Status.Ended, + ) val root = Series(metadata) every { mockMapper.readValue(any(), Series::class.java) } returns root diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/SeriesTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/SeriesTest.kt index 2f228db73..bc81be033 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/SeriesTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/metadata/mylar/dto/SeriesTest.kt @@ -13,11 +13,11 @@ import org.springframework.boot.test.context.SpringBootTest class SeriesTest( @Autowired private val mapper: ObjectMapper, ) { - @Test fun `given valid json file when deserializing then properties are available`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -39,7 +39,7 @@ class SeriesTest( "status": "Continuing" } } - """.trimIndent() + """.trimIndent() val seriesJson = mapper.readValue(json) assertThat(seriesJson.metadata).isNotNull @@ -65,7 +65,8 @@ class SeriesTest( @Test fun `given another valid json file when deserializing then properties are available`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -87,7 +88,7 @@ class SeriesTest( "status": "Ended" } } - """.trimIndent() + """.trimIndent() val seriesJson = mapper.readValue(json) assertThat(seriesJson.metadata).isNotNull @@ -113,7 +114,8 @@ class SeriesTest( @Test fun `given yet another valid json file when deserializing then properties are available`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -135,7 +137,7 @@ class SeriesTest( "status": "Ended" } } - """.trimIndent() + """.trimIndent() val seriesJson = mapper.readValue(json) assertThat(seriesJson.metadata).isNotNull @@ -161,7 +163,8 @@ class SeriesTest( @Test fun `given invalid json file missing year when deserializing then it fails`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -182,7 +185,7 @@ class SeriesTest( "status": "Ended" } } - """.trimIndent() + """.trimIndent() val thrown = catchThrowable { mapper.readValue(json) } assertThat(thrown).isInstanceOf(MismatchedInputException::class.java) @@ -191,7 +194,8 @@ class SeriesTest( @Test fun `given invalid json file missing publisher when deserializing then it fails`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -212,7 +216,7 @@ class SeriesTest( "status": "Ended" } } - """.trimIndent() + """.trimIndent() val thrown = catchThrowable { mapper.readValue(json) } assertThat(thrown).isInstanceOf(MismatchedInputException::class.java) @@ -221,7 +225,8 @@ class SeriesTest( @Test fun `given invalid json file missing status when deserializing then it fails`() { // language=JSON - val json = """ + val json = + """ { "version": "1.0.1", "metadata": { @@ -243,7 +248,7 @@ class SeriesTest( } } - """.trimIndent() + """.trimIndent() val thrown = catchThrowable { mapper.readValue(json) } assertThat(thrown).isInstanceOf(MismatchedInputException::class.java) diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualAnalyzerTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualAnalyzerTest.kt index 0c5dedde7..8e7e98975 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualAnalyzerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualAnalyzerTest.kt @@ -4,7 +4,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MultilingualAnalyzerTest { - private val analyzer = MultiLingualAnalyzer() @Test diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualNGramAnalyzerTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualNGramAnalyzerTest.kt index 44421aa75..e582e50cf 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualNGramAnalyzerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/MultilingualNGramAnalyzerTest.kt @@ -4,7 +4,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MultilingualNGramAnalyzerTest { - @Test fun `single letter`() { // given diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycleTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycleTest.kt index 557677d6f..4293662d7 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycleTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/search/SearchIndexLifecycleTest.kt @@ -48,7 +48,6 @@ class SearchIndexLifecycleTest( @Autowired private val searchIndexLifecycle: SearchIndexLifecycle, @Autowired private val luceneHelper: LuceneHelper, ) { - private val library = makeLibrary() @MockkBean diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/SessionTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/SessionTest.kt index 65a2f36f7..0413371a8 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/SessionTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/SessionTest.kt @@ -25,7 +25,6 @@ class SessionTest( @Autowired private val sessionHeaderName: String, @Autowired private val sessionCookieName: String, ) { - private lateinit var user: KomgaUser @BeforeAll @@ -70,10 +69,11 @@ class SessionTest( @Test fun `given existing session when exchanging for cookies then session is returned in cookies`() { - val sessionId = mockMvc.get("/api/v2/users/me") { - with(httpBasic(user.email, user.password)) - header(sessionHeaderName, "") - }.andReturn().response.getHeader(this.sessionHeaderName) + val sessionId = + mockMvc.get("/api/v2/users/me") { + with(httpBasic(user.email, user.password)) + header(sessionHeaderName, "") + }.andReturn().response.getHeader(this.sessionHeaderName) assertThat(sessionId).isNotNull @@ -93,10 +93,11 @@ class SessionTest( @Test fun `given existing session when logging out then session cookie is cleared`() { - val sessionId = mockMvc.get("/api/v2/users/me") { - with(httpBasic(user.email, user.password)) - header(sessionHeaderName, "") - }.andReturn().response.getHeader(this.sessionHeaderName) + val sessionId = + mockMvc.get("/api/v2/users/me") { + with(httpBasic(user.email, user.password)) + header(sessionHeaderName, "") + }.andReturn().response.getHeader(this.sessionHeaderName) assertThat(sessionId).isNotNull diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/opds/OpdsControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/opds/OpdsControllerTest.kt index bb02f7647..e1d762786 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/opds/OpdsControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/opds/OpdsControllerTest.kt @@ -43,7 +43,6 @@ class OpdsControllerTest( @Autowired private val userLifecycle: KomgaUserLifecycle, @Autowired private val mockMvc: MockMvc, ) { - private val library = makeLibrary(id = "1") private val user = KomgaUser("user@example.org", "", false, id = "1") private val user2 = KomgaUser("user2@example.org", "", false, id = "2") @@ -75,12 +74,13 @@ class OpdsControllerTest( @Test @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = ["1"]) fun `given user with access to a single library when getting series then only gets series from this library`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).let { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).let { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val otherLibrary = makeLibrary("other") libraryRepository.insert(otherLibrary) @@ -107,42 +107,46 @@ class OpdsControllerTest( @Test @WithMockCustomUser(allowAgeUnder = 10) fun `given user only allowed content with specific age rating when getting series then only gets series that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) - } - } - val series5 = makeSeries(name = "series_5", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series5 = + makeSeries(name = "series_5", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 5)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 5)) - } - } - val series15 = makeSeries(name = "series_15", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series15 = + makeSeries(name = "series_15", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 15)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 15)) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/opds/v1.2/series/${series5.id}").andExpect { status { isOk() } } mockMvc.get("/opds/v1.2/series/${series10.id}").andExpect { status { isOk() } } @@ -161,42 +165,46 @@ class OpdsControllerTest( @Test @WithMockCustomUser(excludeAgeOver = 16) fun `given user disallowed content with specific age rating when getting series then only gets series that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) - } - } - val series18 = makeSeries(name = "series_18", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series18 = + makeSeries(name = "series_18", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 18)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 18)) - } - } - val series16 = makeSeries(name = "series_16", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series16 = + makeSeries(name = "series_16", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 16)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 16)) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/opds/v1.2/series/${series.id}").andExpect { status { isOk() } } mockMvc.get("/opds/v1.2/series/${series10.id}").andExpect { status { isOk() } } @@ -276,12 +284,13 @@ class OpdsControllerTest( @Test @WithMockCustomUser fun `given books with unordered index when requesting via opds then books are ordered`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val addedBook = makeBook("2", libraryId = library.id) seriesLifecycle.addBooks(createdSeries, listOf(addedBook)) @@ -308,12 +317,13 @@ class OpdsControllerTest( @Test @WithMockCustomUser fun `given books not ready when requesting via opds then no books are returned`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } bookRepository.findAll().forEach { mediaRepository.findById(it.id).let { media -> @@ -337,12 +347,13 @@ class OpdsControllerTest( @Test @WithMockCustomUser fun `given deleted ready books when requesting via opds then no books are returned`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val addedBook = makeBook("2", libraryId = library.id) seriesLifecycle.addBooks(createdSeries, listOf(addedBook)) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ActuatorTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ActuatorTest.kt index ddbec62b3..f412a0758 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ActuatorTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ActuatorTest.kt @@ -18,7 +18,6 @@ import org.springframework.test.web.servlet.get class ActuatorTest( @Autowired private val mockMvc: MockMvc, ) { - @Test @WithAnonymousUser fun `given anonymous user when getting actuator endpoints then returns unauthorized`() { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementControllerTest.kt index 7add1b2b6..a84cd94d9 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/AnnouncementControllerTest.kt @@ -20,40 +20,40 @@ import java.time.ZoneOffset class AnnouncementControllerTest( @Autowired private val mockMvc: MockMvc, ) { - @SpykBean private lateinit var announcementController: AnnouncementController - private val mockFeed = JsonFeedDto( - "https://jsonfeed.org/version/1", - "Announcements", - "https://komga.org/blog", - "Latest Komga announcements", - listOf( - JsonFeedDto.ItemDto( - "https://komga.org/blog/prepare-v1", - "https://komga.org/blog/prepare-v1", - "Prepare for v1.0.0", - "The future v1.0.0 will bring some breaking changes, this guide will help you to prepare for the next major version.", - """ + private val mockFeed = + JsonFeedDto( + "https://jsonfeed.org/version/1", + "Announcements", + "https://komga.org/blog", + "Latest Komga announcements", + listOf( + JsonFeedDto.ItemDto( + "https://komga.org/blog/prepare-v1", + "https://komga.org/blog/prepare-v1", + "Prepare for v1.0.0", + "The future v1.0.0 will bring some breaking changes, this guide will help you to prepare for the next major version.", + """ You can still change the port through configuration Another link here. A normal link. - """.trimIndent(), - OffsetDateTime.of( - LocalDate.of(2023, 3, 21).atStartOfDay(), - ZoneOffset.UTC, + """.trimIndent(), + OffsetDateTime.of( + LocalDate.of(2023, 3, 21).atStartOfDay(), + ZoneOffset.UTC, + ), + JsonFeedDto.AuthorDto("gotson", "https://github.com/gotson"), + setOf( + "breaking change", + "upgrade", + "komga", + ), + null, ), - JsonFeedDto.AuthorDto("gotson", "https://github.com/gotson"), - setOf( - "breaking change", - "upgrade", - "komga", - ), - null, ), - ), - ) + ) @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerPageTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerPageTest.kt index 4f47da3a7..8942215f9 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerPageTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerPageTest.kt @@ -75,7 +75,12 @@ class BookControllerPageTest( @ParameterizedTest @MethodSource("arguments") @WithMockCustomUser - fun `given pdf book when getting page with Accept header then returns page in correct format`(bookType: String, acceptTypes: List, success: Boolean, resultType: String?) { + fun `given pdf book when getting page with Accept header then returns page in correct format`( + bookType: String, + acceptTypes: List, + success: Boolean, + resultType: String?, + ) { makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).let { created -> val books = listOf(makeBook("1", libraryId = library.id)) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerTest.kt index 473cfad76..34857d59f 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/BookControllerTest.kt @@ -70,7 +70,6 @@ class BookControllerTest( @Autowired private val userLifecycle: KomgaUserLifecycle, @Autowired private val mockMvc: MockMvc, ) { - private val library = makeLibrary(id = "1") private val user = KomgaUser("user@example.org", "", false, id = "1") private val user2 = KomgaUser("user2@example.org", "", false, id = "2") @@ -663,7 +662,6 @@ class BookControllerTest( @Nested inner class Siblings { - @Test @WithMockCustomUser fun `given series with multiple books when getting siblings then it is returned or not found`() { @@ -712,12 +710,13 @@ class BookControllerTest( @Test @WithMockCustomUser fun `given regular user when getting books then full url is hidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1.cbr", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1.cbr", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val book = bookRepository.findAll().first() @@ -745,12 +744,13 @@ class BookControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given admin user when getting books then full url is available`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1.cbr", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1.cbr", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val book = bookRepository.findAll().first() @@ -803,8 +803,9 @@ class BookControllerTest( val url = "/api/v1/books/${book.id}/thumbnail" - val response = mockMvc.get(url) - .andReturn().response + val response = + mockMvc.get(url) + .andReturn().response mockMvc.get(url) { headers { @@ -829,8 +830,9 @@ class BookControllerTest( val url = "/api/v1/books/${book.id}/pages/1" - val lastModified = mockMvc.get(url) - .andReturn().response.getHeader(HttpHeaders.LAST_MODIFIED) + val lastModified = + mockMvc.get(url) + .andReturn().response.getHeader(HttpHeaders.LAST_MODIFIED) mockMvc.get(url) { headers { @@ -937,7 +939,8 @@ class BookControllerTest( val bookId = bookRepository.findAll().first().id // language=JSON - val jsonString = """ + val jsonString = + """ { "title":"newTitle", "titleLock":true, @@ -965,7 +968,7 @@ class BookControllerTest( "isbn":"978-161-729-045-9abc xxxoefj", "isbnLock":true } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/$bookId/metadata") { contentType = MediaType.APPLICATION_JSON @@ -1014,21 +1017,23 @@ class BookControllerTest( val bookId = bookRepository.findAll().first().id bookMetadataRepository.findById(bookId).let { metadata -> - val updated = metadata.copy( - summary = "summary", - isbn = "9781617290459", - ) + val updated = + metadata.copy( + summary = "summary", + isbn = "9781617290459", + ) bookMetadataRepository.update(updated) } // language=JSON - val jsonString = """ + val jsonString = + """ { "summary":"", "isbn":"" } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/$bookId/metadata") { contentType = MediaType.APPLICATION_JSON @@ -1058,13 +1063,14 @@ class BookControllerTest( val bookId = bookRepository.findAll().first().id bookMetadataRepository.findById(bookId).let { metadata -> - val updated = metadata.copy( - authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) }, - releaseDate = testDate, - tags = setOf("tag"), - summary = "summary", - isbn = "9781617290459", - ) + val updated = + metadata.copy( + authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) }, + releaseDate = testDate, + tags = setOf("tag"), + summary = "summary", + isbn = "9781617290459", + ) bookMetadataRepository.update(updated) } @@ -1076,7 +1082,8 @@ class BookControllerTest( } // language=JSON - val jsonString = """ + val jsonString = + """ { "authors":null, "releaseDate":null, @@ -1084,7 +1091,7 @@ class BookControllerTest( "summary":null, "isbn":null } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/$bookId/metadata") { contentType = MediaType.APPLICATION_JSON @@ -1117,26 +1124,28 @@ class BookControllerTest( val bookId = bookRepository.findAll().first().id bookMetadataRepository.findById(bookId).let { metadata -> - val updated = metadata.copy( - authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) }, - releaseDate = testDate, - summary = "summary", - number = "number", - numberLock = true, - numberSort = 2F, - numberSortLock = true, - title = "title", - isbn = "9781617290459", - ) + val updated = + metadata.copy( + authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) }, + releaseDate = testDate, + summary = "summary", + number = "number", + numberLock = true, + numberSort = 2F, + numberSortLock = true, + title = "title", + isbn = "9781617290459", + ) bookMetadataRepository.update(updated) } // language=JSON - val jsonString = """ + val jsonString = + """ { } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/$bookId/metadata") { contentType = MediaType.APPLICATION_JSON @@ -1160,7 +1169,6 @@ class BookControllerTest( @Nested inner class ReadProgress { - @ParameterizedTest @ValueSource( strings = [ @@ -1201,11 +1209,12 @@ class BookControllerTest( } // language=JSON - val jsonString = """ + val jsonString = + """ { "page": 5 } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/${book.id}/read-progress") { contentType = MediaType.APPLICATION_JSON @@ -1244,11 +1253,12 @@ class BookControllerTest( } // language=JSON - val jsonString = """ + val jsonString = + """ { "completed": true } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/${book.id}/read-progress") { contentType = MediaType.APPLICATION_JSON @@ -1287,12 +1297,13 @@ class BookControllerTest( } // language=JSON - val jsonString = """ + val jsonString = + """ { "page": 5, "completed": false } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/books/${book.id}/read-progress") { contentType = MediaType.APPLICATION_JSON @@ -1335,11 +1346,12 @@ class BookControllerTest( } // language=JSON - val jsonString = """ - { - "completed": true - } - """.trimIndent() + val jsonString = + """ + { + "completed": true + } + """.trimIndent() mockMvc.perform( MockMvcRequestBuilders @@ -1372,8 +1384,9 @@ class BookControllerTest( @WithMockCustomUser fun `given book with Unicode name when getting book file then attachment name is correct`() { val bookName = "アキラ" - val tempFile = Files.createTempFile(bookName, ".cbz") - .also { it.toFile().deleteOnExit() } + val tempFile = + Files.createTempFile(bookName, ".cbz") + .also { it.toFile().deleteOnExit() } makeSeries(name = "series", libraryId = library.id).let { series -> seriesLifecycle.createSeries(series).let { created -> val books = listOf(makeBook(bookName, libraryId = library.id, url = tempFile.toUri().toURL())) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ClaimControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ClaimControllerTest.kt index 52340a7b8..3812ff027 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ClaimControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ClaimControllerTest.kt @@ -17,7 +17,6 @@ import org.springframework.test.web.servlet.post @ActiveProfiles("test") class ClaimControllerTest( @Autowired private val mockMvc: MockMvc, - ) { @ParameterizedTest @ValueSource(strings = ["user", "user@domain"]) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemControllerTest.kt index 24f8f8d10..7d5a2a5c1 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/FileSystemControllerTest.kt @@ -47,7 +47,9 @@ class FileSystemControllerTest( @Test @WithMockUser(roles = [ROLE_USER, ROLE_ADMIN]) - fun `given non-existent path param when getDirectoryListing then return bad request`(@TempDir parent: Path) { + fun `given non-existent path param when getDirectoryListing then return bad request`( + @TempDir parent: Path, + ) { Files.delete(parent) mockMvc.post(route) { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/LibraryControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/LibraryControllerTest.kt index bd0522c5f..971875a2b 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/LibraryControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/LibraryControllerTest.kt @@ -29,7 +29,6 @@ class LibraryControllerTest( @Autowired private val mockMvc: MockMvc, @Autowired private val libraryRepository: LibraryRepository, ) { - private val route = "/api/v1/libraries" private val library = makeLibrary(path = "file:/library1", id = "1") @@ -161,15 +160,18 @@ class LibraryControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) - fun `given library with exclusions when updating library then exclusions are updated`(@TempDir tmp: Path) { + fun `given library with exclusions when updating library then exclusions are updated`( + @TempDir tmp: Path, + ) { libraryRepository.update(library.copy(root = tmp.toUri().toURL(), scanDirectoryExclusions = setOf("test", "value"))) // language=JSON - val jsonString = """ + val jsonString = + """ { "scanDirectoryExclusions": ["updated"] } - """.trimIndent() + """.trimIndent() mockMvc.patch("$route/${library.id}") { contentType = MediaType.APPLICATION_JSON diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/MockSpringSecurity.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/MockSpringSecurity.kt index a1153aa05..e423e9e22 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/MockSpringSecurity.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/MockSpringSecurity.kt @@ -33,25 +33,31 @@ class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory= 0) AgeRestriction(customUser.allowAgeUnder, AllowExclude.ALLOW_ONLY) - else if (customUser.excludeAgeOver >= 0) AgeRestriction(customUser.excludeAgeOver, AllowExclude.EXCLUDE) - else null, - labelsAllow = customUser.allowLabels.toSet(), - labelsExclude = customUser.excludeLabels.toSet(), + val principal = + KomgaPrincipal( + KomgaUser( + email = customUser.email, + password = "", + roleAdmin = customUser.roles.contains(ROLE_ADMIN), + roleFileDownload = customUser.roles.contains(ROLE_FILE_DOWNLOAD), + rolePageStreaming = customUser.roles.contains(ROLE_PAGE_STREAMING), + sharedAllLibraries = customUser.sharedAllLibraries, + sharedLibrariesIds = customUser.sharedLibraries.toSet(), + restrictions = + ContentRestrictions( + ageRestriction = + if (customUser.allowAgeUnder >= 0) + AgeRestriction(customUser.allowAgeUnder, AllowExclude.ALLOW_ONLY) + else if (customUser.excludeAgeOver >= 0) + AgeRestriction(customUser.excludeAgeOver, AllowExclude.EXCLUDE) + else + null, + labelsAllow = customUser.allowLabels.toSet(), + labelsExclude = customUser.excludeLabels.toSet(), + ), + id = customUser.id, ), - id = customUser.id, - ), - ) + ) val auth = UsernamePasswordAuthenticationToken(principal, "", principal.authorities) context.authentication = auth return context diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2ControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2ControllerTest.kt index 091e7de35..bd9a2befb 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2ControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/OAuth2ControllerTest.kt @@ -15,7 +15,6 @@ import org.springframework.test.web.servlet.get class OAuth2ControllerTest( @Autowired private val mockMvc: MockMvc, ) { - @Test @WithAnonymousUser fun `given anonymous user when getting oauth2 providers then returns OK`() { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ReadListControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ReadListControllerTest.kt index b32d76274..e845b90e7 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ReadListControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/ReadListControllerTest.kt @@ -49,7 +49,6 @@ class ReadListControllerTest( @Autowired private val seriesMetadataRepository: SeriesMetadataRepository, @Autowired private val bookMetadataRepository: BookMetadataRepository, ) { - private val library1 = makeLibrary("Library1", id = "1") private val library2 = makeLibrary("Library2", id = "2") private val seriesLib1 = makeSeries("Series1", library1.id) @@ -88,26 +87,29 @@ class ReadListControllerTest( } private fun makeReadLists() { - rlLib1 = readListLifecycle.addReadList( - ReadList( - name = "Lib1", - bookIds = booksLibrary1.map { it.id }.toIndexedMap(), - ), - ) + rlLib1 = + readListLifecycle.addReadList( + ReadList( + name = "Lib1", + bookIds = booksLibrary1.map { it.id }.toIndexedMap(), + ), + ) - rlLib2 = readListLifecycle.addReadList( - ReadList( - name = "Lib2", - bookIds = booksLibrary2.map { it.id }.toIndexedMap(), - ), - ) + rlLib2 = + readListLifecycle.addReadList( + ReadList( + name = "Lib2", + bookIds = booksLibrary2.map { it.id }.toIndexedMap(), + ), + ) - rlLibBoth = readListLifecycle.addReadList( - ReadList( - name = "Lib1+2", - bookIds = (booksLibrary1 + booksLibrary2).map { it.id }.toIndexedMap(), - ), - ) + rlLibBoth = + readListLifecycle.addReadList( + ReadList( + name = "Lib1+2", + bookIds = (booksLibrary1 + booksLibrary2).map { it.id }.toIndexedMap(), + ), + ) } @Nested @@ -203,26 +205,29 @@ class ReadListControllerTest( } } - val rlAllowed = readListLifecycle.addReadList( - ReadList( - name = "Allowed", - bookIds = listOf(book10.id).toIndexedMap(), - ), - ) + val rlAllowed = + readListLifecycle.addReadList( + ReadList( + name = "Allowed", + bookIds = listOf(book10.id).toIndexedMap(), + ), + ) - val rlFiltered = readListLifecycle.addReadList( - ReadList( - name = "Filtered", - bookIds = listOf(book10.id, book.id).toIndexedMap(), - ), - ) + val rlFiltered = + readListLifecycle.addReadList( + ReadList( + name = "Filtered", + bookIds = listOf(book10.id, book.id).toIndexedMap(), + ), + ) - val rlDenied = readListLifecycle.addReadList( - ReadList( - name = "Denied", - bookIds = listOf(book.id).toIndexedMap(), - ), - ) + val rlDenied = + readListLifecycle.addReadList( + ReadList( + name = "Denied", + bookIds = listOf(book.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists") .andExpect { @@ -326,27 +331,30 @@ class ReadListControllerTest( bookMetadataRepository.findById(book16.id).let { bookMetadataRepository.update(it.copy(releaseDate = LocalDate.of(2002, 1, 1))) } bookMetadataRepository.findById(book18.id).let { bookMetadataRepository.update(it.copy(releaseDate = LocalDate.of(2003, 1, 1))) } - val rlAllowed = readListLifecycle.addReadList( - ReadList( - name = "Allowed", - bookIds = listOf(book10.id, book.id).toIndexedMap(), - ), - ) + val rlAllowed = + readListLifecycle.addReadList( + ReadList( + name = "Allowed", + bookIds = listOf(book10.id, book.id).toIndexedMap(), + ), + ) - val rlFiltered = readListLifecycle.addReadList( - ReadList( - name = "Filtered", - ordered = false, - bookIds = listOf(book10.id, book.id, book16.id, book18.id).toIndexedMap(), - ), - ) + val rlFiltered = + readListLifecycle.addReadList( + ReadList( + name = "Filtered", + ordered = false, + bookIds = listOf(book10.id, book.id, book16.id, book18.id).toIndexedMap(), + ), + ) - val rlDenied = readListLifecycle.addReadList( - ReadList( - name = "Denied", - bookIds = listOf(book16.id, book18.id).toIndexedMap(), - ), - ) + val rlDenied = + readListLifecycle.addReadList( + ReadList( + name = "Denied", + bookIds = listOf(book16.id, book18.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists") .andExpect { @@ -457,26 +465,29 @@ class ReadListControllerTest( } } - val rlAllowed = readListLifecycle.addReadList( - ReadList( - name = "Allowed", - bookIds = listOf(bookAdult.id, book.id).toIndexedMap(), - ), - ) + val rlAllowed = + readListLifecycle.addReadList( + ReadList( + name = "Allowed", + bookIds = listOf(bookAdult.id, book.id).toIndexedMap(), + ), + ) - val rlFiltered = readListLifecycle.addReadList( - ReadList( - name = "Filtered", - bookIds = listOf(bookKids.id, book.id, bookAdult.id, bookCute.id).toIndexedMap(), - ), - ) + val rlFiltered = + readListLifecycle.addReadList( + ReadList( + name = "Filtered", + bookIds = listOf(bookKids.id, book.id, bookAdult.id, bookCute.id).toIndexedMap(), + ), + ) - val rlDenied = readListLifecycle.addReadList( - ReadList( - name = "Denied", - bookIds = listOf(bookKids.id, bookCute.id).toIndexedMap(), - ), - ) + val rlDenied = + readListLifecycle.addReadList( + ReadList( + name = "Denied", + bookIds = listOf(bookKids.id, bookCute.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists") .andExpect { @@ -558,26 +569,29 @@ class ReadListControllerTest( } } - val rlAllowed = readListLifecycle.addReadList( - ReadList( - name = "Allowed", - bookIds = listOf(bookKids.id, bookCute.id).toIndexedMap(), - ), - ) + val rlAllowed = + readListLifecycle.addReadList( + ReadList( + name = "Allowed", + bookIds = listOf(bookKids.id, bookCute.id).toIndexedMap(), + ), + ) - val rlFiltered = readListLifecycle.addReadList( - ReadList( - name = "Filtered", - bookIds = listOf(bookKids.id, book.id, bookAdult.id, bookCute.id).toIndexedMap(), - ), - ) + val rlFiltered = + readListLifecycle.addReadList( + ReadList( + name = "Filtered", + bookIds = listOf(bookKids.id, book.id, bookAdult.id, bookCute.id).toIndexedMap(), + ), + ) - val rlDenied = readListLifecycle.addReadList( - ReadList( - name = "Denied", - bookIds = listOf(bookAdult.id, book.id).toIndexedMap(), - ), - ) + val rlDenied = + readListLifecycle.addReadList( + ReadList( + name = "Denied", + bookIds = listOf(bookAdult.id, book.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists") .andExpect { @@ -640,26 +654,29 @@ class ReadListControllerTest( } } - val rlAllowed = readListLifecycle.addReadList( - ReadList( - name = "Allowed", - bookIds = listOf(bookTeen.id).toIndexedMap(), - ), - ) + val rlAllowed = + readListLifecycle.addReadList( + ReadList( + name = "Allowed", + bookIds = listOf(bookTeen.id).toIndexedMap(), + ), + ) - val rlFiltered = readListLifecycle.addReadList( - ReadList( - name = "Filtered", - bookIds = listOf(bookTeen16.id, bookTeen.id).toIndexedMap(), - ), - ) + val rlFiltered = + readListLifecycle.addReadList( + ReadList( + name = "Filtered", + bookIds = listOf(bookTeen16.id, bookTeen.id).toIndexedMap(), + ), + ) - val rlDenied = readListLifecycle.addReadList( - ReadList( - name = "Denied", - bookIds = listOf(bookTeen16.id).toIndexedMap(), - ), - ) + val rlDenied = + readListLifecycle.addReadList( + ReadList( + name = "Denied", + bookIds = listOf(bookTeen16.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists") .andExpect { @@ -830,9 +847,10 @@ class ReadListControllerTest( @WithMockCustomUser fun `given non-admin user when creating read list then return forbidden`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"readlist","bookIds":["3"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/readlists") { contentType = MediaType.APPLICATION_JSON @@ -846,9 +864,10 @@ class ReadListControllerTest( @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given admin user when creating read list then return ok`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"readlist","summary":"summary","bookIds":["${booksLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/readlists") { contentType = MediaType.APPLICATION_JSON @@ -867,9 +886,10 @@ class ReadListControllerTest( makeReadLists() // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"Lib1","bookIds":["${booksLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/readlists") { contentType = MediaType.APPLICATION_JSON @@ -883,9 +903,10 @@ class ReadListControllerTest( @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given read list with duplicate bookIds when creating read list then return bad request`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"Lib1","bookIds":["${booksLibrary1.first().id}","${booksLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/readlists") { contentType = MediaType.APPLICATION_JSON @@ -902,9 +923,10 @@ class ReadListControllerTest( @WithMockCustomUser fun `given non-admin user when updating read list then return forbidden`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"readlist","bookIds":["3"]} - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/readlists/5") { contentType = MediaType.APPLICATION_JSON @@ -920,9 +942,10 @@ class ReadListControllerTest( makeReadLists() // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"updated","summary":"updatedSummary","bookIds":["${booksLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/readlists/${rlLib1.id}") { contentType = MediaType.APPLICATION_JSON @@ -1053,8 +1076,9 @@ class ReadListControllerTest( @WithMockCustomUser fun `given readlist with Unicode name when getting readlist file then attachment name is correct`() { val name = "アキラ" - val tempFile = Files.createTempFile(name, ".cbz") - .also { it.toFile().deleteOnExit() } + val tempFile = + Files.createTempFile(name, ".cbz") + .also { it.toFile().deleteOnExit() } val book = makeBook(name, libraryId = library1.id, url = tempFile.toUri().toURL()) makeSeries(name = "series", libraryId = library1.id).let { series -> seriesLifecycle.createSeries(series).let { created -> @@ -1063,12 +1087,13 @@ class ReadListControllerTest( } } - val readlist = readListLifecycle.addReadList( - ReadList( - name = name, - bookIds = listOf(book.id).toIndexedMap(), - ), - ) + val readlist = + readListLifecycle.addReadList( + ReadList( + name = name, + bookIds = listOf(book.id).toIndexedMap(), + ), + ) mockMvc.get("/api/v1/readlists/${readlist.id}/file") .andExpect { @@ -1103,29 +1128,32 @@ class ReadListControllerTest( @BeforeEach fun makeReadLists() { - rlAllDiffDates = readListLifecycle.addReadList( - ReadList( - name = "All different dates", - ordered = false, - bookIds = listOf(2, 1).map { books[it].id }.toIndexedMap(), - ), - ) + rlAllDiffDates = + readListLifecycle.addReadList( + ReadList( + name = "All different dates", + ordered = false, + bookIds = listOf(2, 1).map { books[it].id }.toIndexedMap(), + ), + ) - rlAllNullDates = readListLifecycle.addReadList( - ReadList( - name = "All null dates", - ordered = false, - bookIds = books.drop(3).map { it.id }.toIndexedMap(), - ), - ) + rlAllNullDates = + readListLifecycle.addReadList( + ReadList( + name = "All null dates", + ordered = false, + bookIds = books.drop(3).map { it.id }.toIndexedMap(), + ), + ) - rlAllBooks = readListLifecycle.addReadList( - ReadList( - name = "All books", - ordered = false, - bookIds = books.map { it.id }.toIndexedMap(), - ), - ) + rlAllBooks = + readListLifecycle.addReadList( + ReadList( + name = "All books", + ordered = false, + bookIds = books.map { it.id }.toIndexedMap(), + ), + ) } @Test @@ -1280,14 +1308,15 @@ class ReadListControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given cbl file without books when matching then return bad request`() { - val content = """ + val content = + """ RL - """.trimIndent() + """.trimIndent() mockMvc.multipart("/api/v1/readlists/match/comicrack") { file("file", content.toByteArray()) @@ -1303,7 +1332,8 @@ class ReadListControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given cbl file without name when matching then return bad request`() { - val content = """ + val content = + """ @@ -1311,7 +1341,7 @@ class ReadListControllerTest( - """.trimIndent() + """.trimIndent() mockMvc.multipart("/api/v1/readlists/match/comicrack") { file("file", content.toByteArray()) @@ -1327,7 +1357,8 @@ class ReadListControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given cbl file with book without series when matching then return bad request`() { - val content = """ + val content = + """ RL @@ -1335,7 +1366,7 @@ class ReadListControllerTest( - """.trimIndent() + """.trimIndent() mockMvc.multipart("/api/v1/readlists/match/comicrack") { file("file", content.toByteArray()) diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionControllerTest.kt index 4ec15848f..21c676b25 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesCollectionControllerTest.kt @@ -38,7 +38,6 @@ class SeriesCollectionControllerTest( @Autowired private val seriesLifecycle: SeriesLifecycle, @Autowired private val seriesMetadataRepository: SeriesMetadataRepository, ) { - private val library1 = makeLibrary("Library1", id = "1") private val library2 = makeLibrary("Library2", id = "2") private lateinit var seriesLibrary1: List @@ -52,13 +51,15 @@ class SeriesCollectionControllerTest( libraryRepository.insert(library1) libraryRepository.insert(library2) - seriesLibrary1 = (1..5) - .map { makeSeries("Series_$it", library1.id) } - .map { seriesLifecycle.createSeries(it) } + seriesLibrary1 = + (1..5) + .map { makeSeries("Series_$it", library1.id) } + .map { seriesLifecycle.createSeries(it) } - seriesLibrary2 = (6..10) - .map { makeSeries("Series_$it", library2.id) } - .map { seriesLifecycle.createSeries(it) } + seriesLibrary2 = + (6..10) + .map { makeSeries("Series_$it", library2.id) } + .map { seriesLifecycle.createSeries(it) } } @AfterAll @@ -74,26 +75,29 @@ class SeriesCollectionControllerTest( } private fun makeCollections() { - colLib1 = collectionLifecycle.addCollection( - SeriesCollection( - name = "Lib1", - seriesIds = seriesLibrary1.map { it.id }, - ), - ) + colLib1 = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Lib1", + seriesIds = seriesLibrary1.map { it.id }, + ), + ) - colLib2 = collectionLifecycle.addCollection( - SeriesCollection( - name = "Lib2", - seriesIds = seriesLibrary2.map { it.id }, - ), - ) + colLib2 = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Lib2", + seriesIds = seriesLibrary2.map { it.id }, + ), + ) - colLibBoth = collectionLifecycle.addCollection( - SeriesCollection( - name = "Lib1+2", - seriesIds = (seriesLibrary1 + seriesLibrary2).map { it.id }, - ), - ) + colLibBoth = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Lib1+2", + seriesIds = (seriesLibrary1 + seriesLibrary2).map { it.id }, + ), + ) } @Nested @@ -170,43 +174,48 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(allowAgeUnder = 10) fun `given user only allowed content with specific age rating when getting collections then only get collections that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) + + val series = + makeSeries(name = "series_no", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } } - } - val series = makeSeries(name = "series_no", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(series10.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(series10.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(series10.id, series.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(series10.id, series.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(series.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(series.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -247,63 +256,70 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(excludeAgeOver = 16) fun `given user disallowed content with specific age rating when getting collections then only gets collections that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) - } - } - val series18 = makeSeries(name = "series_18", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series18 = + makeSeries(name = "series_18", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 18)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 18)) + + val series16 = + makeSeries(name = "series_16", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 16)) + } } - } - val series16 = makeSeries(name = "series_16", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 16)) - } - } - val series = makeSeries(name = "series_no", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(series10.id, series.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(series10.id, series.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(series10.id, series16.id, series18.id, series.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(series10.id, series16.id, series18.id, series.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(series16.id, series18.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(series16.id, series18.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -344,63 +360,70 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(allowLabels = ["kids", "cute"]) fun `given user allowed only content with specific labels when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(seriesKids.id, seriesCute.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(seriesKids.id, seriesCute.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(series.id, seriesKids.id, seriesCute.id, seriesAdult.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(series.id, seriesKids.id, seriesCute.id, seriesAdult.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(seriesAdult.id, series.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(seriesAdult.id, series.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -441,63 +464,70 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(excludeLabels = ["kids", "cute"]) fun `given user disallowed content with specific labels when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(seriesAdult.id, series.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(seriesAdult.id, series.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(seriesAdult.id, seriesCute.id, seriesKids.id, series.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(seriesAdult.id, seriesCute.id, seriesKids.id, series.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(seriesKids.id, seriesCute.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(seriesKids.id, seriesCute.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -538,63 +568,70 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(allowAgeUnder = 10, allowLabels = ["kids"], excludeLabels = ["adult", "teen"]) fun `given user allowed and disallowed content when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 5, sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 5, sharingLabels = setOf("cute", "other"))) + + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(seriesKids.id, seriesCute.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(seriesKids.id, seriesCute.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(series.id, seriesKids.id, seriesCute.id, seriesAdult.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(series.id, seriesKids.id, seriesCute.id, seriesAdult.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(seriesAdult.id, series.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(seriesAdult.id, series.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -635,46 +672,51 @@ class SeriesCollectionControllerTest( @Test @WithMockCustomUser(excludeAgeOver = 16, allowLabels = ["teen"]) fun `given user allowed and disallowed content when getting series then only gets series that satisfies this criteria (2)`() { - val seriesTeen16 = makeSeries(name = "series_teen_16", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) + val seriesTeen16 = + makeSeries(name = "series_teen_16", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"), ageRating = 16)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"), ageRating = 16)) + + val seriesTeen = + makeSeries(name = "series_teen", libraryId = library1.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library1.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"))) + } } - } - val seriesTeen = makeSeries(name = "series_teen", libraryId = library1.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library1.id)) - seriesLifecycle.addBooks(created, books) - } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"))) - } - } + val colAllowed = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Allowed", + seriesIds = listOf(seriesTeen.id), + ), + ) - val colAllowed = collectionLifecycle.addCollection( - SeriesCollection( - name = "Allowed", - seriesIds = listOf(seriesTeen.id), - ), - ) + val colFiltered = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Filtered", + seriesIds = listOf(seriesTeen16.id, seriesTeen.id), + ), + ) - val colFiltered = collectionLifecycle.addCollection( - SeriesCollection( - name = "Filtered", - seriesIds = listOf(seriesTeen16.id, seriesTeen.id), - ), - ) - - val colDenied = collectionLifecycle.addCollection( - SeriesCollection( - name = "Denied", - seriesIds = listOf(seriesTeen16.id), - ), - ) + val colDenied = + collectionLifecycle.addCollection( + SeriesCollection( + name = "Denied", + seriesIds = listOf(seriesTeen16.id), + ), + ) mockMvc.get("/api/v1/collections") .andExpect { @@ -719,9 +761,10 @@ class SeriesCollectionControllerTest( @WithMockCustomUser fun `given non-admin user when creating collection then return forbidden`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"collection","ordered":false,"seriesIds":["3"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/collections") { contentType = MediaType.APPLICATION_JSON @@ -735,9 +778,10 @@ class SeriesCollectionControllerTest( @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given admin user when creating collection then return ok`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"collection","ordered":false,"seriesIds":["${seriesLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/collections") { contentType = MediaType.APPLICATION_JSON @@ -756,9 +800,10 @@ class SeriesCollectionControllerTest( makeCollections() // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"Lib1","ordered":false,"seriesIds":["${seriesLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/collections") { contentType = MediaType.APPLICATION_JSON @@ -772,9 +817,10 @@ class SeriesCollectionControllerTest( @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given collection with duplicate seriesIds when creating collection then return bad request`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"Lib1","ordered":false,"seriesIds":["${seriesLibrary1.first().id}","${seriesLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.post("/api/v1/collections") { contentType = MediaType.APPLICATION_JSON @@ -791,9 +837,10 @@ class SeriesCollectionControllerTest( @WithMockCustomUser fun `given non-admin user when updating collection then return forbidden`() { // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"collection","ordered":false,"seriesIds":["3"]} - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/collections/5") { contentType = MediaType.APPLICATION_JSON @@ -809,9 +856,10 @@ class SeriesCollectionControllerTest( makeCollections() // language=JSON - val jsonString = """ + val jsonString = + """ {"name":"updated","ordered":true,"seriesIds":["${seriesLibrary1.first().id}"]} - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/collections/${colLib1.id}") { contentType = MediaType.APPLICATION_JSON diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesControllerTest.kt index 381fe3f77..319240f78 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SeriesControllerTest.kt @@ -174,12 +174,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given books with unordered index when requesting via api then books are ordered`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id), makeBook("3", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val addedBook = makeBook("2", libraryId = library.id) seriesLifecycle.addBooks(createdSeries, listOf(addedBook)) @@ -197,12 +198,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given many books with unordered index when requesting via api then books are ordered and paged`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = (1..100 step 2).map { makeBook("$it", libraryId = library.id) } - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = (1..100 step 2).map { makeBook("$it", libraryId = library.id) } + seriesLifecycle.addBooks(created, books) + } } - } val addedBook = makeBook("2", libraryId = library.id) seriesLifecycle.addBooks(createdSeries, listOf(addedBook)) @@ -257,42 +259,46 @@ class SeriesControllerTest( @Test @WithMockCustomUser(allowAgeUnder = 10) fun `given user only allowed content with specific age rating when getting series then only gets series that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) - } - } - val series5 = makeSeries(name = "series_5", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series5 = + makeSeries(name = "series_5", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 5)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 5)) - } - } - val series15 = makeSeries(name = "series_15", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series15 = + makeSeries(name = "series_15", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 15)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 15)) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${series5.id}").andExpect { status { isOk() } } mockMvc.get("/api/v1/series/${series10.id}").andExpect { status { isOk() } } @@ -311,42 +317,46 @@ class SeriesControllerTest( @Test @WithMockCustomUser(excludeAgeOver = 16) fun `given user disallowed content with specific age rating when getting series then only gets series that satisfies this criteria`() { - val series10 = makeSeries(name = "series_10", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series10 = + makeSeries(name = "series_10", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 10)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 10)) - } - } - val series18 = makeSeries(name = "series_18", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series18 = + makeSeries(name = "series_18", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 18)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 18)) - } - } - val series16 = makeSeries(name = "series_16", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series16 = + makeSeries(name = "series_16", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 16)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 16)) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${series.id}").andExpect { status { isOk() } } mockMvc.get("/api/v1/series/${series10.id}").andExpect { status { isOk() } } @@ -365,42 +375,46 @@ class SeriesControllerTest( @Test @WithMockCustomUser(allowLabels = ["kids", "cute"]) fun `given user allowed only content with specific labels when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) - } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${seriesKids.id}").andExpect { status { isOk() } } mockMvc.get("/api/v1/series/${seriesCute.id}").andExpect { status { isOk() } } @@ -419,42 +433,46 @@ class SeriesControllerTest( @Test @WithMockCustomUser(excludeLabels = ["kids", "cute"]) fun `given user disallowed content with specific labels when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("cute", "other"))) - } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${seriesKids.id}").andExpect { status { isForbidden() } } mockMvc.get("/api/v1/series/${seriesCute.id}").andExpect { status { isForbidden() } } @@ -473,42 +491,46 @@ class SeriesControllerTest( @Test @WithMockCustomUser(allowAgeUnder = 10, allowLabels = ["kids"], excludeLabels = ["adult", "teen"]) fun `given user allowed and disallowed content when getting series then only gets series that satisfies this criteria`() { - val seriesKids = makeSeries(name = "series_kids", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesKids = + makeSeries(name = "series_kids", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("kids"))) - } - } - val seriesCute = makeSeries(name = "series_cute", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesCute = + makeSeries(name = "series_cute", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(ageRating = 5, sharingLabels = setOf("cute", "other"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(ageRating = 5, sharingLabels = setOf("cute", "other"))) - } - } - val seriesAdult = makeSeries(name = "series_adult", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesAdult = + makeSeries(name = "series_adult", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("adult"))) - } - } - val series = makeSeries(name = "series_no", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val series = + makeSeries(name = "series_no", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${seriesKids.id}").andExpect { status { isOk() } } mockMvc.get("/api/v1/series/${seriesCute.id}").andExpect { status { isOk() } } @@ -527,15 +549,16 @@ class SeriesControllerTest( @Test @WithMockCustomUser(excludeAgeOver = 16, allowLabels = ["teen"]) fun `given user allowed and disallowed content when getting series then only gets series that satisfies this criteria (2)`() { - val seriesTeen16 = makeSeries(name = "series_teen_16", libraryId = library.id).also { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val seriesTeen16 = + makeSeries(name = "series_teen_16", libraryId = library.id).also { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } + seriesMetadataRepository.findById(series.id).let { + seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"), ageRating = 16)) + } } - seriesMetadataRepository.findById(series.id).let { - seriesMetadataRepository.update(it.copy(sharingLabels = setOf("teen"), ageRating = 16)) - } - } mockMvc.get("/api/v1/series/${seriesTeen16.id}").andExpect { status { isForbidden() } } @@ -552,12 +575,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = []) fun `given user with no access to any library when getting specific series then returns forbidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}") .andExpect { status { isForbidden() } } @@ -566,12 +590,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = []) fun `given user with no access to any library when getting specific series thumbnail then returns forbidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}/thumbnail") .andExpect { status { isForbidden() } } @@ -580,12 +605,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = []) fun `given user with no access to any library when getting specific series books then returns forbidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}/books") .andExpect { status { isForbidden() } } @@ -594,12 +620,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = []) fun `given user with no access to any library when getting specific series file then returns forbidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}/file") .andExpect { status { isForbidden() } } @@ -611,12 +638,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(roles = []) fun `given user without file download role when getting specific series file then returns forbidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}/file") .andExpect { status { isForbidden() } } @@ -628,12 +656,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given book without thumbnail when getting series thumbnail then returns not found`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } mockMvc.get("/api/v1/series/${createdSeries.id}/thumbnail") .andExpect { status { isNotFound() } } @@ -645,12 +674,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given regular user when getting series then url is hidden`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val validation: MockMvcResultMatchersDsl.() -> Unit = { status { isOk() } @@ -676,12 +706,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given admin user when getting series then url is available`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } val validation: MockMvcResultMatchersDsl.() -> Unit = { status { isOk() } @@ -741,15 +772,17 @@ class SeriesControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given valid json when updating metadata then fields are updated`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } // language=JSON - val jsonString = """ + val jsonString = + """ { "title":"newTitle", "titleLock":true, @@ -774,7 +807,7 @@ class SeriesControllerTest( "totalBookCount":5, "totalBookCountLock":true } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/series/${createdSeries.id}/metadata") { contentType = MediaType.APPLICATION_JSON @@ -814,21 +847,23 @@ class SeriesControllerTest( @Test @WithMockCustomUser(roles = [ROLE_ADMIN]) fun `given json with null fields when updating metadata then fields with null are unset`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } seriesMetadataRepository.findById(createdSeries.id).let { metadata -> - val updated = metadata.copy( - ageRating = 12, - readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, - genres = setOf("Action"), - tags = setOf("tag"), - totalBookCount = 5, - ) + val updated = + metadata.copy( + ageRating = 12, + readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT, + genres = setOf("Action"), + tags = setOf("tag"), + totalBookCount = 5, + ) seriesMetadataRepository.update(updated) } @@ -841,7 +876,8 @@ class SeriesControllerTest( } // language=JSON - val jsonString = """ + val jsonString = + """ { "readingDirection":null, "ageRating":null, @@ -849,7 +885,7 @@ class SeriesControllerTest( "tags":null, "totalBookCount":null } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/series/${createdSeries.id}/metadata") { contentType = MediaType.APPLICATION_JSON @@ -874,12 +910,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given request with cache headers when getting series thumbnail then returns 304 not modified`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } bookRepository.findAll().first().let { book -> bookLifecycle.addThumbnailForBook( @@ -897,8 +934,9 @@ class SeriesControllerTest( val url = "/api/v1/series/${createdSeries.id}/thumbnail" - val response = mockMvc.get(url) - .andReturn().response + val response = + mockMvc.get(url) + .andReturn().response mockMvc.get(url) { headers { @@ -912,12 +950,13 @@ class SeriesControllerTest( @Test @WithMockCustomUser fun `given request with cache headers and modified first book when getting series thumbnail then returns 200 ok`() { - val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1", libraryId = library.id), makeBook("2", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) + val createdSeries = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1", libraryId = library.id), makeBook("2", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + } } - } bookRepository.findAll().forEach { book -> bookLifecycle.addThumbnailForBook( @@ -958,13 +997,14 @@ class SeriesControllerTest( @Test @WithMockCustomUser(id = "1") fun `given user when marking series as read then progress is marked for all books`() { - val series = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) - seriesLifecycle.sortBooks(created) + val series = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + seriesLifecycle.sortBooks(created) + } } - } bookRepository.findAll().forEach { book -> mediaRepository.findById(book.id).let { media -> @@ -1001,13 +1041,14 @@ class SeriesControllerTest( @Test @WithMockCustomUser(id = "1") fun `given user when marking series as unread then progress is removed for all books`() { - val series = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id)) - seriesLifecycle.addBooks(created, books) - seriesLifecycle.sortBooks(created) + val series = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id)) + seriesLifecycle.addBooks(created, books) + seriesLifecycle.sortBooks(created) + } } - } bookRepository.findAll().forEach { book -> mediaRepository.findById(book.id).let { media -> @@ -1049,17 +1090,19 @@ class SeriesControllerTest( @Test @WithMockCustomUser(id = "1") fun `given user when marking book as in progress then progress series return books count accordingly`() { - val series = makeSeries(name = "series", libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).also { created -> - val books = listOf( - makeBook("1.cbr", libraryId = library.id), - makeBook("2.cbr", libraryId = library.id), - makeBook("3.cbr", libraryId = library.id), - ) - seriesLifecycle.addBooks(created, books) - seriesLifecycle.sortBooks(created) + val series = + makeSeries(name = "series", libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).also { created -> + val books = + listOf( + makeBook("1.cbr", libraryId = library.id), + makeBook("2.cbr", libraryId = library.id), + makeBook("3.cbr", libraryId = library.id), + ) + seriesLifecycle.addBooks(created, books) + seriesLifecycle.sortBooks(created) + } } - } bookRepository.findAll().forEach { book -> mediaRepository.findById(book.id).let { media -> @@ -1100,15 +1143,17 @@ class SeriesControllerTest( @WithMockCustomUser fun `given series with Unicode name when getting series file then attachment name is correct`() { val name = "アキラ" - val tempFile = Files.createTempFile(name, ".cbz") - .also { it.toFile().deleteOnExit() } - val series = makeSeries(name = name, libraryId = library.id).let { series -> - seriesLifecycle.createSeries(series).let { created -> - val books = listOf(makeBook(name, libraryId = library.id, url = tempFile.toUri().toURL())) - seriesLifecycle.addBooks(created, books) + val tempFile = + Files.createTempFile(name, ".cbz") + .also { it.toFile().deleteOnExit() } + val series = + makeSeries(name = name, libraryId = library.id).let { series -> + seriesLifecycle.createSeries(series).let { created -> + val books = listOf(makeBook(name, libraryId = library.id, url = tempFile.toUri().toURL())) + seriesLifecycle.addBooks(created, books) + } + series } - series - } mockMvc.get("/api/v1/series/${series.id}/file") .andExpect { diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt index 0d5d549e5..f99851193 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/SettingsControllerTest.kt @@ -88,7 +88,8 @@ class SettingsControllerTest( val rememberMeKey = komgaSettingsProvider.rememberMeKey //language=JSON - val jsonString = """ + val jsonString = + """ { "deleteEmptyCollections": false, "rememberMeDurationDays": 15, @@ -98,7 +99,7 @@ class SettingsControllerTest( "serverPort": 5678, "serverContextPath": "/komga-hyphen/subpath123" } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/settings") { contentType = MediaType.APPLICATION_JSON @@ -132,7 +133,8 @@ class SettingsControllerTest( val rememberMeKey = komgaSettingsProvider.rememberMeKey //language=JSON - val jsonString = """ + val jsonString = + """ { "deleteEmptyCollections": null, "rememberMeDurationDays": null, @@ -142,7 +144,7 @@ class SettingsControllerTest( "serverPort": null, "serverContextPath": null } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v1/settings") { contentType = MediaType.APPLICATION_JSON diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerDemoTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerDemoTest.kt index bd503aca6..a0bd28c20 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerDemoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerDemoTest.kt @@ -15,7 +15,6 @@ import org.springframework.test.web.servlet.patch @ActiveProfiles("demo", "test") class UserControllerDemoTest( @Autowired private val mockMvc: MockMvc, - ) { @Test @WithMockCustomUser diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerTest.kt index bbc848dc9..b6cea2377 100644 --- a/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/api/rest/UserControllerTest.kt @@ -38,7 +38,6 @@ class UserControllerTest( @Autowired private val libraryLifecycle: LibraryLifecycle, @Autowired private val userRepository: KomgaUserRepository, ) { - @Autowired private lateinit var userLifecycle: KomgaUserLifecycle @@ -92,11 +91,12 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "roles": ["$ROLE_FILE_DOWNLOAD","$ROLE_PAGE_STREAMING"] } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -120,11 +120,12 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "roles": [] } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -148,14 +149,15 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "sharedLibraries": { "all": "false", "libraryIds" : ["1", "2"] } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -178,14 +180,15 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "sharedLibraries": { "all": "false", "libraryIds" : ["2"] } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -208,14 +211,15 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "sharedLibraries": { "all": "true", "libraryIds": [] } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -238,12 +242,13 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "labelsAllow": ["cute", "kids"], "labelsExclude": ["adult"] } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -262,25 +267,28 @@ class UserControllerTest( @Test @WithMockCustomUser(id = "admin", roles = [ROLE_ADMIN]) fun `given user with labels restrictions when removing restrictions then restrictions are updated`() { - val user = KomgaUser( - "user@example.org", - "", - false, - id = "user", - restrictions = ContentRestrictions( - labelsAllow = setOf("kids", "cute"), - labelsExclude = setOf("adult"), - ), - ) + val user = + KomgaUser( + "user@example.org", + "", + false, + id = "user", + restrictions = + ContentRestrictions( + labelsAllow = setOf("kids", "cute"), + labelsExclude = setOf("adult"), + ), + ) userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "labelsAllow": [], "labelsExclude": null } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -303,14 +311,15 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "ageRestriction": { "age": 12, "restriction": "ALLOW_ONLY" } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -334,14 +343,15 @@ class UserControllerTest( userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "ageRestriction": { "age": -12, "restriction": "ALLOW_ONLY" } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -354,23 +364,26 @@ class UserControllerTest( @Test @WithMockCustomUser(id = "admin", roles = [ROLE_ADMIN]) fun `given user with age restriction when removing restriction then restrictions are updated`() { - val user = KomgaUser( - "user@example.org", - "", - false, - id = "user", - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(12, AllowExclude.ALLOW_ONLY), - ), - ) + val user = + KomgaUser( + "user@example.org", + "", + false, + id = "user", + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(12, AllowExclude.ALLOW_ONLY), + ), + ) userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "ageRestriction": null } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON @@ -388,26 +401,29 @@ class UserControllerTest( @Test @WithMockCustomUser(id = "admin", roles = [ROLE_ADMIN]) fun `given user with age restriction when changing restriction then restrictions are updated`() { - val user = KomgaUser( - "user@example.org", - "", - false, - id = "user", - restrictions = ContentRestrictions( - ageRestriction = AgeRestriction(12, AllowExclude.ALLOW_ONLY), - ), - ) + val user = + KomgaUser( + "user@example.org", + "", + false, + id = "user", + restrictions = + ContentRestrictions( + ageRestriction = AgeRestriction(12, AllowExclude.ALLOW_ONLY), + ), + ) userLifecycle.createUser(user) // language=JSON - val jsonString = """ + val jsonString = + """ { "ageRestriction": { "age": 16, "restriction": "EXCLUDE" } } - """.trimIndent() + """.trimIndent() mockMvc.patch("/api/v2/users/${user.id}") { contentType = MediaType.APPLICATION_JSON