This commit is contained in:
Aria Moradi 2021-03-14 23:57:33 +03:30
parent 53ef836326
commit 1128f40bac
14 changed files with 66 additions and 46 deletions

View File

@ -88,8 +88,8 @@ dependencies {
implementation(project(":AndroidCompat:Config")) implementation(project(":AndroidCompat:Config"))
testImplementation("org.jetbrains.kotlin:kotlin-test") // testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") // testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
} }
val name = "ir.armor.tachidesk.Main" val name = "ir.armor.tachidesk.Main"

View File

@ -58,6 +58,10 @@ class Main {
openInBrowser() openInBrowser()
} }
app.exception(NullPointerException::class.java) { _, ctx ->
ctx.status(404)
}
app.get("/api/v1/extension/list") { ctx -> app.get("/api/v1/extension/list") { ctx ->
ctx.json(getExtensionList()) ctx.json(getExtensionList())
} }

View File

@ -8,7 +8,7 @@ import ir.armor.tachidesk.database.table.MangaStatus
data class MangaDataClass( data class MangaDataClass(
val id: Int, val id: Int,
val sourceId: Long, val sourceId: String,
val url: String, val url: String,
val title: String, val title: String,
@ -21,7 +21,8 @@ data class MangaDataClass(
val description: String? = null, val description: String? = null,
val genre: String? = null, val genre: String? = null,
val status: String = MangaStatus.UNKNOWN.name, val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false val inLibrary: Boolean = false,
val source: SourceDataClass? = null
) )
data class PagedMangaListDataClass( data class PagedMangaListDataClass(

View File

@ -6,8 +6,8 @@ package ir.armor.tachidesk.database.dataclass
data class SourceDataClass( data class SourceDataClass(
val id: String, val id: String,
val name: String, val name: String?,
val lang: String, val lang: String?,
val iconUrl: String, val iconUrl: String?,
val supportsLatest: Boolean val supportsLatest: Boolean?
) )

View File

@ -28,13 +28,13 @@ object MangaTable : IntIdTable() {
val defaultCategory = bool("default_category").default(true) val defaultCategory = bool("default_category").default(true)
// source is used by some ancestor of IntIdTable // source is used by some ancestor of IntIdTable
val sourceReference = reference("source", SourceTable) val sourceReference = long("source")
} }
fun MangaTable.toDataClass(mangaEntry: ResultRow) = fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass( MangaDataClass(
mangaEntry[MangaTable.id].value, mangaEntry[MangaTable.id].value,
mangaEntry[sourceReference].value, mangaEntry[sourceReference].toString(),
mangaEntry[MangaTable.url], mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title], mangaEntry[MangaTable.title],

View File

@ -18,7 +18,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
fun getChapterList(mangaId: Int): List<ChapterDataClass> { fun getChapterList(mangaId: Int): List<ChapterDataClass> {
val mangaDetails = getManga(mangaId) val mangaDetails = getManga(mangaId)
val source = getHttpSource(mangaDetails.sourceId) val source = getHttpSource(mangaDetails.sourceId.toLong())
val chapterList = source.fetchChapterList( val chapterList = source.fetchChapterList(
SManga.create().apply { SManga.create().apply {
@ -62,7 +62,7 @@ fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! 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( val pageList = source.fetchPageList(
SChapter.create().apply { SChapter.create().apply {

View File

@ -21,7 +21,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
return if (mangaEntry[MangaTable.initialized]) { return if (mangaEntry[MangaTable.initialized]) {
MangaDataClass( MangaDataClass(
mangaId, mangaId,
mangaEntry[MangaTable.sourceReference].value, mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url], mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title], mangaEntry[MangaTable.title],
@ -34,10 +34,11 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
mangaEntry[MangaTable.description], mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre], mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary] mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference])
) )
} else { // initialize manga } else { // initialize manga
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
val fetchedManga = source.fetchMangaDetails( val fetchedManga = source.fetchMangaDetails(
SManga.create().apply { SManga.create().apply {
url = mangaEntry[MangaTable.url] url = mangaEntry[MangaTable.url]
@ -65,7 +66,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
MangaDataClass( MangaDataClass(
mangaId, mangaId,
mangaEntry[MangaTable.sourceReference].value, mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url], mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title], mangaEntry[MangaTable.title],
@ -78,7 +79,8 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
fetchedManga.description, fetchedManga.description,
fetchedManga.genre, fetchedManga.genre,
MangaStatus.valueOf(fetchedManga.status).name, MangaStatus.valueOf(fetchedManga.status).name,
false false,
getSource(mangaEntry[MangaTable.sourceReference])
) )
} }
} }
@ -89,7 +91,7 @@ fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
val fileName = mangaId.toString() val fileName = mangaId.toString()
return getCachedResponse(saveDir, fileName) { return getCachedResponse(saveDir, fileName) {
val sourceId = mangaEntry[MangaTable.sourceReference].value val sourceId = mangaEntry[MangaTable.sourceReference]
val source = getHttpSource(sourceId) val source = getHttpSource(sourceId)
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) { if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {

View File

@ -52,7 +52,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
MangaDataClass( MangaDataClass(
mangaId, mangaId,
sourceId, sourceId.toString(),
manga.url, manga.url,
manga.title, manga.title,
@ -70,7 +70,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
val mangaId = mangaEntry[MangaTable.id].value val mangaId = mangaEntry[MangaTable.id].value
MangaDataClass( MangaDataClass(
mangaId, mangaId,
sourceId, sourceId.toString(),
manga.url, manga.url,
manga.title, manga.title,

View File

@ -27,7 +27,7 @@ fun getTrueImageUrl(page: Page, source: HttpSource): String {
fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> { fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } 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 chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.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<InputStream, St
fun getChapterDir(mangaId: Int, chapterId: Int): String { fun getChapterDir(mangaId: Int, chapterId: Int): String {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val sourceId = mangaEntry[MangaTable.sourceReference].value val sourceId = mangaEntry[MangaTable.sourceReference]
val source = getHttpSource(sourceId) val source = getHttpSource(sourceId)
val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! } val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! }
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! } val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }

View File

@ -15,14 +15,18 @@ import ir.armor.tachidesk.database.table.SourceTable
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import java.lang.NullPointerException
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.util.Locale
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>() private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
private val extensionCache = mutableListOf<Pair<String, Any>>() private val extensionCache = mutableListOf<Pair<String, Any>>()
fun getHttpSource(sourceId: Long): HttpSource { fun getHttpSource(sourceId: Long): HttpSource {
val sourceRecord = transaction {
SourceEntity.findById(sourceId)
} ?: throw NullPointerException("Source with id $sourceId is not installed")
val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId } val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId }
if (cachedResult != null) { if (cachedResult != null) {
println("used cached HttpSource: ${cachedResult.second.name}") println("used cached HttpSource: ${cachedResult.second.name}")
@ -30,7 +34,6 @@ fun getHttpSource(sourceId: Long): HttpSource {
} }
val result: HttpSource = transaction { val result: HttpSource = transaction {
val sourceRecord = SourceEntity.findById(sourceId)!!
val extensionId = sourceRecord.extension.id.value val extensionId = sourceRecord.extension.id.value
val extensionRecord = ExtensionEntity.findById(extensionId)!! val extensionRecord = ExtensionEntity.findById(extensionId)!!
val apkName = extensionRecord.apkName val apkName = extensionRecord.apkName
@ -87,14 +90,14 @@ fun getSourceList(): List<SourceDataClass> {
fun getSource(sourceId: Long): SourceDataClass { fun getSource(sourceId: Long): SourceDataClass {
return transaction { return transaction {
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
return@transaction SourceDataClass( return@transaction SourceDataClass(
source[SourceTable.id].value.toString(), sourceId.toString(),
source[SourceTable.name], source?.get(SourceTable.name),
Locale(source[SourceTable.lang]).getDisplayLanguage(Locale(source[SourceTable.lang])), source?.get(SourceTable.lang),
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl], source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] },
getHttpSource(source[SourceTable.id].value).supportsLatest source?.let { getHttpSource(sourceId).supportsLatest }
) )
} }
} }

View File

@ -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)
}
}

View File

@ -19,11 +19,17 @@ const useStyles = makeStyles(() => createStyles({
interface IProps{ interface IProps{
manga: IManga manga: IManga
source: ISource
}
function getSourceName(source: ISource) {
if (source.name !== null) { return source.name; }
return source.id;
} }
export default function MangaDetails(props: IProps) { export default function MangaDetails(props: IProps) {
const classes = useStyles(); const classes = useStyles();
const { manga } = props; const { manga, source } = props;
const [inLibrary, setInLibrary] = useState<string>( const [inLibrary, setInLibrary] = useState<string>(
manga.inLibrary ? 'In Library' : 'Not In Library', manga.inLibrary ? 'In Library' : 'Not In Library',
); );
@ -54,8 +60,13 @@ export default function MangaDetails(props: IProps) {
return ( return (
<div> <div>
<h1> <h1>
{manga && manga.title} {manga.title}
</h1> </h1>
<h3>
Source:
{' '}
{getSourceName(source)}
</h3>
<div className={classes.root}> <div className={classes.root}>
<Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button> <Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button>
{inLibrary === 'In Library' {inLibrary === 'In Library'

View File

@ -16,6 +16,7 @@ export default function Manga() {
const { id } = useParams<{id: string}>(); const { id } = useParams<{id: string}>();
const [manga, setManga] = useState<IManga>(); const [manga, setManga] = useState<IManga>();
const [source, setSource] = useState<ISource>();
const [chapters, setChapters] = useState<IChapter[]>([]); const [chapters, setChapters] = useState<IChapter[]>([]);
useEffect(() => { 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(() => { useEffect(() => {
client.get(`/api/v1/manga/${id}/chapters`) client.get(`/api/v1/manga/${id}/chapters`)
.then((response) => response.data) .then((response) => response.data)
@ -41,7 +52,7 @@ export default function Manga() {
return ( return (
<> <>
{manga && <MangaDetails manga={manga} />} {(manga && source) && <MangaDetails manga={manga} source={source} />}
{chapterCards} {chapterCards}
</> </>
); );

View File

@ -23,6 +23,7 @@ interface ISource {
interface IManga { interface IManga {
id: number id: number
sourceId?: string
title: string title: string
thumbnailUrl: string thumbnailUrl: string
inLibrary?: boolean inLibrary?: boolean