From 1128f40bacf209637af46a68a09c53e8d606109f Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sun, 14 Mar 2021 23:57:33 +0330 Subject: [PATCH] closes #32 --- server/build.gradle.kts | 4 ++-- .../main/kotlin/ir/armor/tachidesk/Main.kt | 4 ++++ .../database/dataclass/MangaDataClass.kt | 5 +++-- .../database/dataclass/SourceDataClass.kt | 8 ++++---- .../tachidesk/database/table/MangaTable.kt | 4 ++-- .../kotlin/ir/armor/tachidesk/util/Chapter.kt | 4 ++-- .../kotlin/ir/armor/tachidesk/util/Manga.kt | 14 ++++++++------ .../ir/armor/tachidesk/util/MangaList.kt | 4 ++-- .../kotlin/ir/armor/tachidesk/util/Page.kt | 4 ++-- .../ir/armor/tachidesk/util/SourceList.kt | 19 +++++++++++-------- .../test/kotlin/ir/armor/tachidesk/AppTest.kt | 13 ------------- webUI/react/src/components/MangaDetails.tsx | 15 +++++++++++++-- webUI/react/src/screens/Manga.tsx | 13 ++++++++++++- webUI/react/src/typings.d.ts | 1 + 14 files changed, 66 insertions(+), 46 deletions(-) delete mode 100644 server/src/test/kotlin/ir/armor/tachidesk/AppTest.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts index ebe177c6..fa816264 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -88,8 +88,8 @@ dependencies { implementation(project(":AndroidCompat:Config")) - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit") +// testImplementation("org.jetbrains.kotlin:kotlin-test") +// testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } val name = "ir.armor.tachidesk.Main" diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index f160e3d9..15a5e0cc 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -58,6 +58,10 @@ class Main { openInBrowser() } + app.exception(NullPointerException::class.java) { _, ctx -> + ctx.status(404) + } + app.get("/api/v1/extension/list") { ctx -> ctx.json(getExtensionList()) } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt index 3bdab8fd..e7e81f09 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt @@ -8,7 +8,7 @@ import ir.armor.tachidesk.database.table.MangaStatus data class MangaDataClass( val id: Int, - val sourceId: Long, + val sourceId: String, val url: String, val title: String, @@ -21,7 +21,8 @@ data class MangaDataClass( val description: String? = null, val genre: String? = null, val status: String = MangaStatus.UNKNOWN.name, - val inLibrary: Boolean = false + val inLibrary: Boolean = false, + val source: SourceDataClass? = null ) data class PagedMangaListDataClass( diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/SourceDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/SourceDataClass.kt index badd3979..53a02ca9 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/SourceDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/SourceDataClass.kt @@ -6,8 +6,8 @@ package ir.armor.tachidesk.database.dataclass data class SourceDataClass( val id: String, - val name: String, - val lang: String, - val iconUrl: String, - val supportsLatest: Boolean + val name: String?, + val lang: String?, + val iconUrl: String?, + val supportsLatest: Boolean? ) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt index f2a1275e..1312aac9 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt @@ -28,13 +28,13 @@ object MangaTable : IntIdTable() { val defaultCategory = bool("default_category").default(true) // source is used by some ancestor of IntIdTable - val sourceReference = reference("source", SourceTable) + val sourceReference = long("source") } fun MangaTable.toDataClass(mangaEntry: ResultRow) = MangaDataClass( mangaEntry[MangaTable.id].value, - mangaEntry[sourceReference].value, + mangaEntry[sourceReference].toString(), mangaEntry[MangaTable.url], mangaEntry[MangaTable.title], diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt index a82ccc5a..b4daf919 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt @@ -18,7 +18,7 @@ import org.jetbrains.exposed.sql.transactions.transaction fun getChapterList(mangaId: Int): List { val mangaDetails = getManga(mangaId) - val source = getHttpSource(mangaDetails.sourceId) + val source = getHttpSource(mangaDetails.sourceId.toLong()) val chapterList = source.fetchChapterList( SManga.create().apply { @@ -62,7 +62,7 @@ fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass { val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! - val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) val pageList = source.fetchPageList( SChapter.create().apply { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt index 929d891a..85daf78b 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt @@ -21,7 +21,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { return if (mangaEntry[MangaTable.initialized]) { MangaDataClass( mangaId, - mangaEntry[MangaTable.sourceReference].value, + mangaEntry[MangaTable.sourceReference].toString(), mangaEntry[MangaTable.url], mangaEntry[MangaTable.title], @@ -34,10 +34,11 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { mangaEntry[MangaTable.description], mangaEntry[MangaTable.genre], MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary] + mangaEntry[MangaTable.inLibrary], + getSource(mangaEntry[MangaTable.sourceReference]) ) } else { // initialize manga - val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) val fetchedManga = source.fetchMangaDetails( SManga.create().apply { url = mangaEntry[MangaTable.url] @@ -65,7 +66,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { MangaDataClass( mangaId, - mangaEntry[MangaTable.sourceReference].value, + mangaEntry[MangaTable.sourceReference].toString(), mangaEntry[MangaTable.url], mangaEntry[MangaTable.title], @@ -78,7 +79,8 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { fetchedManga.description, fetchedManga.genre, MangaStatus.valueOf(fetchedManga.status).name, - false + false, + getSource(mangaEntry[MangaTable.sourceReference]) ) } } @@ -89,7 +91,7 @@ fun getThumbnail(mangaId: Int): Pair { val fileName = mangaId.toString() return getCachedResponse(saveDir, fileName) { - val sourceId = mangaEntry[MangaTable.sourceReference].value + val sourceId = mangaEntry[MangaTable.sourceReference] val source = getHttpSource(sourceId) var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] if (thumbnailUrl == null || thumbnailUrl.isEmpty()) { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt index e007079b..2a7ffe0e 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt @@ -52,7 +52,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { MangaDataClass( mangaId, - sourceId, + sourceId.toString(), manga.url, manga.title, @@ -70,7 +70,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { val mangaId = mangaEntry[MangaTable.id].value MangaDataClass( mangaId, - sourceId, + sourceId.toString(), manga.url, manga.title, diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt index c34f7328..eb2cc984 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt @@ -27,7 +27,7 @@ fun getTrueImageUrl(page: Page, source: HttpSource): String { fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } - val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! } val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.firstOrNull()!! } @@ -56,7 +56,7 @@ fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair>() private val extensionCache = mutableListOf>() fun getHttpSource(sourceId: Long): HttpSource { + val sourceRecord = transaction { + SourceEntity.findById(sourceId) + } ?: throw NullPointerException("Source with id $sourceId is not installed") + val cachedResult: Pair? = sourceCache.firstOrNull { it.first == sourceId } if (cachedResult != null) { println("used cached HttpSource: ${cachedResult.second.name}") @@ -30,7 +34,6 @@ fun getHttpSource(sourceId: Long): HttpSource { } val result: HttpSource = transaction { - val sourceRecord = SourceEntity.findById(sourceId)!! val extensionId = sourceRecord.extension.id.value val extensionRecord = ExtensionEntity.findById(extensionId)!! val apkName = extensionRecord.apkName @@ -87,14 +90,14 @@ fun getSourceList(): List { fun getSource(sourceId: Long): SourceDataClass { return transaction { - val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! + val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() return@transaction SourceDataClass( - source[SourceTable.id].value.toString(), - source[SourceTable.name], - Locale(source[SourceTable.lang]).getDisplayLanguage(Locale(source[SourceTable.lang])), - ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl], - getHttpSource(source[SourceTable.id].value).supportsLatest + sourceId.toString(), + source?.get(SourceTable.name), + source?.get(SourceTable.lang), + source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] }, + source?.let { getHttpSource(sourceId).supportsLatest } ) } } diff --git a/server/src/test/kotlin/ir/armor/tachidesk/AppTest.kt b/server/src/test/kotlin/ir/armor/tachidesk/AppTest.kt deleted file mode 100644 index aa2a59fa..00000000 --- a/server/src/test/kotlin/ir/armor/tachidesk/AppTest.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package ir.armor.tachidesk - -import kotlin.test.Test -import kotlin.test.assertTrue - -class AppTest { - @Test fun testAppHasAGreeting() { - assertTrue(true) - } -} diff --git a/webUI/react/src/components/MangaDetails.tsx b/webUI/react/src/components/MangaDetails.tsx index afc49f74..6efe101b 100644 --- a/webUI/react/src/components/MangaDetails.tsx +++ b/webUI/react/src/components/MangaDetails.tsx @@ -19,11 +19,17 @@ const useStyles = makeStyles(() => createStyles({ interface IProps{ manga: IManga + source: ISource +} + +function getSourceName(source: ISource) { + if (source.name !== null) { return source.name; } + return source.id; } export default function MangaDetails(props: IProps) { const classes = useStyles(); - const { manga } = props; + const { manga, source } = props; const [inLibrary, setInLibrary] = useState( manga.inLibrary ? 'In Library' : 'Not In Library', ); @@ -54,8 +60,13 @@ export default function MangaDetails(props: IProps) { return (

- {manga && manga.title} + {manga.title}

+

+ Source: + {' '} + {getSourceName(source)} +

{inLibrary === 'In Library' diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/Manga.tsx index 43281fee..867945f0 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/Manga.tsx @@ -16,6 +16,7 @@ export default function Manga() { const { id } = useParams<{id: string}>(); const [manga, setManga] = useState(); + const [source, setSource] = useState(); const [chapters, setChapters] = useState([]); useEffect(() => { @@ -27,6 +28,16 @@ export default function Manga() { }); }, []); + useEffect(() => { + if (manga !== undefined) { + client.get(`/api/v1/source/${manga.sourceId}`) + .then((response) => response.data) + .then((data: ISource) => { + setSource(data); + }); + } + }, [manga]); + useEffect(() => { client.get(`/api/v1/manga/${id}/chapters`) .then((response) => response.data) @@ -41,7 +52,7 @@ export default function Manga() { return ( <> - {manga && } + {(manga && source) && } {chapterCards} ); diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index d969e344..d4466e8d 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -23,6 +23,7 @@ interface ISource { interface IManga { id: number + sourceId?: string title: string thumbnailUrl: string inLibrary?: boolean