From 152b193ad5dc5a3d9a90bd819ec7ab11735f66c9 Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Tue, 22 Mar 2022 07:21:07 -0400 Subject: [PATCH] Improve source handling, fix errors with uninitialized mangas in broken sources (#319) --- .../manga/controller/SourceController.kt | 2 +- .../suwayomi/tachidesk/manga/impl/Manga.kt | 51 ++++++++++--------- .../suwayomi/tachidesk/manga/impl/Source.kt | 36 ++++++------- .../impl/util/source/GetCatalogueSource.kt | 8 ++- .../manga/model/dataclass/SourceDataClass.kt | 14 ++--- .../masstest/TestExtensionCompatibility.kt | 4 +- 6 files changed, 59 insertions(+), 56 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt index b3fb62b3..b51a1783 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt @@ -29,7 +29,7 @@ object SourceController { /** fetch source with id `sourceId` */ fun retrieve(ctx: Context) { val sourceId = ctx.pathParam("sourceId").toLong() - ctx.json(Source.getSource(sourceId)) + ctx.json(Source.getSource(sourceId)!!) } /** popular mangas from source with id `sourceId` */ 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 0a3ed05d..4db83aa4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select @@ -23,6 +24,7 @@ import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl import suwayomi.tachidesk.manga.impl.Source.getSource import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.network.await +import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse @@ -50,30 +52,10 @@ object Manga { var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } return if (mangaEntry[MangaTable.initialized] && !onlineFetch) { - MangaDataClass( - mangaId, - mangaEntry[MangaTable.sourceReference].toString(), - - mangaEntry[MangaTable.url], - mangaEntry[MangaTable.title], - proxyThumbnailUrl(mangaId), - - true, - - mangaEntry[MangaTable.artist], - mangaEntry[MangaTable.author], - mangaEntry[MangaTable.description], - mangaEntry[MangaTable.genre].toGenreList(), - MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary], - mangaEntry[MangaTable.inLibraryAt], - getSource(mangaEntry[MangaTable.sourceReference]), - getMangaMetaMap(mangaId), - mangaEntry[MangaTable.realUrl], - false - ) + getMangaDataClass(mangaId, mangaEntry) } else { // initialize manga - val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) + val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) + ?: return getMangaDataClass(mangaId, mangaEntry) val sManga = SManga.create().apply { url = mangaEntry[MangaTable.url] title = mangaEntry[MangaTable.title] @@ -135,6 +117,29 @@ object Manga { } } + private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass( + mangaId, + mangaEntry[MangaTable.sourceReference].toString(), + + mangaEntry[MangaTable.url], + mangaEntry[MangaTable.title], + proxyThumbnailUrl(mangaId), + + true, + + mangaEntry[MangaTable.artist], + mangaEntry[MangaTable.author], + mangaEntry[MangaTable.description], + mangaEntry[MangaTable.genre].toGenreList(), + MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + mangaEntry[MangaTable.inLibrary], + mangaEntry[MangaTable.inLibraryAt], + getSource(mangaEntry[MangaTable.sourceReference]), + getMangaMetaMap(mangaId), + mangaEntry[MangaTable.realUrl], + false + ) + fun getMangaMetaMap(manga: Int): Map { return transaction { MangaMetaTable.select { MangaMetaTable.ref eq manga } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 8688e1d1..6df0f7c8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -21,10 +21,9 @@ import org.kodein.di.DI import org.kodein.di.conf.global import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl -import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource +import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource -import suwayomi.tachidesk.manga.impl.util.source.StubSource import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.SourceTable @@ -38,8 +37,7 @@ object Source { fun getSourceList(): List { return transaction { SourceTable.selectAll().mapNotNull { - val catalogueSource = getCatalogueSourceOrStub(it[SourceTable.id].value) - if (catalogueSource is StubSource) return@mapNotNull null + val catalogueSource = getCatalogueSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first() SourceDataClass( @@ -56,27 +54,23 @@ object Source { } } - fun getSource(sourceId: Long): SourceDataClass { // all the data extracted fresh form the source instance + fun getSource(sourceId: Long): SourceDataClass? { // all the data extracted fresh form the source instance return transaction { - val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() - val catalogueSource = source?.let { getCatalogueSource(sourceId) } - val extension = source?.let { - ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first() - } + val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null + val catalogueSource = getCatalogueSourceOrNull(sourceId) ?: return@transaction null + val extension = ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first() SourceDataClass( sourceId.toString(), - source?.get(SourceTable.name), - source?.get(SourceTable.lang), - source?.let { - getExtensionIconUrl( - extension!![ExtensionTable.apkName] - ) - }, - catalogueSource?.supportsLatest, - catalogueSource?.let { it is ConfigurableSource }, - source?.get(SourceTable.isNsfw), - catalogueSource?.toString() + source[SourceTable.name], + source[SourceTable.lang], + getExtensionIconUrl( + extension[ExtensionTable.apkName] + ), + catalogueSource.supportsLatest, + catalogueSource is ConfigurableSource, + source[SourceTable.isNsfw], + catalogueSource.toString() ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt index 4972eff8..c3e2a06e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt @@ -26,7 +26,7 @@ object GetCatalogueSource { private val sourceCache = ConcurrentHashMap() private val applicationDirs by DI.global.instance() - fun getCatalogueSource(sourceId: Long): CatalogueSource? { + private fun getCatalogueSource(sourceId: Long): CatalogueSource? { val cachedResult: CatalogueSource? = sourceCache[sourceId] if (cachedResult != null) { return cachedResult @@ -56,8 +56,12 @@ object GetCatalogueSource { return sourceCache[sourceId]!! } + fun getCatalogueSourceOrNull(sourceId: Long): CatalogueSource? { + return runCatching { getCatalogueSource(sourceId) }.getOrNull() + } + fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource { - return runCatching { getCatalogueSource(sourceId) }.getOrNull() ?: StubSource(sourceId) + return getCatalogueSourceOrNull(sourceId) ?: StubSource(sourceId) } fun registerCatalogueSource(sourcePair: Pair) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt index 82096abf..7aefb7e2 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt @@ -11,19 +11,19 @@ import eu.kanade.tachiyomi.source.ConfigurableSource data class SourceDataClass( val id: String, - val name: String?, - val lang: String?, - val iconUrl: String?, + val name: String, + val lang: String, + val iconUrl: String, /** The Source provides a latest listing */ - val supportsLatest: Boolean?, + val supportsLatest: Boolean, /** The Source implements [ConfigurableSource] */ - val isConfigurable: Boolean?, + val isConfigurable: Boolean, /** The Source class has a @Nsfw annotation */ - val isNsfw: Boolean?, + val isNsfw: Boolean, /** A nicer version of [name] */ - val displayName: String?, + val displayName: String, ) diff --git a/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt b/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt index 39052b46..83266a25 100644 --- a/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt +++ b/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt @@ -27,7 +27,7 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle -import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource +import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass import suwayomi.tachidesk.server.applicationSetup import suwayomi.tachidesk.test.BASE_PATH @@ -72,7 +72,7 @@ class TestExtensionCompatibility { } } } - sources = getSourceList().map { getCatalogueSource(it.id.toLong())!! as HttpSource } + sources = getSourceList().map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource } } setLoggingEnabled(true) File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })