From 5656016700e9099d8739a8b37580fe79a12415a2 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Tue, 30 Mar 2021 21:10:41 +0430 Subject: [PATCH] let's not polute the namespace together --- .../main/kotlin/ir/armor/tachidesk/Main.kt | 2 +- .../ir/armor/tachidesk/impl/Category.kt | 54 +-- .../ir/armor/tachidesk/impl/CategoryManga.kt | 63 ++- .../kotlin/ir/armor/tachidesk/impl/Chapter.kt | 56 +-- .../ir/armor/tachidesk/impl/Extension.kt | 324 +++++++------- .../ir/armor/tachidesk/impl/ExtensionsList.kt | 156 ++++--- .../kotlin/ir/armor/tachidesk/impl/Library.kt | 56 +-- .../kotlin/ir/armor/tachidesk/impl/Manga.kt | 152 +++---- .../ir/armor/tachidesk/impl/MangaList.kt | 132 +++--- .../kotlin/ir/armor/tachidesk/impl/Page.kt | 112 ++--- .../kotlin/ir/armor/tachidesk/impl/Search.kt | 36 +- .../kotlin/ir/armor/tachidesk/impl/Source.kt | 104 ++--- .../tachidesk/model/dataclass/DBMangaer.kt | 4 +- .../ir/armor/tachidesk/server/JavalinSetup.kt | 400 +++++++++--------- .../ir/armor/tachidesk/server/ServerSetup.kt | 12 +- .../armor/tachidesk/server/util/SystemTray.kt | 4 +- 16 files changed, 833 insertions(+), 834 deletions(-) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 50e3f1f..3d1da5a 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -7,8 +7,8 @@ package ir.armor.tachidesk * 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/. */ +import ir.armor.tachidesk.server.JavalinSetup.javalinSetup import ir.armor.tachidesk.server.applicationSetup -import ir.armor.tachidesk.server.javalinSetup class Main { companion object { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt index 40e138a..2a0f0bc 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt @@ -45,34 +45,34 @@ object Category { } /** - * Move the category from position `from` to `to` - */ -fun reorderCategory(categoryId: Int, from: Int, to: Int) { - transaction { - val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList() - categories.add(to - 1, categories.removeAt(from - 1)) - categories.forEachIndexed { index, cat -> - CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) { - it[CategoryTable.order] = index + 1 + * Move the category from position `from` to `to` + */ + fun reorderCategory(categoryId: Int, from: Int, to: Int) { + transaction { + val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList() + categories.add(to - 1, categories.removeAt(from - 1)) + categories.forEachIndexed { index, cat -> + CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) { + 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 { + 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 { - return transaction { - CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).map { - CategoryTable.toDataClass(it) - } - } -} -} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt index 401267b..69a512a 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt @@ -22,52 +22,51 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update object CategoryManga { -fun addMangaToCategory(mangaId: Int, categoryId: Int) { - transaction { - if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) { - CategoryMangaTable.insert { - it[CategoryMangaTable.category] = categoryId - it[CategoryMangaTable.manga] = mangaId - } + fun addMangaToCategory(mangaId: Int, categoryId: Int) { + transaction { + if (CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.firstOrNull() == null) { + CategoryMangaTable.insert { + it[CategoryMangaTable.category] = categoryId + it[CategoryMangaTable.manga] = mangaId + } - MangaTable.update({ MangaTable.id eq mangaId }) { - it[MangaTable.defaultCategory] = false + MangaTable.update({ MangaTable.id eq mangaId }) { + it[MangaTable.defaultCategory] = false + } } } } -} -fun removeMangaFromCategory(mangaId: Int, categoryId: Int) { - transaction { - CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) } - if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) { - MangaTable.update({ MangaTable.id eq mangaId }) { - it[MangaTable.defaultCategory] = true + fun removeMangaFromCategory(mangaId: Int, categoryId: Int) { + transaction { + CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) } + if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) { + MangaTable.update({ MangaTable.id eq mangaId }) { + it[MangaTable.defaultCategory] = true + } } } } -} - /** - * list of mangas that belong to a category - */ -fun getCategoryMangaList(categoryId: Int): List { - return transaction { - CategoryMangaTable.innerJoin(MangaTable).select { CategoryMangaTable.category eq categoryId }.map { - MangaTable.toDataClass(it) + * list of mangas that belong to a category + */ + fun getCategoryMangaList(categoryId: Int): List { + return transaction { + CategoryMangaTable.innerJoin(MangaTable).select { CategoryMangaTable.category eq categoryId }.map { + MangaTable.toDataClass(it) + } } } -} /** - * list of categories that a manga belongs to - */ -fun getMangaCategories(mangaId: Int): List { - return transaction { - CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map { - CategoryTable.toDataClass(it) + * list of categories that a manga belongs to + */ + fun getMangaCategories(mangaId: Int): List { + return transaction { + CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map { + CategoryTable.toDataClass(it) + } } } } -} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt index 26165b9..6794d4b 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt @@ -29,10 +29,10 @@ object Chapter { val source = getHttpSource(mangaDetails.sourceId.toLong()) val chapterList = source.fetchChapterList( - SManga.create().apply { - title = mangaDetails.title - url = mangaDetails.url - } + SManga.create().apply { + title = mangaDetails.title + url = mangaDetails.url + } ).toBlocking().first() val chapterCount = chapterList.count() @@ -72,15 +72,15 @@ object Chapter { chapterList.mapIndexed { index, it -> ChapterDataClass( - ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value, - it.url, - it.name, - it.date_upload, - it.chapter_number, - it.scanlator, - mangaId, - chapterCount - index, - chapterCount + ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value, + it.url, + it.name, + it.date_upload, + it.chapter_number, + it.scanlator, + mangaId, + chapterCount - index, + chapterCount ) } } @@ -95,27 +95,27 @@ object Chapter { val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) val pageList = source.fetchPageList( - SChapter.create().apply { - url = chapterEntry[ChapterTable.url] - name = chapterEntry[ChapterTable.name] - } + SChapter.create().apply { + url = chapterEntry[ChapterTable.url] + name = chapterEntry[ChapterTable.name] + } ).toBlocking().first() val chapterId = chapterEntry[ChapterTable.id].value val chapterCount = transaction { ChapterTable.selectAll().count() } val chapter = ChapterDataClass( - chapterId, - chapterEntry[ChapterTable.url], - chapterEntry[ChapterTable.name], - chapterEntry[ChapterTable.date_upload], - chapterEntry[ChapterTable.chapter_number], - chapterEntry[ChapterTable.scanlator], - mangaId, - chapterEntry[ChapterTable.chapterIndex], - chapterCount.toInt(), + chapterId, + chapterEntry[ChapterTable.url], + chapterEntry[ChapterTable.name], + chapterEntry[ChapterTable.date_upload], + chapterEntry[ChapterTable.chapter_number], + chapterEntry[ChapterTable.scanlator], + mangaId, + chapterEntry[ChapterTable.chapterIndex], + chapterCount.toInt(), - pageList.count() + pageList.count() ) pageList.forEach { page -> @@ -142,4 +142,4 @@ object Chapter { chapter } } -} \ No newline at end of file +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt index a21be70..8b1f003 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt @@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.online.HttpSource 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.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 mu.KotlinLogging import okhttp3.Request @@ -39,206 +39,206 @@ import java.nio.file.Files import java.nio.file.Path object Extension { -private val logger = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} /** - * Convert dex to jar, a wrapper for the dex2jar library - */ -private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) { - // 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 + * Convert dex to jar, a wrapper for the dex2jar library + */ + private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) { + // 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 - val jarFilePath = File(jarFile).toPath() - val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath())) - val handler = BaksmaliBaseDexExceptionHandler() - Dex2jar - .from(reader) - .withExceptionHandler(handler) - .reUseReg(false) - .topoLogicalSort() - .skipDebug(true) - .optimizeSynchronized(false) - .printIR(false) - .noCode(false) - .skipExceptions(false) - .to(jarFilePath) - if (handler.hasException()) { - val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt") - logger.error( - "Detail Error Information in File $errorFile\n" + - "Please report this file to one of following link if possible (any one).\n" + - " https://sourceforge.net/p/dex2jar/tickets/\n" + - " https://bitbucket.org/pxb1988/dex2jar/issues\n" + - " https://github.com/pxb1988/dex2jar/issues\n" + - " dex2jar@googlegroups.com" - ) - handler.dump(errorFile, emptyArray()) + val jarFilePath = File(jarFile).toPath() + val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath())) + val handler = BaksmaliBaseDexExceptionHandler() + Dex2jar + .from(reader) + .withExceptionHandler(handler) + .reUseReg(false) + .topoLogicalSort() + .skipDebug(true) + .optimizeSynchronized(false) + .printIR(false) + .noCode(false) + .skipExceptions(false) + .to(jarFilePath) + if (handler.hasException()) { + val errorFile: Path = File(ApplicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt") + logger.error( + "Detail Error Information in File $errorFile\n" + + "Please report this file to one of following link if possible (any one).\n" + + " https://sourceforge.net/p/dex2jar/tickets/\n" + + " https://bitbucket.org/pxb1988/dex2jar/issues\n" + + " https://github.com/pxb1988/dex2jar/issues\n" + + " dex2jar@googlegroups.com" + ) + handler.dump(errorFile, emptyArray()) + } } -} /** - * 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. - */ -fun loadExtensionInstance(jarPath: String, className: String): Any { - val classLoader = URLClassLoader(arrayOf(URL("file:$jarPath"))) - val classToLoad = Class.forName(className, true, classLoader) - return classToLoad.getDeclaredConstructor().newInstance() -} + * 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. + */ + fun loadExtensionInstance(jarPath: String, className: String): Any { + val classLoader = URLClassLoader(arrayOf(URL("file:$jarPath"))) + val classToLoad = Class.forName(className, true, classLoader) + return classToLoad.getDeclaredConstructor().newInstance() + } -fun installExtension(pkgName: String): Int { - logger.debug("Installing $pkgName") - val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName } - val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk") - val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType" + fun installExtension(pkgName: String): Int { + logger.debug("Installing $pkgName") + val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName } + val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk") + val dirPathWithoutType = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType" - // check if we don't have the dex file already downloaded - val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" - if (!File(jarPath).exists()) { - runBlocking { - val apkToDownload = ExtensionGithubApi.getApkUrl(extensionRecord) + // check if we don't have the dex file already downloaded + val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar" + if (!File(jarPath).exists()) { + runBlocking { + val apkToDownload = ExtensionGithubApi.getApkUrl(extensionRecord) - val apkFilePath = "$dirPathWithoutType.apk" - val jarFilePath = "$dirPathWithoutType.jar" - val dexFilePath = "$dirPathWithoutType.dex" + val apkFilePath = "$dirPathWithoutType.apk" + val jarFilePath = "$dirPathWithoutType.jar" + val dexFilePath = "$dirPathWithoutType.dex" - // download apk file - downloadAPKFile(apkToDownload, apkFilePath) + // download apk file + downloadAPKFile(apkToDownload, apkFilePath) - val className: String = APKExtractor.extractDexAndReadClassname(apkFilePath, dexFilePath) - logger.debug(className) - // dex -> jar - dex2jar(dexFilePath, jarFilePath, fileNameWithoutType) + val className: String = APKExtractor.extractDexAndReadClassname(apkFilePath, dexFilePath) + logger.debug(className) + // dex -> jar + dex2jar(dexFilePath, jarFilePath, fileNameWithoutType) - // clean up - File(apkFilePath).delete() - File(dexFilePath).delete() + // clean up + File(apkFilePath).delete() + File(dexFilePath).delete() - // update sources of the extension - val instance = loadExtensionInstance(jarFilePath, className) + // update sources of the extension + val instance = loadExtensionInstance(jarFilePath, className) - val extensionId = transaction { - 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}") - } + val extensionId = transaction { + ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.firstOrNull()!![ExtensionTable.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) { + + when (instance) { + is HttpSource -> { // single source + transaction { + if (SourceTable.select { SourceTable.id eq instance.id }.count() == 0L) { SourceTable.insert { - it[this.id] = httpSource.id - it[name] = httpSource.name - it[this.lang] = httpSource.lang + it[this.id] = instance.id + it[name] = instance.name + it[this.lang] = instance.lang 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 - transaction { - ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) { - it[isInstalled] = true - it[classFQName] = className + // update extension info + transaction { + ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) { + it[isInstalled] = true + 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) { - val request = Request.Builder().url(url).build() - val response = networkHelper.client.newCall(request).execute() + private fun downloadAPKFile(url: String, apkPath: String) { + val request = Request.Builder().url(url).build() + val response = networkHelper.client.newCall(request).execute() - val downloadedFile = File(apkPath) - val sink = downloadedFile.sink().buffer() - sink.writeAll(response.body!!.source()) - sink.close() -} + val downloadedFile = File(apkPath) + val sink = downloadedFile.sink().buffer() + sink.writeAll(response.body!!.source()) + sink.close() + } -fun uninstallExtension(pkgName: String) { - logger.debug("Uninstalling $pkgName") + fun uninstallExtension(pkgName: String) { + logger.debug("Uninstalling $pkgName") - val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! } - val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") - val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" - transaction { - val extensionId = extensionRecord[ExtensionTable.id].value + val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! } + val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") + val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar" + transaction { + val extensionId = extensionRecord[ExtensionTable.id].value - SourceTable.deleteWhere { SourceTable.extension eq extensionId } - if (extensionRecord[ExtensionTable.isObsolete]) - ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } - else + SourceTable.deleteWhere { SourceTable.extension eq extensionId } + if (extensionRecord[ExtensionTable.isObsolete]) + ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } + 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 }) { - 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()) { - File(jarPath).delete() - } -} + val network: NetworkHelper by injectLazy() -fun updateExtension(pkgName: String): Int { - val targetExtension = ExtensionsList.updateMap.remove(pkgName)!! - uninstallExtension(pkgName) - transaction { - ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { - 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 + fun getExtensionIcon(apkName: String): Pair { + 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() } } - return installExtension(pkgName) -} -val network: NetworkHelper by injectLazy() - -fun getExtensionIcon(apkName: String): Pair { - 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" } } - -fun getExtensionIconUrl(apkName: String): String { - return "/api/v1/extension/icon/$apkName" -} -} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt index 65be114..efb1247 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt @@ -23,75 +23,86 @@ import org.jetbrains.exposed.sql.update import java.util.concurrent.ConcurrentHashMap object ExtensionsList { -private val logger = KotlinLogging.logger {} - + private val logger = KotlinLogging.logger {} var lastUpdateCheck: Long = 0 var updateMap = ConcurrentHashMap() - // const val ExtensionUpdateDelayTime = 60 * 1000 // 60,000 milliseconds = 60 seconds -const val ExtensionUpdateDelayTime = 0 + const val ExtensionUpdateDelayTime = 0 -fun getExtensionList(): List { - // update if {ExtensionUpdateDelayTime} seconds has passed or requested offline and database is empty - if (lastUpdateCheck + ExtensionUpdateDelayTime < System.currentTimeMillis()) { - logger.debug("Getting extensions list from the internet") - lastUpdateCheck = System.currentTimeMillis() - runBlocking { - val foundExtensions = ExtensionGithubApi.findExtensions() - updateExtensionDatabase(foundExtensions) + fun getExtensionList(): List { + // update if {ExtensionUpdateDelayTime} seconds has passed or requested offline and database is empty + if (lastUpdateCheck + ExtensionUpdateDelayTime < System.currentTimeMillis()) { + logger.debug("Getting extensions list from the internet") + lastUpdateCheck = System.currentTimeMillis() + runBlocking { + val foundExtensions = ExtensionGithubApi.findExtensions() + 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( - it[ExtensionTable.name], - it[ExtensionTable.pkgName], - it[ExtensionTable.versionName], - it[ExtensionTable.versionCode], - it[ExtensionTable.lang], - it[ExtensionTable.isNsfw], - it[ExtensionTable.apkName], - getExtensionIconUrl(it[ExtensionTable.apkName]), - it[ExtensionTable.isInstalled], - it[ExtensionTable.hasUpdate], - it[ExtensionTable.isObsolete], - ) + fun extensionTableAsDataClass() = transaction { + ExtensionTable.selectAll().map { + ExtensionDataClass( + it[ExtensionTable.name], + it[ExtensionTable.pkgName], + it[ExtensionTable.versionName], + it[ExtensionTable.versionCode], + it[ExtensionTable.lang], + it[ExtensionTable.isNsfw], + it[ExtensionTable.apkName], + getExtensionIconUrl(it[ExtensionTable.apkName]), + it[ExtensionTable.isInstalled], + it[ExtensionTable.hasUpdate], + it[ExtensionTable.isObsolete], + ) + } } -} -private fun updateExtensionDatabase(foundExtensions: List) { - transaction { - foundExtensions.forEach { foundExtension -> - val extensionRecord = ExtensionTable.select { ExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull() - if (extensionRecord != null) { - if (extensionRecord[ExtensionTable.isInstalled]) { - if (foundExtension.versionCode > extensionRecord[ExtensionTable.versionCode]) { - // there is an update - ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) { - it[hasUpdate] = true - } - updateMap.putIfAbsent(foundExtension.pkgName, foundExtension) - } else if (foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode]) { - // some how the user installed an invalid version - ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) { - it[isObsolete] = true + private fun updateExtensionDatabase(foundExtensions: List) { + transaction { + foundExtensions.forEach { foundExtension -> + val extensionRecord = ExtensionTable.select { ExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull() + if (extensionRecord != null) { + if (extensionRecord[ExtensionTable.isInstalled]) { + if (foundExtension.versionCode > extensionRecord[ExtensionTable.versionCode]) { + // there is an update + ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) { + it[hasUpdate] = true + } + updateMap.putIfAbsent(foundExtension.pkgName, foundExtension) + } else if (foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode]) { + // some how the user installed an invalid version + ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) { + it[isObsolete] = true + } + } else { + // the two are equal + // NOOP } } else { - // the two are equal - // NOOP + // extension is not installed so we can overwrite the data without a care + 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 { - // extension is not installed so we can overwrite the data without a care - ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) { + // 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 @@ -100,37 +111,24 @@ private fun updateExtensionDatabase(foundExtensions: List) 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 - ExtensionTable.selectAll().forEach { extensionRecord -> - val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[ExtensionTable.pkgName] } - if (foundExtension == null) { - // this extensions is obsolete - if (extensionRecord[ExtensionTable.isInstalled]) { - // is installed so we should mark it as obsolete - ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }) { - it[isObsolete] = true + // deal with obsolete extensions + ExtensionTable.selectAll().forEach { extensionRecord -> + val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[ExtensionTable.pkgName] } + if (foundExtension == null) { + // this extensions is obsolete + if (extensionRecord[ExtensionTable.isInstalled]) { + // is installed so we should mark it as obsolete + ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }) { + 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] } } } } } } -} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt index 4dd8b0c..caccf59 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt @@ -22,35 +22,35 @@ object Library { // TODO: `Category.isLanding` is to handle the default categories a new library manga gets, // ..implement that shit at some time... // ..also Consider to rename it to `isDefault` -fun addMangaToLibrary(mangaId: Int) { - val manga = getManga(mangaId) - if (!manga.inLibrary) { - transaction { - MangaTable.update({ MangaTable.id eq manga.id }) { - it[inLibrary] = true + fun addMangaToLibrary(mangaId: Int) { + val manga = getManga(mangaId) + if (!manga.inLibrary) { + transaction { + MangaTable.update({ MangaTable.id eq manga.id }) { + 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 { + 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 { - return transaction { - MangaTable.select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) }.map { - MangaTable.toDataClass(it) - } - } -} -} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt index 289e00c..f67a846 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt @@ -15,99 +15,99 @@ import ir.armor.tachidesk.impl.Source.getSource import ir.armor.tachidesk.model.database.MangaStatus import ir.armor.tachidesk.model.database.MangaTable 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.transactions.transaction import org.jetbrains.exposed.sql.update import java.io.InputStream object Manga { -fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { - var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } + fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { + var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } - return if (mangaEntry[MangaTable.initialized]) { - MangaDataClass( - mangaId, - mangaEntry[MangaTable.sourceReference].toString(), + return if (mangaEntry[MangaTable.initialized]) { + MangaDataClass( + mangaId, + mangaEntry[MangaTable.sourceReference].toString(), - mangaEntry[MangaTable.url], - mangaEntry[MangaTable.title], - if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url], + mangaEntry[MangaTable.url], + mangaEntry[MangaTable.title], + if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url], - true, + true, - mangaEntry[MangaTable.artist], - mangaEntry[MangaTable.author], - mangaEntry[MangaTable.description], - mangaEntry[MangaTable.genre], - MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary], - getSource(mangaEntry[MangaTable.sourceReference]) - ) - } else { // initialize manga - val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) - val fetchedManga = source.fetchMangaDetails( - SManga.create().apply { - url = mangaEntry[MangaTable.url] - title = mangaEntry[MangaTable.title] + mangaEntry[MangaTable.artist], + mangaEntry[MangaTable.author], + mangaEntry[MangaTable.description], + mangaEntry[MangaTable.genre], + MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + mangaEntry[MangaTable.inLibrary], + getSource(mangaEntry[MangaTable.sourceReference]) + ) + } else { // initialize manga + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) + val fetchedManga = source.fetchMangaDetails( + SManga.create().apply { + url = mangaEntry[MangaTable.url] + 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 { - MangaTable.update({ MangaTable.id eq mangaId }) { + mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } + val newThumbnail = mangaEntry[MangaTable.thumbnail_url] - it[MangaTable.initialized] = true + MangaDataClass( + mangaId, + mangaEntry[MangaTable.sourceReference].toString(), - 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 - } + mangaEntry[MangaTable.url], + mangaEntry[MangaTable.title], + if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail, + + true, + + 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()!! } - val newThumbnail = mangaEntry[MangaTable.thumbnail_url] + fun getMangaThumbnail(mangaId: Int): Pair { + val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } + val saveDir = ApplicationDirs.thumbnailsRoot + val fileName = mangaId.toString() - MangaDataClass( - mangaId, - mangaEntry[MangaTable.sourceReference].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!! + } - mangaEntry[MangaTable.url], - mangaEntry[MangaTable.title], - if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail, - - true, - - fetchedManga.artist, - fetchedManga.author, - fetchedManga.description, - fetchedManga.genre, - MangaStatus.valueOf(fetchedManga.status).name, - false, - getSource(mangaEntry[MangaTable.sourceReference]) - ) + source.client.newCall( + GET(thumbnailUrl, source.headers) + ).execute() + } } } - -fun getMangaThumbnail(mangaId: Int): Pair { - 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() - } -} -} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt index 5702473..5ee9a6f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt @@ -18,84 +18,84 @@ import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction object MangaList { -fun proxyThumbnailUrl(mangaId: Int): String { - 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") + fun proxyThumbnailUrl(mangaId: Int): String { + return "/api/v1/manga/$mangaId/thumbnail" } - return mangasPage.processEntries(sourceId) -} -fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { - val mangasPage = this - val mangaList = transaction { - return@transaction mangasPage.mangas.map { manga -> - var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull() - if (mangaEntry == null) { // create manga entry - val mangaId = MangaTable.insertAndGetId { - it[url] = manga.url - it[title] = manga.title + 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) + } - it[artist] = manga.artist - it[author] = manga.author - it[description] = manga.description - it[genre] = manga.genre - it[status] = manga.status - it[thumbnail_url] = manga.thumbnail_url + fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { + val mangasPage = this + val mangaList = transaction { + return@transaction mangasPage.mangas.map { manga -> + var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull() + if (mangaEntry == null) { // create manga entry + val mangaId = MangaTable.insertAndGetId { + it[url] = manga.url + it[title] = manga.title - it[sourceReference] = sourceId - }.value + it[artist] = manga.artist + it[author] = manga.author + it[description] = manga.description + it[genre] = manga.genre + it[status] = manga.status + it[thumbnail_url] = manga.thumbnail_url - MangaDataClass( - mangaId, - sourceId.toString(), + it[sourceReference] = sourceId + }.value - manga.url, - manga.title, - proxyThumbnailUrl(mangaId), + MangaDataClass( + mangaId, + sourceId.toString(), - manga.initialized, + manga.url, + manga.title, + proxyThumbnailUrl(mangaId), - manga.artist, - manga.author, - manga.description, - manga.genre, - MangaStatus.valueOf(manga.status).name - ) - } else { - val mangaId = mangaEntry[MangaTable.id].value - MangaDataClass( - mangaId, - sourceId.toString(), + manga.initialized, - manga.url, - manga.title, - proxyThumbnailUrl(mangaId), + manga.artist, + manga.author, + 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], - mangaEntry[MangaTable.author], - mangaEntry[MangaTable.description], - mangaEntry[MangaTable.genre], - MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary] - ) + true, + + mangaEntry[MangaTable.artist], + mangaEntry[MangaTable.author], + mangaEntry[MangaTable.description], + mangaEntry[MangaTable.genre], + MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + mangaEntry[MangaTable.inLibrary] + ) + } } } + return PagedMangaListDataClass( + mangaList, + mangasPage.hasNextPage + ) } - return PagedMangaListDataClass( - mangaList, - mangasPage.hasNextPage - ) -} } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt index ee4257b..e36125e 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt @@ -14,7 +14,7 @@ import ir.armor.tachidesk.model.database.ChapterTable import ir.armor.tachidesk.model.database.MangaTable import ir.armor.tachidesk.model.database.PageTable 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.select import org.jetbrains.exposed.sql.transactions.transaction @@ -22,72 +22,72 @@ import org.jetbrains.exposed.sql.update import java.io.File import java.io.InputStream -object Page{ +object Page { /** - * 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. - */ -fun getTrueImageUrl(page: Page, source: HttpSource): String { - if (page.imageUrl == null) { - page.imageUrl = source.fetchImageUrl(page).toBlocking().first()!! + * 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. + */ + fun getTrueImageUrl(page: Page, source: HttpSource): String { + if (page.imageUrl == null) { + page.imageUrl = source.fetchImageUrl(page).toBlocking().first()!! + } + return page.imageUrl!! } - return page.imageUrl!! -} -fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int): Pair { - val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } - val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) - val chapterEntry = transaction { - ChapterTable.select { - (ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId) - }.firstOrNull()!! - } - val chapterId = chapterEntry[ChapterTable.id].value + fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int): Pair { + val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) + val chapterEntry = transaction { + ChapterTable.select { + (ChapterTable.chapterIndex eq chapterIndex) and (ChapterTable.manga eq mangaId) + }.firstOrNull()!! + } + 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( - pageEntry[PageTable.index], - pageEntry[PageTable.url], - pageEntry[PageTable.imageUrl] - ) + val tachiPage = Page( + pageEntry[PageTable.index], + pageEntry[PageTable.url], + pageEntry[PageTable.imageUrl] + ) - if (pageEntry[PageTable.imageUrl] == null) { - transaction { - PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq index) }) { - it[imageUrl] = getTrueImageUrl(tachiPage, source) + if (pageEntry[PageTable.imageUrl] == null) { + transaction { + PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq index) }) { + 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 -fun getChapterDir(mangaId: Int, chapterId: Int): String { - val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } - val sourceId = mangaEntry[MangaTable.sourceReference] - val source = getHttpSource(sourceId) - val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! } - val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! } + fun getChapterDir(mangaId: Int, chapterId: Int): String { + val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } + val sourceId = mangaEntry[MangaTable.sourceReference] + val source = getHttpSource(sourceId) + val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! } + val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! } - val chapterDir = when { - chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" - else -> chapterEntry[ChapterTable.name] + val chapterDir = when { + chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${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 -} } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt index df93855..af103f0 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt @@ -13,29 +13,29 @@ import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass object Search { // TODO -fun sourceFilters(sourceId: Long) { - val source = getHttpSource(sourceId) - // source.getFilterList().toItems() -} + fun sourceFilters(sourceId: Long) { + val source = getHttpSource(sourceId) + // source.getFilterList().toItems() + } -fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { - val source = getHttpSource(sourceId) - val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).toBlocking().first() - return searchManga.processEntries(sourceId) -} + fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { + val source = getHttpSource(sourceId) + val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).toBlocking().first() + return searchManga.processEntries(sourceId) + } -fun sourceGlobalSearch(searchTerm: String) { - // TODO -} + fun sourceGlobalSearch(searchTerm: String) { + // TODO + } -data class FilterWrapper( - val type: String, - val filter: Any -) + data class FilterWrapper( + val type: String, + 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 { // return mapNotNull { filter -> // when (filter) { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Source.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Source.kt index ad3f9ce..ba8d3f4 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Source.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Source.kt @@ -14,7 +14,7 @@ import ir.armor.tachidesk.impl.Extension.loadExtensionInstance import ir.armor.tachidesk.model.database.ExtensionTable import ir.armor.tachidesk.model.database.SourceTable import ir.armor.tachidesk.model.dataclass.SourceDataClass -import ir.armor.tachidesk.server.applicationDirs +import ir.armor.tachidesk.server.ApplicationDirs import mu.KotlinLogging import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll @@ -22,67 +22,67 @@ import org.jetbrains.exposed.sql.transactions.transaction import java.util.concurrent.ConcurrentHashMap object Source { -private val logger = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} -private val sourceCache = ConcurrentHashMap() + private val sourceCache = ConcurrentHashMap() -fun getHttpSource(sourceId: Long): HttpSource { - val cachedResult: HttpSource? = sourceCache[sourceId] - if (cachedResult != null) { - logger.debug("used cached HttpSource: ${cachedResult.name}") - return cachedResult + fun getHttpSource(sourceId: Long): HttpSource { + val cachedResult: HttpSource? = sourceCache[sourceId] + if (cachedResult != null) { + logger.debug("used cached HttpSource: ${cachedResult.name}") + 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 { - 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 + fun getSourceList(): List { + return transaction { + SourceTable.selectAll().map { + SourceDataClass( + it[SourceTable.id].value.toString(), + it[SourceTable.name], + it[SourceTable.lang], + getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), + getHttpSource(it[SourceTable.id].value).supportsLatest + ) } } } - return sourceCache[sourceId]!! -} -fun getSourceList(): List { - return transaction { - SourceTable.selectAll().map { + fun getSource(sourceId: Long): SourceDataClass { + return transaction { + val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() + SourceDataClass( - it[SourceTable.id].value.toString(), - it[SourceTable.name], - it[SourceTable.lang], - getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), - getHttpSource(it[SourceTable.id].value).supportsLatest + sourceId.toString(), + source?.get(SourceTable.name), + source?.get(SourceTable.lang), + source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] }, + source?.let { getHttpSource(sourceId).supportsLatest } ) } } } - -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 } - ) - } -} -} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/DBMangaer.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/DBMangaer.kt index 155ca21..ec971dc 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/DBMangaer.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/DBMangaer.kt @@ -14,14 +14,14 @@ import ir.armor.tachidesk.model.database.ExtensionTable import ir.armor.tachidesk.model.database.MangaTable import ir.armor.tachidesk.model.database.PageTable 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.SchemaUtils import org.jetbrains.exposed.sql.transactions.transaction object DBMangaer { val db by lazy { - Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver") + Database.connect("jdbc:h2:${ApplicationDirs.dataRoot}/database", "org.h2.Driver") } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt index e231223..999590c 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt @@ -41,248 +41,250 @@ import java.io.IOException * 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/. */ -private val logger = KotlinLogging.logger {} +object JavalinSetup { + private val logger = KotlinLogging.logger {} -fun javalinSetup() { - var hasWebUiBundled = false + fun javalinSetup() { + var hasWebUiBundled = false - val app = Javalin.create { config -> - try { - Main::class.java.getResource("/react/index.html") - hasWebUiBundled = true - config.addStaticFiles("/react") - config.addSinglePageRoot("/", "/react/index.html") - } catch (e: RuntimeException) { - logger.warn("react build files are missing.") - hasWebUiBundled = false + val app = Javalin.create { config -> + try { + Main::class.java.getResource("/react/index.html") + hasWebUiBundled = true + config.addStaticFiles("/react") + config.addSinglePageRoot("/", "/react/index.html") + } catch (e: RuntimeException) { + logger.warn("react build files are missing.") + 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 -> - logger.error("NullPointerException while handling the request", e) - ctx.status(404) - } + app.exception(NullPointerException::class.java) { e, ctx -> + logger.error("NullPointerException while handling the request", e) + ctx.status(404) + } - app.exception(IOException::class.java) { e, ctx -> - logger.error("IOException while handling the request", e) - ctx.status(500) - ctx.result(e.message ?: "Internal Server Error") - } + app.exception(IOException::class.java) { e, ctx -> + logger.error("IOException while handling the request", e) + ctx.status(500) + ctx.result(e.message ?: "Internal Server Error") + } - app.get("/api/v1/extension/list") { ctx -> - ctx.json(getExtensionList()) - } + app.get("/api/v1/extension/list") { ctx -> + ctx.json(getExtensionList()) + } - app.get("/api/v1/extension/install/:pkgName") { ctx -> - val pkgName = ctx.pathParam("pkgName") + app.get("/api/v1/extension/install/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") - ctx.status( + ctx.status( installExtension(pkgName) - ) - } + ) + } - app.get("/api/v1/extension/update/:pkgName") { ctx -> - val pkgName = ctx.pathParam("pkgName") + app.get("/api/v1/extension/update/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") - ctx.status( + ctx.status( updateExtension(pkgName) - ) - } + ) + } - app.get("/api/v1/extension/uninstall/:pkgName") { ctx -> - val pkgName = ctx.pathParam("pkgName") + app.get("/api/v1/extension/uninstall/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") - uninstallExtension(pkgName) - ctx.status(200) - } + uninstallExtension(pkgName) + ctx.status(200) + } - // icon for extension named `apkName` - app.get("/api/v1/extension/icon/:apkName") { ctx -> - val apkName = ctx.pathParam("apkName") - val result = getExtensionIcon(apkName) + // icon for extension named `apkName` + app.get("/api/v1/extension/icon/:apkName") { ctx -> + val apkName = ctx.pathParam("apkName") + val result = getExtensionIcon(apkName) - ctx.result(result.first) - ctx.header("content-type", result.second) - } + ctx.result(result.first) + ctx.header("content-type", result.second) + } - // list of sources - app.get("/api/v1/source/list") { ctx -> - ctx.json(getSourceList()) - } + // list of sources + app.get("/api/v1/source/list") { ctx -> + ctx.json(getSourceList()) + } - // fetch source with id `sourceId` - app.get("/api/v1/source/:sourceId") { ctx -> - val sourceId = ctx.pathParam("sourceId").toLong() - ctx.json(getSource(sourceId)) - } + // fetch source with id `sourceId` + app.get("/api/v1/source/:sourceId") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + ctx.json(getSource(sourceId)) + } - // popular mangas from source with id `sourceId` - app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx -> - val sourceId = ctx.pathParam("sourceId").toLong() - val pageNum = ctx.pathParam("pageNum").toInt() - ctx.json(getMangaList(sourceId, pageNum, popular = true)) - } + // popular mangas from source with id `sourceId` + app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + val pageNum = ctx.pathParam("pageNum").toInt() + ctx.json(getMangaList(sourceId, pageNum, popular = true)) + } - // latest mangas from source with id `sourceId` - app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx -> - val sourceId = ctx.pathParam("sourceId").toLong() - val pageNum = ctx.pathParam("pageNum").toInt() - ctx.json(getMangaList(sourceId, pageNum, popular = false)) - } + // latest mangas from source with id `sourceId` + app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + val pageNum = ctx.pathParam("pageNum").toInt() + ctx.json(getMangaList(sourceId, pageNum, popular = false)) + } - // get manga info - app.get("/api/v1/manga/:mangaId/") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - ctx.json(getManga(mangaId)) - } + // get manga info + app.get("/api/v1/manga/:mangaId/") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + ctx.json(getManga(mangaId)) + } - // manga thumbnail - app.get("api/v1/manga/:mangaId/thumbnail") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - val result = getMangaThumbnail(mangaId) + // manga thumbnail + app.get("api/v1/manga/:mangaId/thumbnail") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + val result = getMangaThumbnail(mangaId) - ctx.result(result.first) - ctx.header("content-type", result.second) - } + ctx.result(result.first) + ctx.header("content-type", result.second) + } - // adds the manga to library - app.get("api/v1/manga/:mangaId/library") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - addMangaToLibrary(mangaId) - ctx.status(200) - } + // adds the manga to library + app.get("api/v1/manga/:mangaId/library") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + addMangaToLibrary(mangaId) + ctx.status(200) + } - // removes the manga from the library - app.delete("api/v1/manga/:mangaId/library") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - removeMangaFromLibrary(mangaId) - ctx.status(200) - } + // removes the manga from the library + app.delete("api/v1/manga/:mangaId/library") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + removeMangaFromLibrary(mangaId) + ctx.status(200) + } - // list manga's categories - app.get("api/v1/manga/:mangaId/category/") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - ctx.json(getMangaCategories(mangaId)) - } + // list manga's categories + app.get("api/v1/manga/:mangaId/category/") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + ctx.json(getMangaCategories(mangaId)) + } - // adds the manga to category - app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - val categoryId = ctx.pathParam("categoryId").toInt() - addMangaToCategory(mangaId, categoryId) - ctx.status(200) - } + // adds the manga to category + app.get("api/v1/manga/:mangaId/category/:categoryId") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + val categoryId = ctx.pathParam("categoryId").toInt() + addMangaToCategory(mangaId, categoryId) + ctx.status(200) + } - // removes the manga from the category - app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - val categoryId = ctx.pathParam("categoryId").toInt() - removeMangaFromCategory(mangaId, categoryId) - ctx.status(200) - } + // removes the manga from the category + app.delete("api/v1/manga/:mangaId/category/:categoryId") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + val categoryId = ctx.pathParam("categoryId").toInt() + removeMangaFromCategory(mangaId, categoryId) + ctx.status(200) + } - app.get("/api/v1/manga/:mangaId/chapters") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - ctx.json(getChapterList(mangaId)) - } + app.get("/api/v1/manga/:mangaId/chapters") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + ctx.json(getChapterList(mangaId)) + } - app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> - val chapterIndex = ctx.pathParam("chapterIndex").toInt() - val mangaId = ctx.pathParam("mangaId").toInt() - ctx.json(getChapter(chapterIndex, mangaId)) - } + app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> + val chapterIndex = ctx.pathParam("chapterIndex").toInt() + val mangaId = ctx.pathParam("mangaId").toInt() + ctx.json(getChapter(chapterIndex, mangaId)) + } - app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - val chapterIndex = ctx.pathParam("chapterIndex").toInt() - val index = ctx.pathParam("index").toInt() - val result = getPageImage(mangaId, chapterIndex, index) + app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + val chapterIndex = ctx.pathParam("chapterIndex").toInt() + val index = ctx.pathParam("index").toInt() + val result = getPageImage(mangaId, chapterIndex, index) - ctx.result(result.first) - ctx.header("content-type", result.second) - } + ctx.result(result.first) + ctx.header("content-type", result.second) + } - // global search - app.get("/api/v1/search/:searchTerm") { ctx -> - val searchTerm = ctx.pathParam("searchTerm") - ctx.json(sourceGlobalSearch(searchTerm)) - } + // global search + app.get("/api/v1/search/:searchTerm") { ctx -> + val searchTerm = ctx.pathParam("searchTerm") + ctx.json(sourceGlobalSearch(searchTerm)) + } - // single source search - app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx -> - val sourceId = ctx.pathParam("sourceId").toLong() - val searchTerm = ctx.pathParam("searchTerm") - val pageNum = ctx.pathParam("pageNum").toInt() - ctx.json(sourceSearch(sourceId, searchTerm, pageNum)) - } + // single source search + app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + val searchTerm = ctx.pathParam("searchTerm") + val pageNum = ctx.pathParam("pageNum").toInt() + ctx.json(sourceSearch(sourceId, searchTerm, pageNum)) + } - // source filter list - app.get("/api/v1/source/:sourceId/filters/") { ctx -> - val sourceId = ctx.pathParam("sourceId").toLong() - ctx.json(sourceFilters(sourceId)) - } + // source filter list + app.get("/api/v1/source/:sourceId/filters/") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + ctx.json(sourceFilters(sourceId)) + } - // lists mangas that have no category assigned - app.get("/api/v1/library/") { ctx -> - ctx.json(getLibraryMangas()) - } + // lists mangas that have no category assigned + app.get("/api/v1/library/") { ctx -> + ctx.json(getLibraryMangas()) + } - // category list - app.get("/api/v1/category/") { ctx -> - ctx.json(getCategoryList()) - } + // category list + app.get("/api/v1/category/") { ctx -> + ctx.json(getCategoryList()) + } - // category create - app.post("/api/v1/category/") { ctx -> - val name = ctx.formParam("name")!! - createCategory(name) - ctx.status(200) - } + // category create + app.post("/api/v1/category/") { ctx -> + val name = ctx.formParam("name")!! + createCategory(name) + ctx.status(200) + } - // category modification - app.patch("/api/v1/category/:categoryId") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - val name = ctx.formParam("name") - val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null - updateCategory(categoryId, name, isLanding) - ctx.status(200) - } + // category modification + app.patch("/api/v1/category/:categoryId") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + val name = ctx.formParam("name") + val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null + updateCategory(categoryId, name, isLanding) + ctx.status(200) + } - // category re-ordering - app.patch("/api/v1/category/:categoryId/reorder") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - val from = ctx.formParam("from")!!.toInt() - val to = ctx.formParam("to")!!.toInt() - reorderCategory(categoryId, from, to) - ctx.status(200) - } + // category re-ordering + app.patch("/api/v1/category/:categoryId/reorder") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + val from = ctx.formParam("from")!!.toInt() + val to = ctx.formParam("to")!!.toInt() + reorderCategory(categoryId, from, to) + ctx.status(200) + } - // category delete - app.delete("/api/v1/category/:categoryId") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - removeCategory(categoryId) - ctx.status(200) - } + // category delete + app.delete("/api/v1/category/:categoryId") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + removeCategory(categoryId) + ctx.status(200) + } - // returns the manga list associated with a category - app.get("/api/v1/category/:categoryId") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - ctx.json(getCategoryMangaList(categoryId)) - } + // returns the manga list associated with a category + app.get("/api/v1/category/:categoryId") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + ctx.json(getCategoryMangaList(categoryId)) + } - // expects a Tachiyomi legacy backup file to be uploaded - app.get("/api/v1/backup/legacy/import") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - ctx.json(getCategoryMangaList(categoryId)) - } + // expects a Tachiyomi legacy backup file to be uploaded + app.get("/api/v1/backup/legacy/import") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + ctx.json(getCategoryMangaList(categoryId)) + } - // returns a Tachiyomi legacy backup file created from the current database - app.get("/api/v1/backup/legacy/export") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - ctx.json(getCategoryMangaList(categoryId)) + // returns a Tachiyomi legacy backup file created from the current database + app.get("/api/v1/backup/legacy/export") { ctx -> + val categoryId = ctx.pathParam("categoryId").toInt() + ctx.json(getCategoryMangaList(categoryId)) + } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt index 18bcc33..f8b0350 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt @@ -24,7 +24,7 @@ import java.io.File private val logger = KotlinLogging.logger {} -object applicationDirs { +object ApplicationDirs { val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!! val extensionsRoot = "$dataRoot/extensions" val thumbnailsRoot = "$dataRoot/thumbnails" @@ -50,17 +50,17 @@ fun applicationSetup() { // make dirs we need listOf( - applicationDirs.dataRoot, - applicationDirs.extensionsRoot, - "${applicationDirs.extensionsRoot}/icon", - applicationDirs.thumbnailsRoot + ApplicationDirs.dataRoot, + ApplicationDirs.extensionsRoot, + "${ApplicationDirs.extensionsRoot}/icon", + ApplicationDirs.thumbnailsRoot ).forEach { File(it).mkdirs() } // create conf file if doesn't exist try { - val dataConfFile = File("${applicationDirs.dataRoot}/server.conf") + val dataConfFile = File("${ApplicationDirs.dataRoot}/server.conf") if (!dataConfFile.exists()) { Main::class.java.getResourceAsStream("/server-reference.conf").use { input -> dataConfFile.outputStream().use { output -> diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt index 08fc4e8..e8ced2e 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt @@ -20,8 +20,8 @@ import java.io.IOException fun openInBrowser() { try { Desktop.browseURL("http://127.0.0.1:4567") - } catch (e1: IOException) { - e1.printStackTrace() + } catch (e: Exception) { + e.printStackTrace() } }