mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2025-02-28 22:43:38 +01:00
let's not polute the namespace together
This commit is contained in:
parent
90ae180b3e
commit
5656016700
@ -7,8 +7,8 @@ package ir.armor.tachidesk
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import ir.armor.tachidesk.server.JavalinSetup.javalinSetup
|
||||||
import ir.armor.tachidesk.server.applicationSetup
|
import ir.armor.tachidesk.server.applicationSetup
|
||||||
import ir.armor.tachidesk.server.javalinSetup
|
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -45,34 +45,34 @@ object Category {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the category from position `from` to `to`
|
* Move the category from position `from` to `to`
|
||||||
*/
|
*/
|
||||||
fun reorderCategory(categoryId: Int, from: Int, to: Int) {
|
fun reorderCategory(categoryId: Int, from: Int, to: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
|
val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
|
||||||
categories.add(to - 1, categories.removeAt(from - 1))
|
categories.add(to - 1, categories.removeAt(from - 1))
|
||||||
categories.forEachIndexed { index, cat ->
|
categories.forEachIndexed { index, cat ->
|
||||||
CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) {
|
CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) {
|
||||||
it[CategoryTable.order] = index + 1
|
it[CategoryTable.order] = index + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCategory(categoryId: Int) {
|
||||||
|
transaction {
|
||||||
|
CategoryMangaTable.select { CategoryMangaTable.category eq categoryId }.forEach {
|
||||||
|
removeMangaFromCategory(it[CategoryMangaTable.manga].value, categoryId)
|
||||||
|
}
|
||||||
|
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCategoryList(): List<CategoryDataClass> {
|
||||||
|
return transaction {
|
||||||
|
CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||||
|
CategoryTable.toDataClass(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeCategory(categoryId: Int) {
|
|
||||||
transaction {
|
|
||||||
CategoryMangaTable.select { CategoryMangaTable.category eq categoryId }.forEach {
|
|
||||||
removeMangaFromCategory(it[CategoryMangaTable.manga].value, categoryId)
|
|
||||||
}
|
|
||||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCategoryList(): List<CategoryDataClass> {
|
|
||||||
return transaction {
|
|
||||||
CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).map {
|
|
||||||
CategoryTable.toDataClass(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -22,52 +22,51 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
object CategoryManga {
|
object CategoryManga {
|
||||||
fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) {
|
if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) {
|
||||||
CategoryMangaTable.insert {
|
CategoryMangaTable.insert {
|
||||||
it[CategoryMangaTable.category] = categoryId
|
it[CategoryMangaTable.category] = categoryId
|
||||||
it[CategoryMangaTable.manga] = mangaId
|
it[CategoryMangaTable.manga] = mangaId
|
||||||
}
|
}
|
||||||
|
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
it[MangaTable.defaultCategory] = false
|
it[MangaTable.defaultCategory] = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun removeMangaFromCategory(mangaId: Int, categoryId: Int) {
|
fun removeMangaFromCategory(mangaId: Int, categoryId: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }
|
CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }
|
||||||
if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) {
|
if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) {
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
it[MangaTable.defaultCategory] = true
|
it[MangaTable.defaultCategory] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list of mangas that belong to a category
|
* list of mangas that belong to a category
|
||||||
*/
|
*/
|
||||||
fun getCategoryMangaList(categoryId: Int): List<MangaDataClass> {
|
fun getCategoryMangaList(categoryId: Int): List<MangaDataClass> {
|
||||||
return transaction {
|
return transaction {
|
||||||
CategoryMangaTable.innerJoin(MangaTable).select { CategoryMangaTable.category eq categoryId }.map {
|
CategoryMangaTable.innerJoin(MangaTable).select { CategoryMangaTable.category eq categoryId }.map {
|
||||||
MangaTable.toDataClass(it)
|
MangaTable.toDataClass(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list of categories that a manga belongs to
|
* list of categories that a manga belongs to
|
||||||
*/
|
*/
|
||||||
fun getMangaCategories(mangaId: Int): List<CategoryDataClass> {
|
fun getMangaCategories(mangaId: Int): List<CategoryDataClass> {
|
||||||
return transaction {
|
return transaction {
|
||||||
CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map {
|
CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||||
CategoryTable.toDataClass(it)
|
CategoryTable.toDataClass(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -29,10 +29,10 @@ object Chapter {
|
|||||||
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
||||||
|
|
||||||
val chapterList = source.fetchChapterList(
|
val chapterList = source.fetchChapterList(
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
title = mangaDetails.title
|
title = mangaDetails.title
|
||||||
url = mangaDetails.url
|
url = mangaDetails.url
|
||||||
}
|
}
|
||||||
).toBlocking().first()
|
).toBlocking().first()
|
||||||
|
|
||||||
val chapterCount = chapterList.count()
|
val chapterCount = chapterList.count()
|
||||||
@ -72,15 +72,15 @@ object Chapter {
|
|||||||
|
|
||||||
chapterList.mapIndexed { index, it ->
|
chapterList.mapIndexed { index, it ->
|
||||||
ChapterDataClass(
|
ChapterDataClass(
|
||||||
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
||||||
it.url,
|
it.url,
|
||||||
it.name,
|
it.name,
|
||||||
it.date_upload,
|
it.date_upload,
|
||||||
it.chapter_number,
|
it.chapter_number,
|
||||||
it.scanlator,
|
it.scanlator,
|
||||||
mangaId,
|
mangaId,
|
||||||
chapterCount - index,
|
chapterCount - index,
|
||||||
chapterCount
|
chapterCount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,27 +95,27 @@ object Chapter {
|
|||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||||
|
|
||||||
val pageList = source.fetchPageList(
|
val pageList = source.fetchPageList(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = chapterEntry[ChapterTable.url]
|
url = chapterEntry[ChapterTable.url]
|
||||||
name = chapterEntry[ChapterTable.name]
|
name = chapterEntry[ChapterTable.name]
|
||||||
}
|
}
|
||||||
).toBlocking().first()
|
).toBlocking().first()
|
||||||
|
|
||||||
val chapterId = chapterEntry[ChapterTable.id].value
|
val chapterId = chapterEntry[ChapterTable.id].value
|
||||||
val chapterCount = transaction { ChapterTable.selectAll().count() }
|
val chapterCount = transaction { ChapterTable.selectAll().count() }
|
||||||
|
|
||||||
val chapter = ChapterDataClass(
|
val chapter = ChapterDataClass(
|
||||||
chapterId,
|
chapterId,
|
||||||
chapterEntry[ChapterTable.url],
|
chapterEntry[ChapterTable.url],
|
||||||
chapterEntry[ChapterTable.name],
|
chapterEntry[ChapterTable.name],
|
||||||
chapterEntry[ChapterTable.date_upload],
|
chapterEntry[ChapterTable.date_upload],
|
||||||
chapterEntry[ChapterTable.chapter_number],
|
chapterEntry[ChapterTable.chapter_number],
|
||||||
chapterEntry[ChapterTable.scanlator],
|
chapterEntry[ChapterTable.scanlator],
|
||||||
mangaId,
|
mangaId,
|
||||||
chapterEntry[ChapterTable.chapterIndex],
|
chapterEntry[ChapterTable.chapterIndex],
|
||||||
chapterCount.toInt(),
|
chapterCount.toInt(),
|
||||||
|
|
||||||
pageList.count()
|
pageList.count()
|
||||||
)
|
)
|
||||||
|
|
||||||
pageList.forEach { page ->
|
pageList.forEach { page ->
|
||||||
|
@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.impl.ExtensionsList.extensionTableAsDataClass
|
import ir.armor.tachidesk.impl.ExtensionsList.extensionTableAsDataClass
|
||||||
|
import ir.armor.tachidesk.impl.util.APKExtractor
|
||||||
import ir.armor.tachidesk.model.database.ExtensionTable
|
import ir.armor.tachidesk.model.database.ExtensionTable
|
||||||
import ir.armor.tachidesk.model.database.SourceTable
|
import ir.armor.tachidesk.model.database.SourceTable
|
||||||
import ir.armor.tachidesk.impl.util.APKExtractor
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
import ir.armor.tachidesk.server.applicationDirs
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -39,206 +39,206 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
object Extension {
|
object Extension {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert dex to jar, a wrapper for the dex2jar library
|
* Convert dex to jar, a wrapper for the dex2jar library
|
||||||
*/
|
*/
|
||||||
private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) {
|
private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) {
|
||||||
// adopted from com.googlecode.dex2jar.tools.Dex2jarCmd.doCommandLine
|
// adopted from com.googlecode.dex2jar.tools.Dex2jarCmd.doCommandLine
|
||||||
// source at: https://github.com/DexPatcher/dex2jar/tree/v2.1-20190905-lanchon/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java
|
// source at: https://github.com/DexPatcher/dex2jar/tree/v2.1-20190905-lanchon/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java
|
||||||
|
|
||||||
val jarFilePath = File(jarFile).toPath()
|
val jarFilePath = File(jarFile).toPath()
|
||||||
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
||||||
val handler = BaksmaliBaseDexExceptionHandler()
|
val handler = BaksmaliBaseDexExceptionHandler()
|
||||||
Dex2jar
|
Dex2jar
|
||||||
.from(reader)
|
.from(reader)
|
||||||
.withExceptionHandler(handler)
|
.withExceptionHandler(handler)
|
||||||
.reUseReg(false)
|
.reUseReg(false)
|
||||||
.topoLogicalSort()
|
.topoLogicalSort()
|
||||||
.skipDebug(true)
|
.skipDebug(true)
|
||||||
.optimizeSynchronized(false)
|
.optimizeSynchronized(false)
|
||||||
.printIR(false)
|
.printIR(false)
|
||||||
.noCode(false)
|
.noCode(false)
|
||||||
.skipExceptions(false)
|
.skipExceptions(false)
|
||||||
.to(jarFilePath)
|
.to(jarFilePath)
|
||||||
if (handler.hasException()) {
|
if (handler.hasException()) {
|
||||||
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
val errorFile: Path = File(ApplicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
||||||
logger.error(
|
logger.error(
|
||||||
"Detail Error Information in File $errorFile\n" +
|
"Detail Error Information in File $errorFile\n" +
|
||||||
"Please report this file to one of following link if possible (any one).\n" +
|
"Please report this file to one of following link if possible (any one).\n" +
|
||||||
" https://sourceforge.net/p/dex2jar/tickets/\n" +
|
" https://sourceforge.net/p/dex2jar/tickets/\n" +
|
||||||
" https://bitbucket.org/pxb1988/dex2jar/issues\n" +
|
" https://bitbucket.org/pxb1988/dex2jar/issues\n" +
|
||||||
" https://github.com/pxb1988/dex2jar/issues\n" +
|
" https://github.com/pxb1988/dex2jar/issues\n" +
|
||||||
" dex2jar@googlegroups.com"
|
" dex2jar@googlegroups.com"
|
||||||
)
|
)
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* loads the extension main class called $className from the jar located at $jarPath
|
* loads the extension main class called $className from the jar located at $jarPath
|
||||||
* It may return an instance of HttpSource or SourceFactory depending on the extension.
|
* It may return an instance of HttpSource or SourceFactory depending on the extension.
|
||||||
*/
|
*/
|
||||||
fun loadExtensionInstance(jarPath: String, className: String): Any {
|
fun loadExtensionInstance(jarPath: String, className: String): Any {
|
||||||
val classLoader = URLClassLoader(arrayOf<URL>(URL("file:$jarPath")))
|
val classLoader = URLClassLoader(arrayOf<URL>(URL("file:$jarPath")))
|
||||||
val classToLoad = Class.forName(className, true, classLoader)
|
val classToLoad = Class.forName(className, true, classLoader)
|
||||||
return classToLoad.getDeclaredConstructor().newInstance()
|
return classToLoad.getDeclaredConstructor().newInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun installExtension(pkgName: String): Int {
|
fun installExtension(pkgName: String): Int {
|
||||||
logger.debug("Installing $pkgName")
|
logger.debug("Installing $pkgName")
|
||||||
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
||||||
val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk")
|
val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk")
|
||||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
val dirPathWithoutType = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType"
|
||||||
|
|
||||||
// check if we don't have the dex file already downloaded
|
// check if we don't have the dex file already downloaded
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
if (!File(jarPath).exists()) {
|
if (!File(jarPath).exists()) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val apkToDownload = ExtensionGithubApi.getApkUrl(extensionRecord)
|
val apkToDownload = ExtensionGithubApi.getApkUrl(extensionRecord)
|
||||||
|
|
||||||
val apkFilePath = "$dirPathWithoutType.apk"
|
val apkFilePath = "$dirPathWithoutType.apk"
|
||||||
val jarFilePath = "$dirPathWithoutType.jar"
|
val jarFilePath = "$dirPathWithoutType.jar"
|
||||||
val dexFilePath = "$dirPathWithoutType.dex"
|
val dexFilePath = "$dirPathWithoutType.dex"
|
||||||
|
|
||||||
// download apk file
|
// download apk file
|
||||||
downloadAPKFile(apkToDownload, apkFilePath)
|
downloadAPKFile(apkToDownload, apkFilePath)
|
||||||
|
|
||||||
val className: String = APKExtractor.extractDexAndReadClassname(apkFilePath, dexFilePath)
|
val className: String = APKExtractor.extractDexAndReadClassname(apkFilePath, dexFilePath)
|
||||||
logger.debug(className)
|
logger.debug(className)
|
||||||
// dex -> jar
|
// dex -> jar
|
||||||
dex2jar(dexFilePath, jarFilePath, fileNameWithoutType)
|
dex2jar(dexFilePath, jarFilePath, fileNameWithoutType)
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
File(apkFilePath).delete()
|
File(apkFilePath).delete()
|
||||||
File(dexFilePath).delete()
|
File(dexFilePath).delete()
|
||||||
|
|
||||||
// update sources of the extension
|
// update sources of the extension
|
||||||
val instance = loadExtensionInstance(jarFilePath, className)
|
val instance = loadExtensionInstance(jarFilePath, className)
|
||||||
|
|
||||||
val extensionId = transaction {
|
val extensionId = transaction {
|
||||||
ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.firstOrNull()!![ExtensionTable.id]
|
ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.firstOrNull()!![ExtensionTable.id]
|
||||||
}
|
|
||||||
|
|
||||||
when (instance) {
|
|
||||||
is HttpSource -> { // single source
|
|
||||||
transaction {
|
|
||||||
if (SourceTable.select { SourceTable.id eq instance.id }.count() == 0L) {
|
|
||||||
SourceTable.insert {
|
|
||||||
it[this.id] = instance.id
|
|
||||||
it[name] = instance.name
|
|
||||||
it[this.lang] = instance.lang
|
|
||||||
it[extension] = extensionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug("Installed source ${instance.name} with id ${instance.id}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is SourceFactory -> { // theme source or multi lang
|
|
||||||
transaction {
|
when (instance) {
|
||||||
instance.createSources().forEachIndexed { index, source ->
|
is HttpSource -> { // single source
|
||||||
val httpSource = source as HttpSource
|
transaction {
|
||||||
if (SourceTable.select { SourceTable.id eq httpSource.id }.count() == 0L) {
|
if (SourceTable.select { SourceTable.id eq instance.id }.count() == 0L) {
|
||||||
SourceTable.insert {
|
SourceTable.insert {
|
||||||
it[this.id] = httpSource.id
|
it[this.id] = instance.id
|
||||||
it[name] = httpSource.name
|
it[name] = instance.name
|
||||||
it[this.lang] = httpSource.lang
|
it[this.lang] = instance.lang
|
||||||
it[extension] = extensionId
|
it[extension] = extensionId
|
||||||
it[partOfFactorySource] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.debug("Installed source ${httpSource.name} with id:${httpSource.id}")
|
logger.debug("Installed source ${instance.name} with id ${instance.id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is SourceFactory -> { // theme source or multi lang
|
||||||
|
transaction {
|
||||||
|
instance.createSources().forEachIndexed { index, source ->
|
||||||
|
val httpSource = source as HttpSource
|
||||||
|
if (SourceTable.select { SourceTable.id eq httpSource.id }.count() == 0L) {
|
||||||
|
SourceTable.insert {
|
||||||
|
it[this.id] = httpSource.id
|
||||||
|
it[name] = httpSource.name
|
||||||
|
it[this.lang] = httpSource.lang
|
||||||
|
it[extension] = extensionId
|
||||||
|
it[partOfFactorySource] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Installed source ${httpSource.name} with id:${httpSource.id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw RuntimeException("Extension content is unexpected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
throw RuntimeException("Extension content is unexpected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update extension info
|
// update extension info
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) {
|
ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) {
|
||||||
it[isInstalled] = true
|
it[isInstalled] = true
|
||||||
it[classFQName] = className
|
it[classFQName] = className
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 201 // we downloaded successfully
|
||||||
|
} else {
|
||||||
|
return 302
|
||||||
}
|
}
|
||||||
return 201 // we downloaded successfully
|
|
||||||
} else {
|
|
||||||
return 302
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val networkHelper: NetworkHelper by injectLazy()
|
val networkHelper: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
private fun downloadAPKFile(url: String, apkPath: String) {
|
private fun downloadAPKFile(url: String, apkPath: String) {
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
val response = networkHelper.client.newCall(request).execute()
|
val response = networkHelper.client.newCall(request).execute()
|
||||||
|
|
||||||
val downloadedFile = File(apkPath)
|
val downloadedFile = File(apkPath)
|
||||||
val sink = downloadedFile.sink().buffer()
|
val sink = downloadedFile.sink().buffer()
|
||||||
sink.writeAll(response.body!!.source())
|
sink.writeAll(response.body!!.source())
|
||||||
sink.close()
|
sink.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstallExtension(pkgName: String) {
|
fun uninstallExtension(pkgName: String) {
|
||||||
logger.debug("Uninstalling $pkgName")
|
logger.debug("Uninstalling $pkgName")
|
||||||
|
|
||||||
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! }
|
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! }
|
||||||
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
|
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
transaction {
|
transaction {
|
||||||
val extensionId = extensionRecord[ExtensionTable.id].value
|
val extensionId = extensionRecord[ExtensionTable.id].value
|
||||||
|
|
||||||
SourceTable.deleteWhere { SourceTable.extension eq extensionId }
|
SourceTable.deleteWhere { SourceTable.extension eq extensionId }
|
||||||
if (extensionRecord[ExtensionTable.isObsolete])
|
if (extensionRecord[ExtensionTable.isObsolete])
|
||||||
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
|
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
|
||||||
else
|
else
|
||||||
|
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||||
|
it[isInstalled] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File(jarPath).exists()) {
|
||||||
|
File(jarPath).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateExtension(pkgName: String): Int {
|
||||||
|
val targetExtension = ExtensionsList.updateMap.remove(pkgName)!!
|
||||||
|
uninstallExtension(pkgName)
|
||||||
|
transaction {
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||||
it[isInstalled] = false
|
it[name] = targetExtension.name
|
||||||
|
it[versionName] = targetExtension.versionName
|
||||||
|
it[versionCode] = targetExtension.versionCode
|
||||||
|
it[lang] = targetExtension.lang
|
||||||
|
it[isNsfw] = targetExtension.isNsfw
|
||||||
|
it[apkName] = targetExtension.apkName
|
||||||
|
it[iconUrl] = targetExtension.iconUrl
|
||||||
|
it[hasUpdate] = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return installExtension(pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File(jarPath).exists()) {
|
val network: NetworkHelper by injectLazy()
|
||||||
File(jarPath).delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateExtension(pkgName: String): Int {
|
fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
|
||||||
val targetExtension = ExtensionsList.updateMap.remove(pkgName)!!
|
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl]
|
||||||
uninstallExtension(pkgName)
|
|
||||||
transaction {
|
val saveDir = "${ApplicationDirs.extensionsRoot}/icon"
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
|
||||||
it[name] = targetExtension.name
|
return getCachedImageResponse(saveDir, apkName) {
|
||||||
it[versionName] = targetExtension.versionName
|
network.client.newCall(
|
||||||
it[versionCode] = targetExtension.versionCode
|
GET(iconUrl)
|
||||||
it[lang] = targetExtension.lang
|
).execute()
|
||||||
it[isNsfw] = targetExtension.isNsfw
|
|
||||||
it[apkName] = targetExtension.apkName
|
|
||||||
it[iconUrl] = targetExtension.iconUrl
|
|
||||||
it[hasUpdate] = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return installExtension(pkgName)
|
|
||||||
}
|
|
||||||
|
|
||||||
val network: NetworkHelper by injectLazy()
|
fun getExtensionIconUrl(apkName: String): String {
|
||||||
|
return "/api/v1/extension/icon/$apkName"
|
||||||
fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
|
|
||||||
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl]
|
|
||||||
|
|
||||||
val saveDir = "${applicationDirs.extensionsRoot}/icon"
|
|
||||||
|
|
||||||
return getCachedImageResponse(saveDir, apkName) {
|
|
||||||
network.client.newCall(
|
|
||||||
GET(iconUrl)
|
|
||||||
).execute()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getExtensionIconUrl(apkName: String): String {
|
|
||||||
return "/api/v1/extension/icon/$apkName"
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,75 +23,86 @@ import org.jetbrains.exposed.sql.update
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object ExtensionsList {
|
object ExtensionsList {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
|
||||||
var lastUpdateCheck: Long = 0
|
var lastUpdateCheck: Long = 0
|
||||||
var updateMap = ConcurrentHashMap<String, Extension.Available>()
|
var updateMap = ConcurrentHashMap<String, Extension.Available>()
|
||||||
|
|
||||||
|
|
||||||
// const val ExtensionUpdateDelayTime = 60 * 1000 // 60,000 milliseconds = 60 seconds
|
// const val ExtensionUpdateDelayTime = 60 * 1000 // 60,000 milliseconds = 60 seconds
|
||||||
const val ExtensionUpdateDelayTime = 0
|
const val ExtensionUpdateDelayTime = 0
|
||||||
|
|
||||||
fun getExtensionList(): List<ExtensionDataClass> {
|
fun getExtensionList(): List<ExtensionDataClass> {
|
||||||
// update if {ExtensionUpdateDelayTime} seconds has passed or requested offline and database is empty
|
// update if {ExtensionUpdateDelayTime} seconds has passed or requested offline and database is empty
|
||||||
if (lastUpdateCheck + ExtensionUpdateDelayTime < System.currentTimeMillis()) {
|
if (lastUpdateCheck + ExtensionUpdateDelayTime < System.currentTimeMillis()) {
|
||||||
logger.debug("Getting extensions list from the internet")
|
logger.debug("Getting extensions list from the internet")
|
||||||
lastUpdateCheck = System.currentTimeMillis()
|
lastUpdateCheck = System.currentTimeMillis()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val foundExtensions = ExtensionGithubApi.findExtensions()
|
val foundExtensions = ExtensionGithubApi.findExtensions()
|
||||||
updateExtensionDatabase(foundExtensions)
|
updateExtensionDatabase(foundExtensions)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("used cached extension list")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.debug("used cached extension list")
|
return extensionTableAsDataClass()
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionTableAsDataClass()
|
fun extensionTableAsDataClass() = transaction {
|
||||||
}
|
ExtensionTable.selectAll().map {
|
||||||
|
ExtensionDataClass(
|
||||||
fun extensionTableAsDataClass() = transaction {
|
it[ExtensionTable.name],
|
||||||
ExtensionTable.selectAll().map {
|
it[ExtensionTable.pkgName],
|
||||||
ExtensionDataClass(
|
it[ExtensionTable.versionName],
|
||||||
it[ExtensionTable.name],
|
it[ExtensionTable.versionCode],
|
||||||
it[ExtensionTable.pkgName],
|
it[ExtensionTable.lang],
|
||||||
it[ExtensionTable.versionName],
|
it[ExtensionTable.isNsfw],
|
||||||
it[ExtensionTable.versionCode],
|
it[ExtensionTable.apkName],
|
||||||
it[ExtensionTable.lang],
|
getExtensionIconUrl(it[ExtensionTable.apkName]),
|
||||||
it[ExtensionTable.isNsfw],
|
it[ExtensionTable.isInstalled],
|
||||||
it[ExtensionTable.apkName],
|
it[ExtensionTable.hasUpdate],
|
||||||
getExtensionIconUrl(it[ExtensionTable.apkName]),
|
it[ExtensionTable.isObsolete],
|
||||||
it[ExtensionTable.isInstalled],
|
)
|
||||||
it[ExtensionTable.hasUpdate],
|
}
|
||||||
it[ExtensionTable.isObsolete],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateExtensionDatabase(foundExtensions: List<Extension.Available>) {
|
private fun updateExtensionDatabase(foundExtensions: List<Extension.Available>) {
|
||||||
transaction {
|
transaction {
|
||||||
foundExtensions.forEach { foundExtension ->
|
foundExtensions.forEach { foundExtension ->
|
||||||
val extensionRecord = ExtensionTable.select { ExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull()
|
val extensionRecord = ExtensionTable.select { ExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull()
|
||||||
if (extensionRecord != null) {
|
if (extensionRecord != null) {
|
||||||
if (extensionRecord[ExtensionTable.isInstalled]) {
|
if (extensionRecord[ExtensionTable.isInstalled]) {
|
||||||
if (foundExtension.versionCode > extensionRecord[ExtensionTable.versionCode]) {
|
if (foundExtension.versionCode > extensionRecord[ExtensionTable.versionCode]) {
|
||||||
// there is an update
|
// there is an update
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
||||||
it[hasUpdate] = true
|
it[hasUpdate] = true
|
||||||
}
|
}
|
||||||
updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
|
updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
|
||||||
} else if (foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode]) {
|
} else if (foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode]) {
|
||||||
// some how the user installed an invalid version
|
// some how the user installed an invalid version
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
||||||
it[isObsolete] = true
|
it[isObsolete] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the two are equal
|
||||||
|
// NOOP
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// the two are equal
|
// extension is not installed so we can overwrite the data without a care
|
||||||
// NOOP
|
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
||||||
|
it[name] = foundExtension.name
|
||||||
|
it[versionName] = foundExtension.versionName
|
||||||
|
it[versionCode] = foundExtension.versionCode
|
||||||
|
it[lang] = foundExtension.lang
|
||||||
|
it[isNsfw] = foundExtension.isNsfw
|
||||||
|
it[apkName] = foundExtension.apkName
|
||||||
|
it[iconUrl] = foundExtension.iconUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// extension is not installed so we can overwrite the data without a care
|
// insert new record
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
|
ExtensionTable.insert {
|
||||||
it[name] = foundExtension.name
|
it[name] = foundExtension.name
|
||||||
|
it[pkgName] = foundExtension.pkgName
|
||||||
it[versionName] = foundExtension.versionName
|
it[versionName] = foundExtension.versionName
|
||||||
it[versionCode] = foundExtension.versionCode
|
it[versionCode] = foundExtension.versionCode
|
||||||
it[lang] = foundExtension.lang
|
it[lang] = foundExtension.lang
|
||||||
@ -100,37 +111,24 @@ private fun updateExtensionDatabase(foundExtensions: List<Extension.Available>)
|
|||||||
it[iconUrl] = foundExtension.iconUrl
|
it[iconUrl] = foundExtension.iconUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// insert new record
|
|
||||||
ExtensionTable.insert {
|
|
||||||
it[name] = foundExtension.name
|
|
||||||
it[pkgName] = foundExtension.pkgName
|
|
||||||
it[versionName] = foundExtension.versionName
|
|
||||||
it[versionCode] = foundExtension.versionCode
|
|
||||||
it[lang] = foundExtension.lang
|
|
||||||
it[isNsfw] = foundExtension.isNsfw
|
|
||||||
it[apkName] = foundExtension.apkName
|
|
||||||
it[iconUrl] = foundExtension.iconUrl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// deal with obsolete extensions
|
// deal with obsolete extensions
|
||||||
ExtensionTable.selectAll().forEach { extensionRecord ->
|
ExtensionTable.selectAll().forEach { extensionRecord ->
|
||||||
val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[ExtensionTable.pkgName] }
|
val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[ExtensionTable.pkgName] }
|
||||||
if (foundExtension == null) {
|
if (foundExtension == null) {
|
||||||
// this extensions is obsolete
|
// this extensions is obsolete
|
||||||
if (extensionRecord[ExtensionTable.isInstalled]) {
|
if (extensionRecord[ExtensionTable.isInstalled]) {
|
||||||
// is installed so we should mark it as obsolete
|
// is installed so we should mark it as obsolete
|
||||||
ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }) {
|
ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }) {
|
||||||
it[isObsolete] = true
|
it[isObsolete] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// is not installed so we can remove the record without a care
|
||||||
|
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// is not installed so we can remove the record without a care
|
|
||||||
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -22,35 +22,35 @@ object Library {
|
|||||||
// TODO: `Category.isLanding` is to handle the default categories a new library manga gets,
|
// TODO: `Category.isLanding` is to handle the default categories a new library manga gets,
|
||||||
// ..implement that shit at some time...
|
// ..implement that shit at some time...
|
||||||
// ..also Consider to rename it to `isDefault`
|
// ..also Consider to rename it to `isDefault`
|
||||||
fun addMangaToLibrary(mangaId: Int) {
|
fun addMangaToLibrary(mangaId: Int) {
|
||||||
val manga = getManga(mangaId)
|
val manga = getManga(mangaId)
|
||||||
if (!manga.inLibrary) {
|
if (!manga.inLibrary) {
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
MangaTable.update({ MangaTable.id eq manga.id }) {
|
||||||
it[inLibrary] = true
|
it[inLibrary] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeMangaFromLibrary(mangaId: Int) {
|
||||||
|
val manga = getManga(mangaId)
|
||||||
|
if (manga.inLibrary) {
|
||||||
|
transaction {
|
||||||
|
MangaTable.update({ MangaTable.id eq manga.id }) {
|
||||||
|
it[inLibrary] = false
|
||||||
|
it[defaultCategory] = true
|
||||||
|
}
|
||||||
|
CategoryMangaTable.deleteWhere { CategoryMangaTable.manga eq mangaId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLibraryMangas(): List<MangaDataClass> {
|
||||||
|
return transaction {
|
||||||
|
MangaTable.select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) }.map {
|
||||||
|
MangaTable.toDataClass(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeMangaFromLibrary(mangaId: Int) {
|
|
||||||
val manga = getManga(mangaId)
|
|
||||||
if (manga.inLibrary) {
|
|
||||||
transaction {
|
|
||||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
|
||||||
it[inLibrary] = false
|
|
||||||
it[defaultCategory] = true
|
|
||||||
}
|
|
||||||
CategoryMangaTable.deleteWhere { CategoryMangaTable.manga eq mangaId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLibraryMangas(): List<MangaDataClass> {
|
|
||||||
return transaction {
|
|
||||||
MangaTable.select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) }.map {
|
|
||||||
MangaTable.toDataClass(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -15,99 +15,99 @@ import ir.armor.tachidesk.impl.Source.getSource
|
|||||||
import ir.armor.tachidesk.model.database.MangaStatus
|
import ir.armor.tachidesk.model.database.MangaStatus
|
||||||
import ir.armor.tachidesk.model.database.MangaTable
|
import ir.armor.tachidesk.model.database.MangaTable
|
||||||
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.server.applicationDirs
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
object Manga {
|
object Manga {
|
||||||
fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
||||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
|
|
||||||
return if (mangaEntry[MangaTable.initialized]) {
|
return if (mangaEntry[MangaTable.initialized]) {
|
||||||
MangaDataClass(
|
MangaDataClass(
|
||||||
mangaId,
|
mangaId,
|
||||||
mangaEntry[MangaTable.sourceReference].toString(),
|
mangaEntry[MangaTable.sourceReference].toString(),
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
mangaEntry[MangaTable.url],
|
||||||
mangaEntry[MangaTable.title],
|
mangaEntry[MangaTable.title],
|
||||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
||||||
|
|
||||||
true,
|
true,
|
||||||
|
|
||||||
mangaEntry[MangaTable.artist],
|
mangaEntry[MangaTable.artist],
|
||||||
mangaEntry[MangaTable.author],
|
mangaEntry[MangaTable.author],
|
||||||
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])
|
getSource(mangaEntry[MangaTable.sourceReference])
|
||||||
)
|
)
|
||||||
} else { // initialize manga
|
} else { // initialize manga
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
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]
|
||||||
title = mangaEntry[MangaTable.title]
|
title = mangaEntry[MangaTable.title]
|
||||||
|
}
|
||||||
|
).toBlocking().first()
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
|
||||||
|
it[MangaTable.initialized] = true
|
||||||
|
|
||||||
|
it[MangaTable.artist] = fetchedManga.artist
|
||||||
|
it[MangaTable.author] = fetchedManga.author
|
||||||
|
it[MangaTable.description] = fetchedManga.description
|
||||||
|
it[MangaTable.genre] = fetchedManga.genre
|
||||||
|
it[MangaTable.status] = fetchedManga.status
|
||||||
|
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
||||||
|
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
).toBlocking().first()
|
|
||||||
|
|
||||||
transaction {
|
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
val newThumbnail = mangaEntry[MangaTable.thumbnail_url]
|
||||||
|
|
||||||
it[MangaTable.initialized] = true
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
mangaEntry[MangaTable.sourceReference].toString(),
|
||||||
|
|
||||||
it[MangaTable.artist] = fetchedManga.artist
|
mangaEntry[MangaTable.url],
|
||||||
it[MangaTable.author] = fetchedManga.author
|
mangaEntry[MangaTable.title],
|
||||||
it[MangaTable.description] = fetchedManga.description
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
||||||
it[MangaTable.genre] = fetchedManga.genre
|
|
||||||
it[MangaTable.status] = fetchedManga.status
|
true,
|
||||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
|
||||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
fetchedManga.artist,
|
||||||
}
|
fetchedManga.author,
|
||||||
|
fetchedManga.description,
|
||||||
|
fetchedManga.genre,
|
||||||
|
MangaStatus.valueOf(fetchedManga.status).name,
|
||||||
|
false,
|
||||||
|
getSource(mangaEntry[MangaTable.sourceReference])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||||
val newThumbnail = mangaEntry[MangaTable.thumbnail_url]
|
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
|
val saveDir = ApplicationDirs.thumbnailsRoot
|
||||||
|
val fileName = mangaId.toString()
|
||||||
|
|
||||||
MangaDataClass(
|
return getCachedImageResponse(saveDir, fileName) {
|
||||||
mangaId,
|
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||||
mangaEntry[MangaTable.sourceReference].toString(),
|
val source = getHttpSource(sourceId)
|
||||||
|
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
|
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
||||||
|
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
||||||
|
}
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
source.client.newCall(
|
||||||
mangaEntry[MangaTable.title],
|
GET(thumbnailUrl, source.headers)
|
||||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
).execute()
|
||||||
|
}
|
||||||
true,
|
|
||||||
|
|
||||||
fetchedManga.artist,
|
|
||||||
fetchedManga.author,
|
|
||||||
fetchedManga.description,
|
|
||||||
fetchedManga.genre,
|
|
||||||
MangaStatus.valueOf(fetchedManga.status).name,
|
|
||||||
false,
|
|
||||||
getSource(mangaEntry[MangaTable.sourceReference])
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
|
||||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
|
||||||
val saveDir = applicationDirs.thumbnailsRoot
|
|
||||||
val fileName = mangaId.toString()
|
|
||||||
|
|
||||||
return getCachedImageResponse(saveDir, fileName) {
|
|
||||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
|
||||||
val source = getHttpSource(sourceId)
|
|
||||||
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
|
||||||
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
|
||||||
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
|
||||||
}
|
|
||||||
|
|
||||||
source.client.newCall(
|
|
||||||
GET(thumbnailUrl, source.headers)
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,84 +18,84 @@ import org.jetbrains.exposed.sql.select
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
object MangaList {
|
object MangaList {
|
||||||
fun proxyThumbnailUrl(mangaId: Int): String {
|
fun proxyThumbnailUrl(mangaId: Int): String {
|
||||||
return "/api/v1/manga/$mangaId/thumbnail"
|
return "/api/v1/manga/$mangaId/thumbnail"
|
||||||
}
|
|
||||||
|
|
||||||
fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
|
||||||
val source = getHttpSource(sourceId.toLong())
|
|
||||||
val mangasPage = if (popular) {
|
|
||||||
source.fetchPopularManga(pageNum).toBlocking().first()
|
|
||||||
} else {
|
|
||||||
if (source.supportsLatest)
|
|
||||||
source.fetchLatestUpdates(pageNum).toBlocking().first()
|
|
||||||
else
|
|
||||||
throw Exception("Source $source doesn't support latest")
|
|
||||||
}
|
}
|
||||||
return mangasPage.processEntries(sourceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
|
fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
||||||
val mangasPage = this
|
val source = getHttpSource(sourceId.toLong())
|
||||||
val mangaList = transaction {
|
val mangasPage = if (popular) {
|
||||||
return@transaction mangasPage.mangas.map { manga ->
|
source.fetchPopularManga(pageNum).toBlocking().first()
|
||||||
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
} else {
|
||||||
if (mangaEntry == null) { // create manga entry
|
if (source.supportsLatest)
|
||||||
val mangaId = MangaTable.insertAndGetId {
|
source.fetchLatestUpdates(pageNum).toBlocking().first()
|
||||||
it[url] = manga.url
|
else
|
||||||
it[title] = manga.title
|
throw Exception("Source $source doesn't support latest")
|
||||||
|
}
|
||||||
|
return mangasPage.processEntries(sourceId)
|
||||||
|
}
|
||||||
|
|
||||||
it[artist] = manga.artist
|
fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
|
||||||
it[author] = manga.author
|
val mangasPage = this
|
||||||
it[description] = manga.description
|
val mangaList = transaction {
|
||||||
it[genre] = manga.genre
|
return@transaction mangasPage.mangas.map { manga ->
|
||||||
it[status] = manga.status
|
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
||||||
it[thumbnail_url] = manga.thumbnail_url
|
if (mangaEntry == null) { // create manga entry
|
||||||
|
val mangaId = MangaTable.insertAndGetId {
|
||||||
|
it[url] = manga.url
|
||||||
|
it[title] = manga.title
|
||||||
|
|
||||||
it[sourceReference] = sourceId
|
it[artist] = manga.artist
|
||||||
}.value
|
it[author] = manga.author
|
||||||
|
it[description] = manga.description
|
||||||
|
it[genre] = manga.genre
|
||||||
|
it[status] = manga.status
|
||||||
|
it[thumbnail_url] = manga.thumbnail_url
|
||||||
|
|
||||||
MangaDataClass(
|
it[sourceReference] = sourceId
|
||||||
mangaId,
|
}.value
|
||||||
sourceId.toString(),
|
|
||||||
|
|
||||||
manga.url,
|
MangaDataClass(
|
||||||
manga.title,
|
mangaId,
|
||||||
proxyThumbnailUrl(mangaId),
|
sourceId.toString(),
|
||||||
|
|
||||||
manga.initialized,
|
manga.url,
|
||||||
|
manga.title,
|
||||||
|
proxyThumbnailUrl(mangaId),
|
||||||
|
|
||||||
manga.artist,
|
manga.initialized,
|
||||||
manga.author,
|
|
||||||
manga.description,
|
|
||||||
manga.genre,
|
|
||||||
MangaStatus.valueOf(manga.status).name
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val mangaId = mangaEntry[MangaTable.id].value
|
|
||||||
MangaDataClass(
|
|
||||||
mangaId,
|
|
||||||
sourceId.toString(),
|
|
||||||
|
|
||||||
manga.url,
|
manga.artist,
|
||||||
manga.title,
|
manga.author,
|
||||||
proxyThumbnailUrl(mangaId),
|
manga.description,
|
||||||
|
manga.genre,
|
||||||
|
MangaStatus.valueOf(manga.status).name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val mangaId = mangaEntry[MangaTable.id].value
|
||||||
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
sourceId.toString(),
|
||||||
|
|
||||||
true,
|
manga.url,
|
||||||
|
manga.title,
|
||||||
|
proxyThumbnailUrl(mangaId),
|
||||||
|
|
||||||
mangaEntry[MangaTable.artist],
|
true,
|
||||||
mangaEntry[MangaTable.author],
|
|
||||||
mangaEntry[MangaTable.description],
|
mangaEntry[MangaTable.artist],
|
||||||
mangaEntry[MangaTable.genre],
|
mangaEntry[MangaTable.author],
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
mangaEntry[MangaTable.description],
|
||||||
mangaEntry[MangaTable.inLibrary]
|
mangaEntry[MangaTable.genre],
|
||||||
)
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
|
mangaEntry[MangaTable.inLibrary]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return PagedMangaListDataClass(
|
||||||
|
mangaList,
|
||||||
|
mangasPage.hasNextPage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return PagedMangaListDataClass(
|
|
||||||
mangaList,
|
|
||||||
mangasPage.hasNextPage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import ir.armor.tachidesk.model.database.ChapterTable
|
|||||||
import ir.armor.tachidesk.model.database.MangaTable
|
import ir.armor.tachidesk.model.database.MangaTable
|
||||||
import ir.armor.tachidesk.model.database.PageTable
|
import ir.armor.tachidesk.model.database.PageTable
|
||||||
import ir.armor.tachidesk.model.database.SourceTable
|
import ir.armor.tachidesk.model.database.SourceTable
|
||||||
import ir.armor.tachidesk.server.applicationDirs
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@ -22,72 +22,72 @@ import org.jetbrains.exposed.sql.update
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
object Page{
|
object Page {
|
||||||
/**
|
/**
|
||||||
* A page might have a imageUrl ready from the get go, or we might need to
|
* A page might have a imageUrl ready from the get go, or we might need to
|
||||||
* go an extra step and call fetchImageUrl to get it.
|
* go an extra step and call fetchImageUrl to get it.
|
||||||
*/
|
*/
|
||||||
fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
||||||
if (page.imageUrl == null) {
|
if (page.imageUrl == null) {
|
||||||
page.imageUrl = source.fetchImageUrl(page).toBlocking().first()!!
|
page.imageUrl = source.fetchImageUrl(page).toBlocking().first()!!
|
||||||
|
}
|
||||||
|
return page.imageUrl!!
|
||||||
}
|
}
|
||||||
return page.imageUrl!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int): Pair<InputStream, String> {
|
fun getPageImage(mangaId: Int, chapterIndex: 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])
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||||
val chapterEntry = transaction {
|
val chapterEntry = transaction {
|
||||||
ChapterTable.select {
|
ChapterTable.select {
|
||||||
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
(ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||||
}.firstOrNull()!!
|
}.firstOrNull()!!
|
||||||
}
|
}
|
||||||
val chapterId = chapterEntry[ChapterTable.id].value
|
val chapterId = chapterEntry[ChapterTable.id].value
|
||||||
|
|
||||||
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()!! }
|
||||||
|
|
||||||
val tachiPage = Page(
|
val tachiPage = Page(
|
||||||
pageEntry[PageTable.index],
|
pageEntry[PageTable.index],
|
||||||
pageEntry[PageTable.url],
|
pageEntry[PageTable.url],
|
||||||
pageEntry[PageTable.imageUrl]
|
pageEntry[PageTable.imageUrl]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (pageEntry[PageTable.imageUrl] == null) {
|
if (pageEntry[PageTable.imageUrl] == null) {
|
||||||
transaction {
|
transaction {
|
||||||
PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq index) }) {
|
PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq index) }) {
|
||||||
it[imageUrl] = getTrueImageUrl(tachiPage, source)
|
it[imageUrl] = getTrueImageUrl(tachiPage, source)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val saveDir = getChapterDir(mangaId, chapterId)
|
||||||
|
File(saveDir).mkdirs()
|
||||||
|
val fileName = index.toString()
|
||||||
|
|
||||||
|
return getCachedImageResponse(saveDir, fileName) {
|
||||||
|
source.fetchImage(tachiPage).toBlocking().first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val saveDir = getChapterDir(mangaId, chapterId)
|
|
||||||
File(saveDir).mkdirs()
|
|
||||||
val fileName = index.toString()
|
|
||||||
|
|
||||||
return getCachedImageResponse(saveDir, fileName) {
|
|
||||||
source.fetchImage(tachiPage).toBlocking().first()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: rewrite this to match tachiyomi
|
// TODO: rewrite this to match tachiyomi
|
||||||
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]
|
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()!! }
|
||||||
|
|
||||||
val chapterDir = when {
|
val chapterDir = when {
|
||||||
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
||||||
else -> chapterEntry[ChapterTable.name]
|
else -> chapterEntry[ChapterTable.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
val mangaTitle = mangaEntry[MangaTable.title]
|
||||||
|
val sourceName = source.toString()
|
||||||
|
|
||||||
|
val mangaDir = "${ApplicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
|
||||||
|
// make sure dirs exist
|
||||||
|
File(mangaDir).mkdirs()
|
||||||
|
return mangaDir
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaTitle = mangaEntry[MangaTable.title]
|
|
||||||
val sourceName = source.toString()
|
|
||||||
|
|
||||||
val mangaDir = "${applicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
|
|
||||||
// make sure dirs exist
|
|
||||||
File(mangaDir).mkdirs()
|
|
||||||
return mangaDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,29 +13,29 @@ import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass
|
|||||||
|
|
||||||
object Search {
|
object Search {
|
||||||
// TODO
|
// TODO
|
||||||
fun sourceFilters(sourceId: Long) {
|
fun sourceFilters(sourceId: Long) {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
// source.getFilterList().toItems()
|
// source.getFilterList().toItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).toBlocking().first()
|
val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).toBlocking().first()
|
||||||
return searchManga.processEntries(sourceId)
|
return searchManga.processEntries(sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceGlobalSearch(searchTerm: String) {
|
fun sourceGlobalSearch(searchTerm: String) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FilterWrapper(
|
data class FilterWrapper(
|
||||||
val type: String,
|
val type: String,
|
||||||
val filter: Any
|
val filter: Any
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
|
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
|
||||||
*/
|
*/
|
||||||
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
||||||
// return mapNotNull { filter ->
|
// return mapNotNull { filter ->
|
||||||
// when (filter) {
|
// when (filter) {
|
||||||
|
@ -14,7 +14,7 @@ import ir.armor.tachidesk.impl.Extension.loadExtensionInstance
|
|||||||
import ir.armor.tachidesk.model.database.ExtensionTable
|
import ir.armor.tachidesk.model.database.ExtensionTable
|
||||||
import ir.armor.tachidesk.model.database.SourceTable
|
import ir.armor.tachidesk.model.database.SourceTable
|
||||||
import ir.armor.tachidesk.model.dataclass.SourceDataClass
|
import ir.armor.tachidesk.model.dataclass.SourceDataClass
|
||||||
import ir.armor.tachidesk.server.applicationDirs
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
@ -22,67 +22,67 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object Source {
|
object Source {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
|
private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
|
||||||
|
|
||||||
fun getHttpSource(sourceId: Long): HttpSource {
|
fun getHttpSource(sourceId: Long): HttpSource {
|
||||||
val cachedResult: HttpSource? = sourceCache[sourceId]
|
val cachedResult: HttpSource? = sourceCache[sourceId]
|
||||||
if (cachedResult != null) {
|
if (cachedResult != null) {
|
||||||
logger.debug("used cached HttpSource: ${cachedResult.name}")
|
logger.debug("used cached HttpSource: ${cachedResult.name}")
|
||||||
return cachedResult
|
return cachedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
val sourceRecord = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
|
||||||
|
|
||||||
|
val extensionId = sourceRecord[SourceTable.extension]
|
||||||
|
val extensionRecord = ExtensionTable.select { ExtensionTable.id eq extensionId }.firstOrNull()!!
|
||||||
|
val apkName = extensionRecord[ExtensionTable.apkName]
|
||||||
|
val className = extensionRecord[ExtensionTable.classFQName]
|
||||||
|
val jarName = apkName.substringBefore(".apk") + ".jar"
|
||||||
|
val jarPath = "${ApplicationDirs.extensionsRoot}/$jarName"
|
||||||
|
|
||||||
|
val extensionInstance = loadExtensionInstance(jarPath, className)
|
||||||
|
|
||||||
|
if (sourceRecord[SourceTable.partOfFactorySource]) {
|
||||||
|
(extensionInstance as SourceFactory).createSources().forEach {
|
||||||
|
sourceCache[it.id] = it as HttpSource
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(extensionInstance as HttpSource).also {
|
||||||
|
sourceCache[it.id] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceCache[sourceId]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction {
|
fun getSourceList(): List<SourceDataClass> {
|
||||||
val sourceRecord = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
|
return transaction {
|
||||||
|
SourceTable.selectAll().map {
|
||||||
val extensionId = sourceRecord[SourceTable.extension]
|
SourceDataClass(
|
||||||
val extensionRecord = ExtensionTable.select { ExtensionTable.id eq extensionId }.firstOrNull()!!
|
it[SourceTable.id].value.toString(),
|
||||||
val apkName = extensionRecord[ExtensionTable.apkName]
|
it[SourceTable.name],
|
||||||
val className = extensionRecord[ExtensionTable.classFQName]
|
it[SourceTable.lang],
|
||||||
val jarName = apkName.substringBefore(".apk") + ".jar"
|
getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]),
|
||||||
val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
|
getHttpSource(it[SourceTable.id].value).supportsLatest
|
||||||
|
)
|
||||||
val extensionInstance = loadExtensionInstance(jarPath, className)
|
|
||||||
|
|
||||||
if (sourceRecord[SourceTable.partOfFactorySource]) {
|
|
||||||
(extensionInstance as SourceFactory).createSources().forEach {
|
|
||||||
sourceCache[it.id] = it as HttpSource
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(extensionInstance as HttpSource).also {
|
|
||||||
sourceCache[it.id] = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sourceCache[sourceId]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourceList(): List<SourceDataClass> {
|
fun getSource(sourceId: Long): SourceDataClass {
|
||||||
return transaction {
|
return transaction {
|
||||||
SourceTable.selectAll().map {
|
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||||
|
|
||||||
SourceDataClass(
|
SourceDataClass(
|
||||||
it[SourceTable.id].value.toString(),
|
sourceId.toString(),
|
||||||
it[SourceTable.name],
|
source?.get(SourceTable.name),
|
||||||
it[SourceTable.lang],
|
source?.get(SourceTable.lang),
|
||||||
getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]),
|
source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] },
|
||||||
getHttpSource(it[SourceTable.id].value).supportsLatest
|
source?.let { getHttpSource(sourceId).supportsLatest }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSource(sourceId: Long): SourceDataClass {
|
|
||||||
return transaction {
|
|
||||||
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
|
||||||
|
|
||||||
SourceDataClass(
|
|
||||||
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 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,14 +14,14 @@ import ir.armor.tachidesk.model.database.ExtensionTable
|
|||||||
import ir.armor.tachidesk.model.database.MangaTable
|
import ir.armor.tachidesk.model.database.MangaTable
|
||||||
import ir.armor.tachidesk.model.database.PageTable
|
import ir.armor.tachidesk.model.database.PageTable
|
||||||
import ir.armor.tachidesk.model.database.SourceTable
|
import ir.armor.tachidesk.model.database.SourceTable
|
||||||
import ir.armor.tachidesk.server.applicationDirs
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
object DBMangaer {
|
object DBMangaer {
|
||||||
val db by lazy {
|
val db by lazy {
|
||||||
Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
|
Database.connect("jdbc:h2:${ApplicationDirs.dataRoot}/database", "org.h2.Driver")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,248 +41,250 @@ import java.io.IOException
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
object JavalinSetup {
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
fun javalinSetup() {
|
fun javalinSetup() {
|
||||||
var hasWebUiBundled = false
|
var hasWebUiBundled = false
|
||||||
|
|
||||||
val app = Javalin.create { config ->
|
val app = Javalin.create { config ->
|
||||||
try {
|
try {
|
||||||
Main::class.java.getResource("/react/index.html")
|
Main::class.java.getResource("/react/index.html")
|
||||||
hasWebUiBundled = true
|
hasWebUiBundled = true
|
||||||
config.addStaticFiles("/react")
|
config.addStaticFiles("/react")
|
||||||
config.addSinglePageRoot("/", "/react/index.html")
|
config.addSinglePageRoot("/", "/react/index.html")
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
logger.warn("react build files are missing.")
|
logger.warn("react build files are missing.")
|
||||||
hasWebUiBundled = false
|
hasWebUiBundled = false
|
||||||
|
}
|
||||||
|
config.enableCorsForAllOrigins()
|
||||||
|
}.start(serverConfig.ip, serverConfig.port)
|
||||||
|
if (hasWebUiBundled && serverConfig.initialOpenInBrowserEnabled) {
|
||||||
|
openInBrowser()
|
||||||
}
|
}
|
||||||
config.enableCorsForAllOrigins()
|
|
||||||
}.start(serverConfig.ip, serverConfig.port)
|
|
||||||
if (hasWebUiBundled && serverConfig.initialOpenInBrowserEnabled) {
|
|
||||||
openInBrowser()
|
|
||||||
}
|
|
||||||
|
|
||||||
app.exception(NullPointerException::class.java) { e, ctx ->
|
app.exception(NullPointerException::class.java) { e, ctx ->
|
||||||
logger.error("NullPointerException while handling the request", e)
|
logger.error("NullPointerException while handling the request", e)
|
||||||
ctx.status(404)
|
ctx.status(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(IOException::class.java) { e, ctx ->
|
app.exception(IOException::class.java) { e, ctx ->
|
||||||
logger.error("IOException while handling the request", e)
|
logger.error("IOException while handling the request", e)
|
||||||
ctx.status(500)
|
ctx.status(500)
|
||||||
ctx.result(e.message ?: "Internal Server Error")
|
ctx.result(e.message ?: "Internal Server Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/extension/list") { ctx ->
|
app.get("/api/v1/extension/list") { ctx ->
|
||||||
ctx.json(getExtensionList())
|
ctx.json(getExtensionList())
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/extension/install/:pkgName") { ctx ->
|
app.get("/api/v1/extension/install/:pkgName") { ctx ->
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
|
|
||||||
ctx.status(
|
ctx.status(
|
||||||
installExtension(pkgName)
|
installExtension(pkgName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/extension/update/:pkgName") { ctx ->
|
app.get("/api/v1/extension/update/:pkgName") { ctx ->
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
|
|
||||||
ctx.status(
|
ctx.status(
|
||||||
updateExtension(pkgName)
|
updateExtension(pkgName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/extension/uninstall/:pkgName") { ctx ->
|
app.get("/api/v1/extension/uninstall/:pkgName") { ctx ->
|
||||||
val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
|
|
||||||
uninstallExtension(pkgName)
|
uninstallExtension(pkgName)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// icon for extension named `apkName`
|
// icon for extension named `apkName`
|
||||||
app.get("/api/v1/extension/icon/:apkName") { ctx ->
|
app.get("/api/v1/extension/icon/:apkName") { ctx ->
|
||||||
val apkName = ctx.pathParam("apkName")
|
val apkName = ctx.pathParam("apkName")
|
||||||
val result = getExtensionIcon(apkName)
|
val result = getExtensionIcon(apkName)
|
||||||
|
|
||||||
ctx.result(result.first)
|
ctx.result(result.first)
|
||||||
ctx.header("content-type", result.second)
|
ctx.header("content-type", result.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// list of sources
|
// list of sources
|
||||||
app.get("/api/v1/source/list") { ctx ->
|
app.get("/api/v1/source/list") { ctx ->
|
||||||
ctx.json(getSourceList())
|
ctx.json(getSourceList())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch source with id `sourceId`
|
// fetch source with id `sourceId`
|
||||||
app.get("/api/v1/source/:sourceId") { ctx ->
|
app.get("/api/v1/source/:sourceId") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
ctx.json(getSource(sourceId))
|
ctx.json(getSource(sourceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// popular mangas from source with id `sourceId`
|
// popular mangas from source with id `sourceId`
|
||||||
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(getMangaList(sourceId, pageNum, popular = true))
|
ctx.json(getMangaList(sourceId, pageNum, popular = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// latest mangas from source with id `sourceId`
|
// latest mangas from source with id `sourceId`
|
||||||
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
|
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(getMangaList(sourceId, pageNum, popular = false))
|
ctx.json(getMangaList(sourceId, pageNum, popular = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
// get manga info
|
// get manga info
|
||||||
app.get("/api/v1/manga/:mangaId/") { ctx ->
|
app.get("/api/v1/manga/:mangaId/") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
ctx.json(getManga(mangaId))
|
ctx.json(getManga(mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// manga thumbnail
|
// manga thumbnail
|
||||||
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
|
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
val result = getMangaThumbnail(mangaId)
|
val result = getMangaThumbnail(mangaId)
|
||||||
|
|
||||||
ctx.result(result.first)
|
ctx.result(result.first)
|
||||||
ctx.header("content-type", result.second)
|
ctx.header("content-type", result.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds the manga to library
|
// adds the manga to library
|
||||||
app.get("api/v1/manga/:mangaId/library") { ctx ->
|
app.get("api/v1/manga/:mangaId/library") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
addMangaToLibrary(mangaId)
|
addMangaToLibrary(mangaId)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes the manga from the library
|
// removes the manga from the library
|
||||||
app.delete("api/v1/manga/:mangaId/library") { ctx ->
|
app.delete("api/v1/manga/:mangaId/library") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
removeMangaFromLibrary(mangaId)
|
removeMangaFromLibrary(mangaId)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// list manga's categories
|
// list manga's categories
|
||||||
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
ctx.json(getMangaCategories(mangaId))
|
ctx.json(getMangaCategories(mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds the manga to category
|
// adds the manga to category
|
||||||
app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
addMangaToCategory(mangaId, categoryId)
|
addMangaToCategory(mangaId, categoryId)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes the manga from the category
|
// removes the manga from the category
|
||||||
app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
removeMangaFromCategory(mangaId, categoryId)
|
removeMangaFromCategory(mangaId, categoryId)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
ctx.json(getChapterList(mangaId))
|
ctx.json(getChapterList(mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||||
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
ctx.json(getChapter(chapterIndex, mangaId))
|
ctx.json(getChapter(chapterIndex, mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx ->
|
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
val index = ctx.pathParam("index").toInt()
|
val index = ctx.pathParam("index").toInt()
|
||||||
val result = getPageImage(mangaId, chapterIndex, index)
|
val result = getPageImage(mangaId, chapterIndex, index)
|
||||||
|
|
||||||
ctx.result(result.first)
|
ctx.result(result.first)
|
||||||
ctx.header("content-type", result.second)
|
ctx.header("content-type", result.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// global search
|
// global search
|
||||||
app.get("/api/v1/search/:searchTerm") { ctx ->
|
app.get("/api/v1/search/:searchTerm") { ctx ->
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
ctx.json(sourceGlobalSearch(searchTerm))
|
ctx.json(sourceGlobalSearch(searchTerm))
|
||||||
}
|
}
|
||||||
|
|
||||||
// single source search
|
// single source search
|
||||||
app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
|
app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(sourceSearch(sourceId, searchTerm, pageNum))
|
ctx.json(sourceSearch(sourceId, searchTerm, pageNum))
|
||||||
}
|
}
|
||||||
|
|
||||||
// source filter list
|
// source filter list
|
||||||
app.get("/api/v1/source/:sourceId/filters/") { ctx ->
|
app.get("/api/v1/source/:sourceId/filters/") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
ctx.json(sourceFilters(sourceId))
|
ctx.json(sourceFilters(sourceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// lists mangas that have no category assigned
|
// lists mangas that have no category assigned
|
||||||
app.get("/api/v1/library/") { ctx ->
|
app.get("/api/v1/library/") { ctx ->
|
||||||
ctx.json(getLibraryMangas())
|
ctx.json(getLibraryMangas())
|
||||||
}
|
}
|
||||||
|
|
||||||
// category list
|
// category list
|
||||||
app.get("/api/v1/category/") { ctx ->
|
app.get("/api/v1/category/") { ctx ->
|
||||||
ctx.json(getCategoryList())
|
ctx.json(getCategoryList())
|
||||||
}
|
}
|
||||||
|
|
||||||
// category create
|
// category create
|
||||||
app.post("/api/v1/category/") { ctx ->
|
app.post("/api/v1/category/") { ctx ->
|
||||||
val name = ctx.formParam("name")!!
|
val name = ctx.formParam("name")!!
|
||||||
createCategory(name)
|
createCategory(name)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// category modification
|
// category modification
|
||||||
app.patch("/api/v1/category/:categoryId") { ctx ->
|
app.patch("/api/v1/category/:categoryId") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
val name = ctx.formParam("name")
|
val name = ctx.formParam("name")
|
||||||
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
||||||
updateCategory(categoryId, name, isLanding)
|
updateCategory(categoryId, name, isLanding)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// category re-ordering
|
// category re-ordering
|
||||||
app.patch("/api/v1/category/:categoryId/reorder") { ctx ->
|
app.patch("/api/v1/category/:categoryId/reorder") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
val from = ctx.formParam("from")!!.toInt()
|
val from = ctx.formParam("from")!!.toInt()
|
||||||
val to = ctx.formParam("to")!!.toInt()
|
val to = ctx.formParam("to")!!.toInt()
|
||||||
reorderCategory(categoryId, from, to)
|
reorderCategory(categoryId, from, to)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// category delete
|
// category delete
|
||||||
app.delete("/api/v1/category/:categoryId") { ctx ->
|
app.delete("/api/v1/category/:categoryId") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
removeCategory(categoryId)
|
removeCategory(categoryId)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the manga list associated with a category
|
// returns the manga list associated with a category
|
||||||
app.get("/api/v1/category/:categoryId") { ctx ->
|
app.get("/api/v1/category/:categoryId") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
ctx.json(getCategoryMangaList(categoryId))
|
ctx.json(getCategoryMangaList(categoryId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// expects a Tachiyomi legacy backup file to be uploaded
|
// expects a Tachiyomi legacy backup file to be uploaded
|
||||||
app.get("/api/v1/backup/legacy/import") { ctx ->
|
app.get("/api/v1/backup/legacy/import") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
ctx.json(getCategoryMangaList(categoryId))
|
ctx.json(getCategoryMangaList(categoryId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a Tachiyomi legacy backup file created from the current database
|
// returns a Tachiyomi legacy backup file created from the current database
|
||||||
app.get("/api/v1/backup/legacy/export") { ctx ->
|
app.get("/api/v1/backup/legacy/export") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
ctx.json(getCategoryMangaList(categoryId))
|
ctx.json(getCategoryMangaList(categoryId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import java.io.File
|
|||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
object applicationDirs {
|
object ApplicationDirs {
|
||||||
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!!
|
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!!
|
||||||
val extensionsRoot = "$dataRoot/extensions"
|
val extensionsRoot = "$dataRoot/extensions"
|
||||||
val thumbnailsRoot = "$dataRoot/thumbnails"
|
val thumbnailsRoot = "$dataRoot/thumbnails"
|
||||||
@ -50,17 +50,17 @@ fun applicationSetup() {
|
|||||||
|
|
||||||
// make dirs we need
|
// make dirs we need
|
||||||
listOf(
|
listOf(
|
||||||
applicationDirs.dataRoot,
|
ApplicationDirs.dataRoot,
|
||||||
applicationDirs.extensionsRoot,
|
ApplicationDirs.extensionsRoot,
|
||||||
"${applicationDirs.extensionsRoot}/icon",
|
"${ApplicationDirs.extensionsRoot}/icon",
|
||||||
applicationDirs.thumbnailsRoot
|
ApplicationDirs.thumbnailsRoot
|
||||||
).forEach {
|
).forEach {
|
||||||
File(it).mkdirs()
|
File(it).mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
// create conf file if doesn't exist
|
// create conf file if doesn't exist
|
||||||
try {
|
try {
|
||||||
val dataConfFile = File("${applicationDirs.dataRoot}/server.conf")
|
val dataConfFile = File("${ApplicationDirs.dataRoot}/server.conf")
|
||||||
if (!dataConfFile.exists()) {
|
if (!dataConfFile.exists()) {
|
||||||
Main::class.java.getResourceAsStream("/server-reference.conf").use { input ->
|
Main::class.java.getResourceAsStream("/server-reference.conf").use { input ->
|
||||||
dataConfFile.outputStream().use { output ->
|
dataConfFile.outputStream().use { output ->
|
||||||
|
@ -20,8 +20,8 @@ import java.io.IOException
|
|||||||
fun openInBrowser() {
|
fun openInBrowser() {
|
||||||
try {
|
try {
|
||||||
Desktop.browseURL("http://127.0.0.1:4567")
|
Desktop.browseURL("http://127.0.0.1:4567")
|
||||||
} catch (e1: IOException) {
|
} catch (e: Exception) {
|
||||||
e1.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user