mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2025-01-11 16:29:08 +01:00
closes #32
This commit is contained in:
parent
53ef836326
commit
1128f40bac
@ -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"
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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?
|
||||||
)
|
)
|
||||||
|
@ -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],
|
||||||
|
@ -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 {
|
||||||
|
@ -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()) {
|
||||||
|
@ -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,
|
||||||
|
@ -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()!! }
|
||||||
|
@ -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 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'
|
||||||
|
@ -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}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
1
webUI/react/src/typings.d.ts
vendored
1
webUI/react/src/typings.d.ts
vendored
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user