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

View File

@ -76,5 +76,8 @@ configure(listOf(
// dependency of :AndroidCompat:Config
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("com.fasterxml.jackson.core:jackson-databind:2.10.3")
// to get application content root
implementation("net.harawata:appdirs:1.2.0")
// Exposed ORM
val exposed_version = "0.28.1"
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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.App
import io.javalin.Javalin
import ir.armor.tachidesk.util.addMangaToCategory
import ir.armor.tachidesk.util.addMangaToLibrary
import ir.armor.tachidesk.util.applicationSetup
import ir.armor.tachidesk.util.createCategory
import ir.armor.tachidesk.util.getCategoryList
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.sourceGlobalSearch
import ir.armor.tachidesk.util.sourceSearch
import ir.armor.tachidesk.util.systemTray
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 {
companion object {
val androidCompat by lazy { AndroidCompat() }
fun registerConfigModules() {
GlobalConfigManager.registerModules(
// ServerConfig.register(GlobalConfigManager.config),
// SyncConfigModule.register(GlobalConfigManager.config)
)
}
@JvmStatic
fun main(args: Array<String>) {
// System.getProperties()["proxySet"] = "true"
// 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())
serverSetup()
var hasWebUiBundled: Boolean = false
@ -86,16 +53,11 @@ class Main {
hasWebUiBundled = false
}
config.enableCorsForAllOrigins()
}.start(4567)
}.start(serverConfig.ip, serverConfig.port)
if (hasWebUiBundled) {
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 ->
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
* 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.CategoryTable
import ir.armor.tachidesk.database.table.ChapterTable
@ -18,7 +18,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
object DBMangaer {
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.online.HttpSource
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.SourceTable
import kotlinx.coroutines.runBlocking
@ -32,10 +32,10 @@ import java.net.URLClassLoader
fun installAPK(apkName: String): Int {
val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
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
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
if (!File(jarPath).exists()) {
runBlocking {
val api = ExtensionGithubApi()
@ -137,7 +137,7 @@ private fun downloadAPKFile(url: String, apkPath: String) {
fun removeExtension(pkgName: String) {
val extensionRecord = getExtensionList(true).first { it.apkName == pkgName }
val fileNameWithoutType = pkgName.substringBefore(".apk")
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction {
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> {
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
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.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.table.MangaStatus
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> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val saveDir = Config.thumbnailsRoot
val saveDir = applicationDirs.thumbnailsRoot
val fileName = mangaId.toString()
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.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.MangaTable
import ir.armor.tachidesk.database.table.PageTable
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.select
import org.jetbrains.exposed.sql.transactions.transaction
@ -70,7 +69,7 @@ fun getChapterDir(mangaId: Int, chapterId: Int): String {
val mangaTitle = mangaEntry[MangaTable.title]
val sourceName = source.toString()
val mangaDir = "${Config.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
val mangaDir = "${applicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
// make sure dirs exist
File(mangaDir).mkdirs()
return mangaDir

View File

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

View File

@ -9,23 +9,10 @@ import dorkbox.systemTray.SystemTray
import dorkbox.systemTray.SystemTray.TrayType
import dorkbox.util.CacheUtil
import dorkbox.util.Desktop
import ir.armor.tachidesk.Config
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.database.makeDataBaseTables
import java.awt.event.ActionListener
import java.io.File
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() {
try {
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? {
try {
// 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.setImage(icon)
// 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 = ""