diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt index 6b5fe8f0..5222b1e0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt @@ -11,6 +11,9 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging +import io.github.reactivecircus.cache4k.Cache +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and @@ -26,6 +29,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.manga.model.table.toDataClass +import kotlin.time.Duration.Companion.minutes suspend fun getChapterDownloadReady( chapterId: Int? = null, @@ -43,6 +47,12 @@ suspend fun getChapterDownloadReadyByIndex( mangaId: Int, ): ChapterDataClass = getChapterDownloadReady(chapterIndex = chapterIndex, mangaId = mangaId) +private val mutexByChapterId: Cache = + Cache + .Builder() + .expireAfterAccess(10.minutes) + .build() + private class ChapterForDownload( optChapterId: Int? = null, optChapterIndex: Int? = null, @@ -69,9 +79,7 @@ private class ChapterForDownload( markAsNotDownloaded() - val pageList = fetchPageList() - - updateDatabasePages(pageList) + updatePageList() } return asDataClass() @@ -109,6 +117,14 @@ private class ChapterForDownload( }.first() } + private suspend fun updatePageList() { + val mutex = mutexByChapterId.get(chapterId) { Mutex() } + mutex.withLock { + val pageList = fetchPageList() + updateDatabasePages(pageList) + } + } + private suspend fun fetchPageList(): List { val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() } val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0045_PreventDuplicatedChapterPages.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0045_PreventDuplicatedChapterPages.kt new file mode 100644 index 00000000..e3985bc3 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0045_PreventDuplicatedChapterPages.kt @@ -0,0 +1,26 @@ +package suwayomi.tachidesk.server.database.migration + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import de.neonew.exposed.migrations.helpers.SQLMigration + +@Suppress("ClassName", "unused") +class M0045_PreventDuplicatedChapterPages : SQLMigration() { + override val sql: String = + """ + DELETE FROM PAGE + WHERE ID NOT IN ( + SELECT MIN(ID) + FROM PAGE + GROUP BY INDEX, "imageUrl", CHAPTER + ); + + ALTER TABLE PAGE + ADD CONSTRAINT UC_PAGE UNIQUE (INDEX, imageUrl, CHAPTER) + """.trimIndent() +}