From a6b05c4a2759d0d5f834a54cad6c8417fe49a0d2 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 31 Mar 2024 19:19:58 +0200 Subject: [PATCH] Feature/refresh outdated thumbnail url on fetch failure (#910) * Extract thumbnail url fresh into function * Remove incorrect non-null assertion According to the typing there is no guarantee that fetching a manga from the source provides a thumbnail url * Refresh manga thumbnail url on 404 error * Refresh manga thumbnail url on unreachable origin cloudflare errors --- .../suwayomi/tachidesk/manga/impl/Manga.kt | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt index b316b408..d15af084 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -8,13 +8,17 @@ package suwayomi.tachidesk.manga.impl * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.HttpSource +import io.javalin.http.HttpCode import mu.KotlinLogging import okhttp3.CacheControl +import okhttp3.Response import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.and @@ -251,9 +255,53 @@ object Manga { } } + private suspend fun fetchThumbnailUrl(mangaId: Int): String? { + getManga(mangaId, true) + return transaction { + MangaTable.select { MangaTable.id eq mangaId }.first() + }[MangaTable.thumbnail_url] + } + private val applicationDirs by DI.global.instance() private val network: NetworkHelper by injectLazy() + private suspend fun fetchHttpSourceMangaThumbnail( + source: HttpSource, + mangaEntry: ResultRow, + refreshUrl: Boolean = false, + ): Response { + val mangaId = mangaEntry[MangaTable.id].value + + val requiresInitialization = mangaEntry[MangaTable.thumbnail_url] == null && !mangaEntry[MangaTable.initialized] + val refreshThumbnailUrl = refreshUrl || requiresInitialization + + val thumbnailUrl = + if (refreshThumbnailUrl) { + fetchThumbnailUrl(mangaId) + } else { + mangaEntry[MangaTable.thumbnail_url] + } ?: throw NullPointerException("No thumbnail found") + + return try { + source.client.newCall( + GET(thumbnailUrl, source.headers, cache = CacheControl.FORCE_NETWORK), + ).awaitSuccess() + } catch (e: HttpException) { + val tryToRefreshUrl = + !refreshUrl && + listOf( + HttpCode.NOT_FOUND.status, + 523, // (Cloudflare) Origin Is Unreachable + 522, // (Cloudflare) Connection timed out + ).contains(e.code) + if (!tryToRefreshUrl) { + throw e + } + + fetchHttpSourceMangaThumbnail(source, mangaEntry, refreshUrl = true) + } + } + suspend fun fetchMangaThumbnail(mangaId: Int): Pair { val cacheSaveDir = applicationDirs.tempThumbnailCacheRoot val fileName = mangaId.toString() @@ -264,22 +312,7 @@ object Manga { return when (val source = getCatalogueSourceOrStub(sourceId)) { is HttpSource -> getImageResponse(cacheSaveDir, fileName) { - val thumbnailUrl = - mangaEntry[MangaTable.thumbnail_url] - ?: if (!mangaEntry[MangaTable.initialized]) { - // initialize then try again - getManga(mangaId) - transaction { - MangaTable.select { MangaTable.id eq mangaId }.first() - }[MangaTable.thumbnail_url]!! - } else { - // source provides no thumbnail url for this manga - throw NullPointerException("No thumbnail found") - } - - source.client.newCall( - GET(thumbnailUrl, source.headers, cache = CacheControl.FORCE_NETWORK), - ).await() + fetchHttpSourceMangaThumbnail(source, mangaEntry) } is LocalSource -> {