diff --git a/build.gradle.kts b/build.gradle.kts index 909a457..6ec1cbf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,7 +61,7 @@ configure(listOf( // Logging implementation("org.slf4j:slf4j-api:1.7.30") - implementation("org.slf4j:slf4j-simple:1.7.30") + implementation("ch.qos.logback:logback-classic:1.2.3") implementation("io.github.microutils:kotlin-logging:2.0.3") // RxJava @@ -76,6 +76,8 @@ configure(listOf( // dependency of :AndroidCompat:Config implementation("com.typesafe:config:1.4.0") + implementation("io.github.config4k:config4k:0.4.2") + // to get application content root implementation("net.harawata:appdirs:1.2.0") diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 6345b58..164917a 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -69,8 +69,6 @@ dependencies { // api implementation("io.javalin:javalin:3.12.0") - implementation("org.slf4j:slf4j-simple:1.8.0-beta4") - implementation("org.slf4j:slf4j-api:1.8.0-beta4") implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3") // Exposed ORM diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 8ef8878..50e3f1f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -7,259 +7,16 @@ 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 io.javalin.Javalin -import ir.armor.tachidesk.util.addMangaToCategory -import ir.armor.tachidesk.util.addMangaToLibrary -import ir.armor.tachidesk.util.createCategory -import ir.armor.tachidesk.util.getCategoryList -import ir.armor.tachidesk.util.getCategoryMangaList -import ir.armor.tachidesk.util.getChapter -import ir.armor.tachidesk.util.getChapterList -import ir.armor.tachidesk.util.getExtensionIcon -import ir.armor.tachidesk.util.getExtensionList -import ir.armor.tachidesk.util.getLibraryMangas -import ir.armor.tachidesk.util.getManga -import ir.armor.tachidesk.util.getMangaCategories -import ir.armor.tachidesk.util.getMangaList -import ir.armor.tachidesk.util.getPageImage -import ir.armor.tachidesk.util.getSource -import ir.armor.tachidesk.util.getSourceList -import ir.armor.tachidesk.util.getThumbnail -import ir.armor.tachidesk.util.installAPK -import ir.armor.tachidesk.util.openInBrowser -import ir.armor.tachidesk.util.removeCategory -import ir.armor.tachidesk.util.removeExtension -import ir.armor.tachidesk.util.removeMangaFromCategory -import ir.armor.tachidesk.util.removeMangaFromLibrary -import ir.armor.tachidesk.util.reorderCategory -import ir.armor.tachidesk.util.sourceFilters -import ir.armor.tachidesk.util.sourceGlobalSearch -import ir.armor.tachidesk.util.sourceSearch -import ir.armor.tachidesk.util.updateCategory -import mu.KLogging +import ir.armor.tachidesk.server.applicationSetup +import ir.armor.tachidesk.server.javalinSetup class Main { - companion object : KLogging() { + companion object { @JvmStatic fun main(args: Array) { - - serverSetup() - - var hasWebUiBundled: Boolean = false - - val app = Javalin.create { config -> - try { - this::class.java.classLoader.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) { - openInBrowser() - } - - app.exception(NullPointerException::class.java) { _, ctx -> - ctx.status(404) - } - - app.get("/api/v1/extension/list") { ctx -> - ctx.json(getExtensionList()) - } - - app.get("/api/v1/extension/install/:apkName") { ctx -> - val apkName = ctx.pathParam("apkName") - - ctx.status( - installAPK(apkName) - ) - } - - app.get("/api/v1/extension/uninstall/:apkName") { ctx -> - val apkName = ctx.pathParam("apkName") - - removeExtension(apkName) - 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) - - ctx.result(result.first) - ctx.header("content-type", result.second) - } - - // 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)) - } - - // 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)) - } - - // 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 = getThumbnail(mangaId) - - 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) - } - - // 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)) - } - - // 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) - } - - 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/:chapterId/page/:index") { ctx -> - val chapterId = ctx.pathParam("chapterId").toInt() - val mangaId = ctx.pathParam("mangaId").toInt() - val index = ctx.pathParam("index").toInt() - val result = getPageImage(mangaId, chapterId, index) - - 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)) - } - - // 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)) - } - - // 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 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 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) - } - - // 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)) - } + applicationSetup() + javalinSetup() } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt index 8873204..0d1c0de 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt @@ -7,7 +7,6 @@ package ir.armor.tachidesk.database * 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.applicationDirs import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.database.table.ChapterTable @@ -15,6 +14,7 @@ import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.database.table.SourceTable +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 diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt index 9f454b6..22316c4 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt @@ -9,7 +9,7 @@ package ir.armor.tachidesk.database.table import eu.kanade.tachiyomi.source.model.SManga import ir.armor.tachidesk.database.dataclass.MangaDataClass -import ir.armor.tachidesk.util.proxyThumbnailUrl +import ir.armor.tachidesk.impl.proxyThumbnailUrl import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/APKExtractor.java b/server/src/main/kotlin/ir/armor/tachidesk/impl/APKExtractor.java similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/APKExtractor.java rename to server/src/main/kotlin/ir/armor/tachidesk/impl/APKExtractor.java index 2f8e7f9..bc50a08 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/APKExtractor.java +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/APKExtractor.java @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util; +package ir.armor.tachidesk.impl; /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt index 0bf0119..62af7df 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt index 440f890..a3fa70f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/CategoryManga.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt index 92d9562..ed929c7 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Chapter.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Chapter.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Extension.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt index eb73758..68cc1b9 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Extension.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project @@ -15,9 +15,9 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.online.HttpSource -import ir.armor.tachidesk.applicationDirs import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.database.table.SourceTable +import ir.armor.tachidesk.server.applicationDirs import kotlinx.coroutines.runBlocking import mu.KotlinLogging import okhttp3.Request diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/ExtensionsList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/ExtensionsList.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt index cb30be6..e1e9d3f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/ExtensionsList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/File.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/File.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/File.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/File.kt index c48de68..af7244d 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/File.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/File.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt similarity index 94% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt index 70d5924..28307ec 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project @@ -11,7 +11,6 @@ import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.toDataClass -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.select diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt index 51ebfc2..3b700b3 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project @@ -9,10 +9,10 @@ package ir.armor.tachidesk.util import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SManga -import ir.armor.tachidesk.applicationDirs import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.database.table.MangaStatus import ir.armor.tachidesk.database.table.MangaTable +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 diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaDexHelper.kt similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/MangaDexHelper.kt index 687a408..700bce7 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaDexHelper.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt similarity index 99% rename from server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt index e0b4c3d..e60085d 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/MangaList.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt similarity index 97% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt index 3f2312e..2784e37 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Page.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project @@ -9,11 +9,11 @@ package ir.armor.tachidesk.util import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource -import ir.armor.tachidesk.applicationDirs import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.database.table.SourceTable +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 diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Search.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Search.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt index 6670b61..6dd8984 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Search.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/SourceList.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/SourceList.kt index e78ee49..de332b8 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/SourceList.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.impl /* * Copyright (C) Contributors to the Suwayomi project @@ -9,12 +9,12 @@ package ir.armor.tachidesk.util import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.online.HttpSource -import ir.armor.tachidesk.applicationDirs import ir.armor.tachidesk.database.dataclass.SourceDataClass import ir.armor.tachidesk.database.entity.ExtensionEntity import ir.armor.tachidesk.database.entity.SourceEntity import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.database.table.SourceTable +import ir.armor.tachidesk.server.applicationDirs import mu.KotlinLogging import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt new file mode 100644 index 0000000..bef3367 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt @@ -0,0 +1,259 @@ +package ir.armor.tachidesk.server + +import io.javalin.Javalin +import ir.armor.tachidesk.Main +import ir.armor.tachidesk.impl.addMangaToCategory +import ir.armor.tachidesk.impl.addMangaToLibrary +import ir.armor.tachidesk.impl.createCategory +import ir.armor.tachidesk.impl.getCategoryList +import ir.armor.tachidesk.impl.getCategoryMangaList +import ir.armor.tachidesk.impl.getChapter +import ir.armor.tachidesk.impl.getChapterList +import ir.armor.tachidesk.impl.getExtensionIcon +import ir.armor.tachidesk.impl.getExtensionList +import ir.armor.tachidesk.impl.getLibraryMangas +import ir.armor.tachidesk.impl.getManga +import ir.armor.tachidesk.impl.getMangaCategories +import ir.armor.tachidesk.impl.getMangaList +import ir.armor.tachidesk.impl.getPageImage +import ir.armor.tachidesk.impl.getSource +import ir.armor.tachidesk.impl.getSourceList +import ir.armor.tachidesk.impl.getThumbnail +import ir.armor.tachidesk.impl.installAPK +import ir.armor.tachidesk.impl.removeCategory +import ir.armor.tachidesk.impl.removeExtension +import ir.armor.tachidesk.impl.removeMangaFromCategory +import ir.armor.tachidesk.impl.removeMangaFromLibrary +import ir.armor.tachidesk.impl.reorderCategory +import ir.armor.tachidesk.impl.sourceFilters +import ir.armor.tachidesk.impl.sourceGlobalSearch +import ir.armor.tachidesk.impl.sourceSearch +import ir.armor.tachidesk.impl.updateCategory +import ir.armor.tachidesk.server.util.openInBrowser +import mu.KotlinLogging + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 {} + +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 + } + config.enableCorsForAllOrigins() + }.start(serverConfig.ip, serverConfig.port) + if (hasWebUiBundled && serverConfig.initialOpenInBrowserEnabled) { + openInBrowser() + } + + app.exception(NullPointerException::class.java) { _, ctx -> + ctx.status(404) + } + + app.get("/api/v1/extension/list") { ctx -> + ctx.json(getExtensionList()) + } + + app.get("/api/v1/extension/install/:apkName") { ctx -> + val apkName = ctx.pathParam("apkName") + + ctx.status( + installAPK(apkName) + ) + } + + app.get("/api/v1/extension/uninstall/:apkName") { ctx -> + val apkName = ctx.pathParam("apkName") + + removeExtension(apkName) + 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) + + ctx.result(result.first) + ctx.header("content-type", result.second) + } + + // 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)) + } + + // 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)) + } + + // 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 = getThumbnail(mangaId) + + 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) + } + + // 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)) + } + + // 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) + } + + 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/:chapterId/page/:index") { ctx -> + val chapterId = ctx.pathParam("chapterId").toInt() + val mangaId = ctx.pathParam("mangaId").toInt() + val index = ctx.pathParam("index").toInt() + val result = getPageImage(mangaId, chapterId, index) + + 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)) + } + + // 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)) + } + + // 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 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 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) + } + + // 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)) + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/ServerConfig.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerConfig.kt similarity index 56% rename from server/src/main/kotlin/ir/armor/tachidesk/ServerConfig.kt rename to server/src/main/kotlin/ir/armor/tachidesk/server/ServerConfig.kt index 96d34a6..52bb9b1 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/ServerConfig.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerConfig.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk +package ir.armor.tachidesk.server /* * Copyright (C) Contributors to the Suwayomi project @@ -8,23 +8,22 @@ package ir.armor.tachidesk * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import com.typesafe.config.Config +import io.github.config4k.getValue import xyz.nulldev.ts.config.ConfigModule -import java.io.File class ServerConfig(config: Config) : ConfigModule(config) { - val ip = config.getString("ip") - val port = config.getInt("port") + val ip: String by config + val port: Int by config // proxy - val socksProxy = config.getBoolean("socksProxy") - val socksProxyHost = config.getString("socksProxyHost") - val socksProxyPort = config.getString("socksProxyPort") + val socksProxy: Boolean by config + val socksProxyHost: String by config + val socksProxyPort: String by config - fun registerFile(file: String): File { - return File(file).apply { - mkdirs() - } - } + // misc + val debugLogsEnabled: Boolean by config + val systemTrayEnabled: Boolean by config + val initialOpenInBrowserEnabled: Boolean by config companion object { fun register(config: Config) = ServerConfig(config.getConfig("server")) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/ServerSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt similarity index 62% rename from server/src/main/kotlin/ir/armor/tachidesk/ServerSetup.kt rename to server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt index a54dafb..b9cfd71 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/ServerSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/ServerSetup.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk +package ir.armor.tachidesk.server /* * Copyright (C) Contributors to the Suwayomi project @@ -7,9 +7,11 @@ 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 ch.qos.logback.classic.Level import eu.kanade.tachiyomi.App +import ir.armor.tachidesk.Main import ir.armor.tachidesk.database.makeDataBaseTables -import ir.armor.tachidesk.util.systemTray +import ir.armor.tachidesk.server.util.systemTray import net.harawata.appdirs.AppDirsFactory import org.kodein.di.DI import org.kodein.di.conf.global @@ -32,7 +34,7 @@ val systemTray by lazy { systemTray() } val androidCompat by lazy { AndroidCompat() } -fun serverSetup() { +fun applicationSetup() { // register server config GlobalConfigManager.registerModule( ServerConfig.register(GlobalConfigManager.config) @@ -48,14 +50,31 @@ fun serverSetup() { File(it).mkdirs() } + // create conf file if doesn't exist + try { + val dataConfFile = File("${applicationDirs.dataRoot}/server.conf") + if (!dataConfFile.exists()) { + val inpStream = File( + Main::class.java.getResource("/server-reference.conf").toURI() + ).inputStream() + val outStream = dataConfFile.outputStream() + + inpStream.copyTo(outStream) + + inpStream.close() + outStream.close() + } + } catch (e: Exception) {} + makeDataBaseTables() // create system tray - try { - systemTray - } catch (e: Exception) { - e.printStackTrace() - } + if (serverConfig.systemTrayEnabled) + try { + systemTray + } catch (e: Exception) { + e.printStackTrace() + } // Load config API DI.global.addImport(ConfigKodeinModule().create()) @@ -64,6 +83,14 @@ fun serverSetup() { // start app androidCompat.startApp(App()) + // set application wide logging level + if (!serverConfig.debugLogsEnabled) + (mu.KotlinLogging.logger("ir.armor.tachidesk").underlyingLogger as ch.qos.logback.classic.Logger).level = Level.INFO + + // Disable jetty's logging + System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog") + System.setProperty("org.eclipse.jetty.LEVEL", "OFF") + // socks proxy settings System.getProperties()["proxySet"] = serverConfig.socksProxy.toString() System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Util.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt similarity index 94% rename from server/src/main/kotlin/ir/armor/tachidesk/util/Util.kt rename to server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt index ad7acae..f053885 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Util.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/util/SystemTray.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.util +package ir.armor.tachidesk.server.util /* * Copyright (C) Contributors to the Suwayomi project @@ -27,7 +27,7 @@ fun openInBrowser() { fun systemTray(): SystemTray? { try { // ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java - SystemTray.DEBUG = true; // for test apps, we always want to run in debug mode + SystemTray.DEBUG = false if (System.getProperty("os.name").startsWith("Windows")) SystemTray.FORCE_TRAY_TYPE = TrayType.Swing diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml new file mode 100644 index 0000000..7e9d0aa --- /dev/null +++ b/server/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/server/src/main/resources/server-reference.conf b/server/src/main/resources/server-reference.conf index b7761fa..14f6f63 100644 --- a/server/src/main/resources/server-reference.conf +++ b/server/src/main/resources/server-reference.conf @@ -1,8 +1,13 @@ # Server ip and port bindings -server.ip = 0.0.0.0 +server.ip = "0.0.0.0" server.port = 4567 # Socks5 proxy server.socksProxy = false server.socksProxyHost = "" -server.socksProxyPort = "" \ No newline at end of file +server.socksProxyPort = "" + +# misc +server.debugLogsEnabled = false +server.systemTrayEnabled = true +server.initialOpenInBrowserEnabled = true