server configuration fam

This commit is contained in:
Aria Moradi 2021-03-11 14:43:29 +03:30
parent 7e1a4259d7
commit 0ef86c34b7
17 changed files with 134 additions and 145 deletions

View File

@ -1,4 +1,4 @@
dependencies { dependencies {
// Config API // Config API, moved to the global build.gradle
// implementation("com.typesafe:config:1.4.0") // implementation("com.typesafe:config:1.4.0")
} }

View File

@ -4,54 +4,55 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import mu.KotlinLogging import mu.KotlinLogging
import net.harawata.appdirs.AppDirsFactory
import java.io.File import java.io.File
/** /**
* Manages app config. * Manages app config.
*/ */
open class ConfigManager { open class ConfigManager {
private val generatedModules private val dataRoot by lazy { AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!! }
= mutableMapOf<Class<out ConfigModule>, ConfigModule>()
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
val config by lazy { loadConfigs() } val config by lazy { loadConfigs() }
//Public read-only view of modules //Public read-only view of modules
val loadedModules: Map<Class<out ConfigModule>, ConfigModule> val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
get() = generatedModules get() = generatedModules
open val configFolder: String open val appConfigFile: String = "$dataRoot/server.conf"
get() = System.getProperty("compat-configdirs") ?: "tachiserver-data/config"
val logger = KotlinLogging.logger {} val logger = KotlinLogging.logger {}
/** /**
* Get a config module * Get a config module
*/ */
inline fun <reified T : ConfigModule> module(): T inline fun <reified T : ConfigModule> module(): T = loadedModules[T::class.java] as T
= loadedModules[T::class.java] as T
/** /**
* Get a config module (Java API) * Get a config module (Java API)
*/ */
fun <T : ConfigModule> module(type: Class<T>): T fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
= loadedModules[type] as T
/** /**
* Load configs * Load configs
*/ */
fun loadConfigs(): Config { fun loadConfigs(): Config {
val configs = mutableListOf<Config>() //Load reference configs
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
//Load reference config //Load user config
configs += ConfigFactory.parseResources("reference.conf") val userConfig =
File(appConfigFile).let{
ConfigFactory.parseFile(it)
}
//Load custom configs from dir val config = ConfigFactory.empty()
File(configFolder).listFiles()?.map { .withFallback(userConfig)
ConfigFactory.parseFile(it) .withFallback(compatConfig)
}?.filterNotNull()?.forEach { .withFallback(serverConfig)
configs += it.withFallback(configs.last()) .resolve()
}
val config = configs.last().resolve()
logger.debug { logger.debug {
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true)) "Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))

View File

@ -1,35 +0,0 @@
package xyz.nulldev.ts.config
import com.typesafe.config.Config
import java.io.File
class ServerConfig(config: Config) : ConfigModule(config) {
val ip = config.getString("ip")
val port = config.getInt("port")
val allowConfigChanges = config.getBoolean("allowConfigChanges")
val enableWebUi = config.getBoolean("enableWebUi")
val useOldWebUi = config.getBoolean("useOldWebUi")
val prettyPrintApi = config.getBoolean("prettyPrintApi")
// TODO Apply to operation IDs
val disabledApiEndpoints = config.getStringList("disabledApiEndpoints").map(String::toLowerCase)
val enabledApiEndpoints = config.getStringList("enabledApiEndpoints").map(String::toLowerCase)
val httpInitializedPrintMessage = config.getString("httpInitializedPrintMessage")
val useExternalStaticFiles = config.getBoolean("useExternalStaticFiles")
val externalStaticFilesFolder = config.getString("externalStaticFilesFolder")
val rootDir = registerFile(config.getString("rootDir"))
val patchesDir = registerFile(config.getString("patchesDir"))
fun registerFile(file: String): File {
return File(file).apply {
mkdirs()
}
}
companion object {
fun register(config: Config)
= ServerConfig(config.getConfig("ts.server"))
}
}

View File

@ -1,6 +1,3 @@
# Server ip and port bindings
ts.server.ip = 0.0.0.0
ts.server.port = 4567
# Allow/disallow preference changes (useful for demos) # Allow/disallow preference changes (useful for demos)
ts.server.allowConfigChanges = true ts.server.allowConfigChanges = true

View File

@ -76,5 +76,8 @@ configure(listOf(
// dependency of :AndroidCompat:Config // dependency of :AndroidCompat:Config
implementation("com.typesafe:config:1.4.0") implementation("com.typesafe:config:1.4.0")
// to get application content root
implementation("net.harawata:appdirs:1.2.0")
} }
} }

View File

@ -72,9 +72,6 @@ dependencies {
implementation("org.slf4j:slf4j-api:1.8.0-beta4") implementation("org.slf4j:slf4j-api:1.8.0-beta4")
implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3") implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3")
// to get application content root
implementation("net.harawata:appdirs:1.2.0")
// Exposed ORM // Exposed ORM
val exposed_version = "0.28.1" val exposed_version = "0.28.1"
implementation("org.jetbrains.exposed:exposed-core:$exposed_version") implementation("org.jetbrains.exposed:exposed-core:$exposed_version")

View File

@ -1,15 +0,0 @@
package ir.armor.tachidesk
/* 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/. */
import net.harawata.appdirs.AppDirsFactory
object Config {
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
val extensionsRoot = "$dataRoot/extensions"
val thumbnailsRoot = "$dataRoot/thumbnails"
val mangaRoot = "$dataRoot/manga"
val serverPort = 4567
}

View File

@ -4,11 +4,9 @@ package ir.armor.tachidesk
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.App
import io.javalin.Javalin import io.javalin.Javalin
import ir.armor.tachidesk.util.addMangaToCategory import ir.armor.tachidesk.util.addMangaToCategory
import ir.armor.tachidesk.util.addMangaToLibrary import ir.armor.tachidesk.util.addMangaToLibrary
import ir.armor.tachidesk.util.applicationSetup
import ir.armor.tachidesk.util.createCategory import ir.armor.tachidesk.util.createCategory
import ir.armor.tachidesk.util.getCategoryList import ir.armor.tachidesk.util.getCategoryList
import ir.armor.tachidesk.util.getCategoryMangaList import ir.armor.tachidesk.util.getCategoryMangaList
@ -34,44 +32,13 @@ import ir.armor.tachidesk.util.reorderCategory
import ir.armor.tachidesk.util.sourceFilters import ir.armor.tachidesk.util.sourceFilters
import ir.armor.tachidesk.util.sourceGlobalSearch import ir.armor.tachidesk.util.sourceGlobalSearch
import ir.armor.tachidesk.util.sourceSearch import ir.armor.tachidesk.util.sourceSearch
import ir.armor.tachidesk.util.systemTray
import ir.armor.tachidesk.util.updateCategory import ir.armor.tachidesk.util.updateCategory
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.AndroidCompat
import xyz.nulldev.androidcompat.AndroidCompatInitializer
import xyz.nulldev.ts.config.ConfigKodeinModule
import xyz.nulldev.ts.config.GlobalConfigManager
class Main { class Main {
companion object { companion object {
val androidCompat by lazy { AndroidCompat() }
fun registerConfigModules() {
GlobalConfigManager.registerModules(
// ServerConfig.register(GlobalConfigManager.config),
// SyncConfigModule.register(GlobalConfigManager.config)
)
}
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
// System.getProperties()["proxySet"] = "true" serverSetup()
// System.getProperties()["socksProxyHost"] = "127.0.0.1"
// System.getProperties()["socksProxyPort"] = "2020"
// make sure everything we need exists
applicationSetup()
val tray = systemTray() // assign it to a variable so it's kept in the memory and not garbage collected
registerConfigModules()
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
var hasWebUiBundled: Boolean = false var hasWebUiBundled: Boolean = false
@ -86,16 +53,11 @@ class Main {
hasWebUiBundled = false hasWebUiBundled = false
} }
config.enableCorsForAllOrigins() config.enableCorsForAllOrigins()
}.start(4567) }.start(serverConfig.ip, serverConfig.port)
if (hasWebUiBundled) { if (hasWebUiBundled) {
openInBrowser() openInBrowser()
} }
// app.before() { ctx ->
// // allow the client which is running on another port
// ctx.header("Access-Control-Allow-Origin", "*")
// }
app.get("/api/v1/extension/list") { ctx -> app.get("/api/v1/extension/list") { ctx ->
ctx.json(getExtensionList()) ctx.json(getExtensionList())
} }

View File

@ -0,0 +1,25 @@
package ir.armor.tachidesk
import com.typesafe.config.Config
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")
// proxy
val socksProxy = config.getBoolean("socksProxy")
val socksProxyHost = config.getString("socksProxyHost")
val socksProxyPort = config.getString("socksProxyPort")
fun registerFile(file: String): File {
return File(file).apply {
mkdirs()
}
}
companion object {
fun register(config: Config) = ServerConfig(config.getConfig("server"))
}
}

View File

@ -0,0 +1,60 @@
package ir.armor.tachidesk
import eu.kanade.tachiyomi.App
import ir.armor.tachidesk.database.makeDataBaseTables
import ir.armor.tachidesk.util.systemTray
import net.harawata.appdirs.AppDirsFactory
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.AndroidCompat
import xyz.nulldev.androidcompat.AndroidCompatInitializer
import xyz.nulldev.ts.config.ConfigKodeinModule
import xyz.nulldev.ts.config.GlobalConfigManager
import java.io.File
object applicationDirs {
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!!
val extensionsRoot = "$dataRoot/extensions"
val thumbnailsRoot = "$dataRoot/thumbnails"
val mangaRoot = "$dataRoot/manga"
}
val serverConfig: ServerConfig by lazy { GlobalConfigManager.module() }
val systemTray by lazy { systemTray() }
val androidCompat by lazy { AndroidCompat() }
fun serverSetup() {
// register server config
GlobalConfigManager.registerModule(
ServerConfig.register(GlobalConfigManager.config)
)
// make dirs we need
listOf(
applicationDirs.dataRoot,
applicationDirs.extensionsRoot,
"${applicationDirs.extensionsRoot}/icon",
applicationDirs.thumbnailsRoot
).forEach {
File(it).mkdirs()
}
makeDataBaseTables()
// create system tray
systemTray
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
// socks proxy settings
System.getProperties()["proxySet"] = serverConfig.socksProxy.toString()
System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost
System.getProperties()["socksProxyPort"] = serverConfig.socksProxyPort
}

View File

@ -4,7 +4,7 @@ package ir.armor.tachidesk.database
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.Config import ir.armor.tachidesk.applicationDirs
import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.database.table.CategoryMangaTable
import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.database.table.CategoryTable
import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.database.table.ChapterTable
@ -18,7 +18,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
object DBMangaer { object DBMangaer {
val db by lazy { val db by lazy {
Database.connect("jdbc:h2:${Config.dataRoot}/database", "org.h2.Driver") Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
} }
} }

View File

@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.APKExtractor import ir.armor.tachidesk.APKExtractor
import ir.armor.tachidesk.Config import ir.armor.tachidesk.applicationDirs
import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.database.table.ExtensionTable
import ir.armor.tachidesk.database.table.SourceTable import ir.armor.tachidesk.database.table.SourceTable
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -32,10 +32,10 @@ import java.net.URLClassLoader
fun installAPK(apkName: String): Int { fun installAPK(apkName: String): Int {
val extensionRecord = getExtensionList(true).first { it.apkName == apkName } val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
val fileNameWithoutType = apkName.substringBefore(".apk") val fileNameWithoutType = apkName.substringBefore(".apk")
val dirPathWithoutType = "${Config.extensionsRoot}/$fileNameWithoutType" val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
// check if we don't have the dex file already downloaded // check if we don't have the dex file already downloaded
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar" val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
if (!File(jarPath).exists()) { if (!File(jarPath).exists()) {
runBlocking { runBlocking {
val api = ExtensionGithubApi() val api = ExtensionGithubApi()
@ -137,7 +137,7 @@ private fun downloadAPKFile(url: String, apkPath: String) {
fun removeExtension(pkgName: String) { fun removeExtension(pkgName: String) {
val extensionRecord = getExtensionList(true).first { it.apkName == pkgName } val extensionRecord = getExtensionList(true).first { it.apkName == pkgName }
val fileNameWithoutType = pkgName.substringBefore(".apk") val fileNameWithoutType = pkgName.substringBefore(".apk")
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar" val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction { transaction {
val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id] val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id]
@ -157,7 +157,7 @@ val network: NetworkHelper by injectLazy()
fun getExtensionIcon(apkName: String): Pair<InputStream, String> { fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl] val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl]
val saveDir = "${Config.extensionsRoot}/icon" val saveDir = "${applicationDirs.extensionsRoot}/icon"
val fileName = apkName val fileName = apkName
return getCachedResponse(saveDir, fileName) { return getCachedResponse(saveDir, fileName) {

View File

@ -6,7 +6,7 @@ package ir.armor.tachidesk.util
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.Config import ir.armor.tachidesk.applicationDirs
import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.database.dataclass.MangaDataClass
import ir.armor.tachidesk.database.table.MangaStatus import ir.armor.tachidesk.database.table.MangaStatus
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.MangaTable
@ -85,7 +85,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
fun getThumbnail(mangaId: Int): Pair<InputStream, String> { fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val saveDir = Config.thumbnailsRoot val saveDir = applicationDirs.thumbnailsRoot
val fileName = mangaId.toString() val fileName = mangaId.toString()
return getCachedResponse(saveDir, fileName) { return getCachedResponse(saveDir, fileName) {

View File

@ -6,12 +6,11 @@ package ir.armor.tachidesk.util
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.Config import ir.armor.tachidesk.applicationDirs
import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.database.table.ChapterTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.MangaTable
import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.database.table.PageTable
import ir.armor.tachidesk.database.table.SourceTable import ir.armor.tachidesk.database.table.SourceTable
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@ -70,7 +69,7 @@ fun getChapterDir(mangaId: Int, chapterId: Int): String {
val mangaTitle = mangaEntry[MangaTable.title] val mangaTitle = mangaEntry[MangaTable.title]
val sourceName = source.toString() val sourceName = source.toString()
val mangaDir = "${Config.mangaRoot}/$sourceName/$mangaTitle/$chapterDir" val mangaDir = "${applicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
// make sure dirs exist // make sure dirs exist
File(mangaDir).mkdirs() File(mangaDir).mkdirs()
return mangaDir return mangaDir

View File

@ -6,7 +6,7 @@ package ir.armor.tachidesk.util
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.Config import ir.armor.tachidesk.applicationDirs
import ir.armor.tachidesk.database.dataclass.SourceDataClass import ir.armor.tachidesk.database.dataclass.SourceDataClass
import ir.armor.tachidesk.database.entity.ExtensionEntity import ir.armor.tachidesk.database.entity.ExtensionEntity
import ir.armor.tachidesk.database.entity.SourceEntity import ir.armor.tachidesk.database.entity.SourceEntity
@ -36,7 +36,7 @@ fun getHttpSource(sourceId: Long): HttpSource {
val apkName = extensionRecord.apkName val apkName = extensionRecord.apkName
val className = extensionRecord.classFQName val className = extensionRecord.classFQName
val jarName = apkName.substringBefore(".apk") + ".jar" val jarName = apkName.substringBefore(".apk") + ".jar"
val jarPath = "${Config.extensionsRoot}/$jarName" val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
println(jarName) println(jarName)

View File

@ -9,23 +9,10 @@ import dorkbox.systemTray.SystemTray
import dorkbox.systemTray.SystemTray.TrayType import dorkbox.systemTray.SystemTray.TrayType
import dorkbox.util.CacheUtil import dorkbox.util.CacheUtil
import dorkbox.util.Desktop import dorkbox.util.Desktop
import ir.armor.tachidesk.Config
import ir.armor.tachidesk.Main import ir.armor.tachidesk.Main
import ir.armor.tachidesk.database.makeDataBaseTables
import java.awt.event.ActionListener import java.awt.event.ActionListener
import java.io.File
import java.io.IOException import java.io.IOException
fun applicationSetup() {
// make dirs we need
File(Config.dataRoot).mkdirs()
File(Config.extensionsRoot).mkdirs()
File("${Config.extensionsRoot}/icon").mkdirs()
File(Config.thumbnailsRoot).mkdirs()
makeDataBaseTables()
}
fun openInBrowser() { fun openInBrowser() {
try { try {
Desktop.browseURL("http://127.0.0.1:4567") Desktop.browseURL("http://127.0.0.1:4567")
@ -34,8 +21,6 @@ fun openInBrowser() {
} }
} }
val icon = Main::class.java.getResource("/icon/faviconlogo.png")
fun systemTray(): SystemTray? { fun systemTray(): SystemTray? {
try { try {
// ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java // ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java
@ -61,6 +46,8 @@ fun systemTray(): SystemTray? {
) )
) )
val icon = Main::class.java.getResource("/icon/faviconlogo.png")
// systemTray.setTooltip("Tachidesk") // systemTray.setTooltip("Tachidesk")
systemTray.setImage(icon) systemTray.setImage(icon)
// systemTray.status = "No Mail" // systemTray.status = "No Mail"

View File

@ -0,0 +1,8 @@
# Server ip and port bindings
server.ip = 0.0.0.0
server.port = 4567
# Socks5 proxy
server.socksProxy = false
server.socksProxyHost = ""
server.socksProxyPort = ""