1
0
mirror of https://gitlab.com/mangadex-pub/mangadex_at_home.git synced 2024-11-16 16:12:32 +01:00

Minor changes + new CLI

This commit is contained in:
carbotaniuman 2020-08-11 14:12:01 -05:00
parent 398ab05788
commit 9d07c18de1
12 changed files with 154 additions and 67 deletions

View File

@ -6,15 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- [2020-08-11] New CLI for specifying database location, cache folder, and settings [@carbotaniuman].
### Changed ### Changed
-- [2020-08-11] Change logging defaults [@carbotaniuman]. - [2020-08-11] Change logging defaults [@carbotaniuman].
### Deprecated ### Deprecated
### Removed ### Removed
### Fixed ### Fixed
- [2020-08-11] Bugs relating to `settings.json` changes [@carbotaniuman].
- [2020-08-11] Logs taking up an absurd amount of space [@carbotaniuman].
- [2020-08-11] Random crashes for no reason [@carbotaniuman].
- [2020-08-11] SQLException is noww properly handled [@carbotaniuman].
### Security ### Security

View File

@ -1,6 +1,7 @@
plugins { plugins {
id "java" id "java"
id "org.jetbrains.kotlin.jvm" version "1.3.72" id "org.jetbrains.kotlin.jvm" version "1.3.72"
id "org.jetbrains.kotlin.kapt" version "1.3.72"
id "application" id "application"
id "com.github.johnrengelman.shadow" version "5.2.0" id "com.github.johnrengelman.shadow" version "5.2.0"
id "com.diffplug.gradle.spotless" version "4.4.0" id "com.diffplug.gradle.spotless" version "4.4.0"
@ -45,6 +46,15 @@ dependencies {
implementation "com.goterl.lazycode:lazysodium-java:4.3.0" implementation "com.goterl.lazycode:lazysodium-java:4.3.0"
implementation "net.java.dev.jna:jna:5.5.0" implementation "net.java.dev.jna:jna:5.5.0"
implementation "info.picocli:picocli:4.5.0"
kapt "info.picocli:picocli-codegen:4.5.0"
}
kapt {
arguments {
arg("project", "${project.group}/${project.name}")
}
} }
java { java {

View File

@ -19,15 +19,48 @@ along with this MangaDex@Home. If not, see <http://www.gnu.org/licenses/>.
package mdnet.base package mdnet.base
import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.LoggerContext
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
import mdnet.BuildInfo import mdnet.BuildInfo
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import picocli.CommandLine
object Main { object Main {
private val LOGGER = LoggerFactory.getLogger(Main::class.java) private val LOGGER = LoggerFactory.getLogger(Main::class.java)
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
CommandLine(ClientArgs()).execute(*args)
}
fun dieWithError(e: Throwable): Nothing {
LOGGER.error(e) { "Critical Error" }
(LoggerFactory.getILoggerFactory() as LoggerContext).stop()
exitProcess(1)
}
fun dieWithError(error: String): Nothing {
LOGGER.error { "Critical Error: $error" }
(LoggerFactory.getILoggerFactory() as LoggerContext).stop()
exitProcess(1)
}
}
@CommandLine.Command(name = "java -jar <jar>", usageHelpWidth = 120, version = ["Client Version ${BuildInfo.VERSION} (Build ${Constants.CLIENT_BUILD})"])
data class ClientArgs(
@field:CommandLine.Option(names = ["-s", "--settings"], defaultValue = "settings.json", paramLabel = "<settings>", description = ["the settings file (default: \${DEFAULT-VALUE})"])
var settingsFile: File = File("settings.json"),
@field:CommandLine.Option(names = ["-d", "--database"], defaultValue = "cache\${sys:file.separator}data.db", paramLabel = "<settings>", description = ["the database file (default: \${DEFAULT-VALUE})"])
var databaseFile: File = File("cache${File.separator}data.db"),
@field:CommandLine.Option(names = ["-c", "--cache"], defaultValue = "cache", paramLabel = "<settings>", description = ["the cache folder (default: \${DEFAULT-VALUE})"])
var cacheFolder: File = File("cache"),
@field:CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["show this help message and exit"])
var helpRequested: Boolean = false,
@field:CommandLine.Option(names = ["-v", "--version"], versionHelp = true, description = ["show the version message and exit"])
var versionRequested: Boolean = false
) : Runnable {
override fun run() {
println( println(
"Mangadex@Home Client Version ${BuildInfo.VERSION} (Build ${Constants.CLIENT_BUILD}) initializing" "Mangadex@Home Client Version ${BuildInfo.VERSION} (Build ${Constants.CLIENT_BUILD}) initializing"
) )
@ -48,31 +81,11 @@ object Main {
along with Mangadex@Home. If not, see <https://www.gnu.org/licenses/>. along with Mangadex@Home. If not, see <https://www.gnu.org/licenses/>.
""".trimIndent()) """.trimIndent())
var file = "settings.json" val client = MangaDexClient(settingsFile, databaseFile, cacheFolder)
if (args.size == 1) {
file = args[0]
} else if (args.isNotEmpty()) {
dieWithError("Expected one argument: path to config file, or nothing")
}
val client = MangaDexClient(file)
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(Thread {
client.shutdown() client.shutdown()
(LoggerFactory.getILoggerFactory() as LoggerContext).stop() (LoggerFactory.getILoggerFactory() as LoggerContext).stop()
}) })
client.runLoop() client.runLoop()
} }
fun dieWithError(e: Throwable): Nothing {
LOGGER.error(e) { "Critical Error" }
(LoggerFactory.getILoggerFactory() as LoggerContext).stop()
exitProcess(1)
}
fun dieWithError(error: String): Nothing {
LOGGER.error { "Critical Error: $error" }
(LoggerFactory.getILoggerFactory() as LoggerContext).stop()
exitProcess(1)
}
} }

View File

@ -38,14 +38,16 @@ import mdnet.base.settings.*
import mdnet.cache.DiskLruCache import mdnet.cache.DiskLruCache
import mdnet.cache.HeaderMismatchException import mdnet.cache.HeaderMismatchException
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
import org.jetbrains.exposed.sql.Database
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
// Exception class to handle when Client Settings have invalid values // Exception class to handle when Client Settings have invalid values
class ClientSettingsException(message: String) : Exception(message) class ClientSettingsException(message: String) : Exception(message)
class MangaDexClient(private val clientSettingsFile: String) { class MangaDexClient(private val settingsFile: File, databaseFile: File, cacheFolder: File) {
// just for scheduling one task, so single-threaded // just for scheduling one task, so single-threaded
private val executor = Executors.newSingleThreadScheduledExecutor() private val executor = Executors.newSingleThreadScheduledExecutor()
private val database: Database
private val cache: DiskLruCache private val cache: DiskLruCache
private var settings: ClientSettings private var settings: ClientSettings
@ -65,9 +67,13 @@ class MangaDexClient(private val clientSettingsFile: String) {
dieWithError(e) dieWithError(e)
} }
LOGGER.info { "Client settings loaded: $settings" }
database = Database.connect("jdbc:sqlite:$databaseFile", "org.sqlite.JDBC")
try { try {
cache = DiskLruCache.open( cache = DiskLruCache.open(
File("cache"), 1, 1, cacheFolder, 1, 1,
(settings.maxCacheSizeInMebibytes * 1024 * 1024 * 0.8).toLong() /* MiB to bytes */ (settings.maxCacheSizeInMebibytes * 1024 * 1024 * 0.8).toLong() /* MiB to bytes */
) )
} catch (e: HeaderMismatchException) { } catch (e: HeaderMismatchException) {
@ -88,6 +94,9 @@ class MangaDexClient(private val clientSettingsFile: String) {
LOGGER.warn(e) { "Reload of ClientSettings failed" } LOGGER.warn(e) { "Reload of ClientSettings failed" }
} }
}, 1, 1, TimeUnit.MINUTES) }, 1, 1, TimeUnit.MINUTES)
startImageServer()
startWebUi()
} }
// Precondition: settings must be filled with up-to-date settings and `imageServer` must not be null // Precondition: settings must be filled with up-to-date settings and `imageServer` must not be null
@ -96,37 +105,53 @@ class MangaDexClient(private val clientSettingsFile: String) {
val imageServer = requireNotNull(imageServer) val imageServer = requireNotNull(imageServer)
if (webUi != null) throw AssertionError() if (webUi != null) throw AssertionError()
LOGGER.info { "WebUI starting" }
webUi = getUiServer(webSettings, imageServer.statistics, imageServer.statsMap).also { webUi = getUiServer(webSettings, imageServer.statistics, imageServer.statsMap).also {
it.start() it.start()
} }
LOGGER.info { "WebUI was successfully started" } LOGGER.info { "WebUI started" }
} }
} }
// Precondition: settings must be filled with up-to-date settings // Precondition: settings must be filled with up-to-date settings
private fun startImageServer() { private fun startImageServer() {
if (imageServer != null) throw AssertionError() if (imageServer != null) throw AssertionError()
imageServer = ServerManager(settings.serverSettings, settings.devSettings, settings.maxCacheSizeInMebibytes, cache).also { LOGGER.info { "Server manager starting" }
imageServer = ServerManager(settings.serverSettings, settings.devSettings, settings.maxCacheSizeInMebibytes, cache, database).also {
it.start() it.start()
} }
LOGGER.info { "Server manager was successfully started" } LOGGER.info { "Server manager started" }
} }
private fun stopImageServer() { private fun stopImageServer() {
LOGGER.info { "Server manager stopping" }
requireNotNull(imageServer).shutdown() requireNotNull(imageServer).shutdown()
LOGGER.info { "Server manager was successfully stopped" } imageServer = null
LOGGER.info { "Server manager stopped" }
} }
private fun stopWebUi() { private fun stopWebUi() {
LOGGER.info { "WebUI stopping" }
requireNotNull(webUi).stop() requireNotNull(webUi).stop()
LOGGER.info { "Server manager was successfully stopped" } webUi = null
LOGGER.info { "WebUI stopped" }
} }
fun shutdown() { fun shutdown() {
LOGGER.info { "Mangadex@Home Client shutting down" } LOGGER.info { "Mangadex@Home Client shutting down" }
stopWebUi() if (webUi != null) {
stopImageServer() stopWebUi()
}
if (imageServer != null) {
stopImageServer()
}
LOGGER.info { "Mangadex@Home Client has shut down" } LOGGER.info { "Mangadex@Home Client has shut down" }
try {
cache.close()
} catch (e: IOException) {
LOGGER.error(e) { "Cache failed to close" }
}
} }
/** /**
@ -142,6 +167,7 @@ class MangaDexClient(private val clientSettingsFile: String) {
LOGGER.info { "Client settings unchanged" } LOGGER.info { "Client settings unchanged" }
return return
} }
LOGGER.info { "New settings loaded: $newSettings" }
cache.maxSize = (newSettings.maxCacheSizeInMebibytes * 1024 * 1024 * 0.8).toLong() cache.maxSize = (newSettings.maxCacheSizeInMebibytes * 1024 * 1024 * 0.8).toLong()
@ -215,7 +241,7 @@ class MangaDexClient(private val clientSettingsFile: String) {
} }
private fun readClientSettings(): ClientSettings { private fun readClientSettings(): ClientSettings {
return JACKSON.readValue<ClientSettings>(FileReader(clientSettingsFile)).apply(::validateSettings) return JACKSON.readValue<ClientSettings>(FileReader(settingsFile)).apply(::validateSettings)
} }
companion object { companion object {

View File

@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import java.io.IOException
import java.time.Instant import java.time.Instant
import java.util.Collections import java.util.Collections
import java.util.LinkedHashMap import java.util.LinkedHashMap
@ -21,6 +20,7 @@ import mdnet.base.settings.RemoteSettings
import mdnet.base.settings.ServerSettings import mdnet.base.settings.ServerSettings
import mdnet.cache.DiskLruCache import mdnet.cache.DiskLruCache
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
import org.jetbrains.exposed.sql.Database
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
sealed class State sealed class State
@ -33,7 +33,7 @@ data class GracefulStop(val lastRunning: Running, val counts: Int = 0, val nextS
// server is currently running // server is currently running
data class Running(val server: Http4kServer, val settings: RemoteSettings, val serverSettings: ServerSettings, val devSettings: DevSettings) : State() data class Running(val server: Http4kServer, val settings: RemoteSettings, val serverSettings: ServerSettings, val devSettings: DevSettings) : State()
class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, maxCacheSizeInMebibytes: Long, private val cache: DiskLruCache) { class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, maxCacheSizeInMebibytes: Long, private val cache: DiskLruCache, private val database: Database) {
// this must remain single-threaded because of how the state mechanism works // this must remain single-threaded because of how the state mechanism works
private val executor = Executors.newSingleThreadScheduledExecutor() private val executor = Executors.newSingleThreadScheduledExecutor()
@ -68,6 +68,7 @@ class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, ma
} }
fun start() { fun start() {
LOGGER.info { "Image server starting" }
loginAndStartServer() loginAndStartServer()
statsMap[Instant.now()] = statistics.get() statsMap[Instant.now()] = statistics.get()
@ -165,6 +166,8 @@ class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, ma
LOGGER.warn(e) { "Graceful shutdown checker failed" } LOGGER.warn(e) { "Graceful shutdown checker failed" }
} }
}, 45, 45, TimeUnit.SECONDS) }, 45, 45, TimeUnit.SECONDS)
LOGGER.info { "Image server has started" }
} }
private fun pingControl() { private fun pingControl() {
@ -197,7 +200,7 @@ class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, ma
val remoteSettings = serverHandler.loginToControl() val remoteSettings = serverHandler.loginToControl()
?: Main.dieWithError("Failed to get a login response from server - check API secret for validity") ?: Main.dieWithError("Failed to get a login response from server - check API secret for validity")
val server = getServer(cache, remoteSettings, state.serverSettings, statistics, isHandled).start() val server = getServer(cache, database, remoteSettings, state.serverSettings, statistics, isHandled).start()
if (remoteSettings.latestBuild > Constants.CLIENT_BUILD) { if (remoteSettings.latestBuild > Constants.CLIENT_BUILD) {
LOGGER.warn { LOGGER.warn {
@ -253,12 +256,6 @@ class ServerManager(serverSettings: ServerSettings, devSettings: DevSettings, ma
}, 0, TimeUnit.SECONDS) }, 0, TimeUnit.SECONDS)
latch.await() latch.await()
try {
cache.close()
} catch (e: IOException) {
LOGGER.error(e) { "Cache failed to close" }
}
executor.shutdown() executor.shutdown()
LOGGER.info { "Image server has shut down" } LOGGER.info { "Image server has shut down" }
} }

View File

@ -57,9 +57,9 @@ import org.http4k.server.Http4kServer
import org.http4k.server.ServerConfig import org.http4k.server.ServerConfig
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
private val LOGGER = LoggerFactory.getLogger("Application") private val LOGGER = LoggerFactory.getLogger("AppNetty")
class Netty(private val tls: TlsCert, internal val serverSettings: ServerSettings, private val statistics: AtomicReference<Statistics>) : ServerConfig { class Netty(private val tls: TlsCert, private val serverSettings: ServerSettings, private val statistics: AtomicReference<Statistics>) : ServerConfig {
override fun toServer(httpHandler: HttpHandler): Http4kServer = object : Http4kServer { override fun toServer(httpHandler: HttpHandler): Http4kServer = object : Http4kServer {
private val masterGroup = NioEventLoopGroup(serverSettings.threads) private val masterGroup = NioEventLoopGroup(serverSettings.threads)
private val workerGroup = NioEventLoopGroup(serverSettings.threads) private val workerGroup = NioEventLoopGroup(serverSettings.threads)

View File

@ -32,7 +32,7 @@ private const val PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----"
private const val PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----" private const val PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----"
private const val PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----" private const val PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----"
internal fun loadKey(keyDataString: String): PrivateKey? { fun loadKey(keyDataString: String): PrivateKey? {
if (keyDataString.contains(PKCS_1_PEM_HEADER)) { if (keyDataString.contains(PKCS_1_PEM_HEADER)) {
val fixedString = keyDataString.replace(PKCS_1_PEM_HEADER, "").replace( val fixedString = keyDataString.replace(PKCS_1_PEM_HEADER, "").replace(
PKCS_1_PEM_FOOTER, "") PKCS_1_PEM_FOOTER, "")

View File

@ -32,7 +32,6 @@ import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.HttpServerKeepAliveHandler import io.netty.handler.codec.http.HttpServerKeepAliveHandler
import io.netty.handler.stream.ChunkedWriteHandler import io.netty.handler.stream.ChunkedWriteHandler
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.util.concurrent.TimeUnit
import org.http4k.core.HttpHandler import org.http4k.core.HttpHandler
import org.http4k.server.Http4kChannelHandler import org.http4k.server.Http4kChannelHandler
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
@ -67,9 +66,9 @@ class WebUiNetty(private val hostname: String, private val port: Int) : ServerCo
} }
override fun stop() = apply { override fun stop() = apply {
masterGroup.shutdownGracefully(5, 15, TimeUnit.SECONDS).sync() closeFuture.cancel(false)
workerGroup.shutdownGracefully(5, 15, TimeUnit.SECONDS).sync() workerGroup.shutdownGracefully()
closeFuture.sync() masterGroup.shutdownGracefully()
} }
override fun port(): Int = address.port override fun port(): Int = address.port

View File

@ -66,6 +66,7 @@ import org.http4k.routing.bind
import org.http4k.routing.routes import org.http4k.routing.routes
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
import org.http4k.server.asServer import org.http4k.server.asServer
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@ -250,13 +251,20 @@ class ImageServer(
LOGGER.trace { "Request for $sanitizedUri is being cached and served" } LOGGER.trace { "Request for $sanitizedUri is being cached and served" }
if (imageDatum == null) { if (imageDatum == null) {
synchronized(database) { try {
transaction(database) { synchronized(database) {
ImageDatum.new(imageId) { transaction(database) {
this.contentType = contentType ImageDatum.new(imageId) {
this.lastModified = lastModified this.contentType = contentType
this.lastModified = lastModified
}
} }
} }
} catch (_: ExposedSQLException) {
// some other code got to the database first, fall back to just serving
editor.abort()
LOGGER.trace { "Request for $sanitizedUri is being served" }
respondWithImage(mdResponse.body.stream, contentLength, contentType, lastModified, false)
} }
} }
@ -331,8 +339,7 @@ class ImageServer(
private fun String.isImageMimetype() = this.toLowerCase().startsWith("image/") private fun String.isImageMimetype() = this.toLowerCase().startsWith("image/")
fun getServer(cache: DiskLruCache, remoteSettings: RemoteSettings, serverSettings: ServerSettings, statistics: AtomicReference<Statistics>, isHandled: AtomicBoolean): Http4kServer { fun getServer(cache: DiskLruCache, database: Database, remoteSettings: RemoteSettings, serverSettings: ServerSettings, statistics: AtomicReference<Statistics>, isHandled: AtomicBoolean): Http4kServer {
val database = Database.connect("jdbc:sqlite:cache/data.db", "org.sqlite.JDBC")
val client = ApacheClient(responseBodyMode = BodyMode.Stream, client = HttpClients.custom() val client = ApacheClient(responseBodyMode = BodyMode.Stream, client = HttpClients.custom()
.disableConnectionState() .disableConnectionState()
.setDefaultRequestConfig( .setDefaultRequestConfig(

View File

@ -27,7 +27,7 @@ import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@Throws(SodiumException::class) @Throws(SodiumException::class)
internal fun LazySodiumJava.cryptoBoxOpenEasyAfterNm(cipherBytes: ByteArray, nonce: ByteArray, sharedKey: ByteArray): String { fun LazySodiumJava.cryptoBoxOpenEasyAfterNm(cipherBytes: ByteArray, nonce: ByteArray, sharedKey: ByteArray): String {
if (!Box.Checker.checkNonce(nonce.size)) { if (!Box.Checker.checkNonce(nonce.size)) {
throw SodiumException("Incorrect nonce length.") throw SodiumException("Incorrect nonce length.")
} }
@ -44,18 +44,18 @@ internal fun LazySodiumJava.cryptoBoxOpenEasyAfterNm(cipherBytes: ByteArray, non
return str(message) return str(message)
} }
internal fun getRc4(key: ByteArray): Cipher { fun getRc4(key: ByteArray): Cipher {
val rc4 = Cipher.getInstance("RC4") val rc4 = Cipher.getInstance("RC4")
rc4.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "RC4")) rc4.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "RC4"))
return rc4 return rc4
} }
internal fun md5Bytes(stringToHash: String): ByteArray { fun md5Bytes(stringToHash: String): ByteArray {
val digest = MessageDigest.getInstance("MD5") val digest = MessageDigest.getInstance("MD5")
return digest.digest(stringToHash.toByteArray()) return digest.digest(stringToHash.toByteArray())
} }
internal fun printHexString(bytes: ByteArray): String { fun printHexString(bytes: ByteArray): String {
val sb = StringBuilder() val sb = StringBuilder()
for (b in bytes) { for (b in bytes) {
sb.append(String.format("%02x", b)) sb.append(String.format("%02x", b))

View File

@ -24,13 +24,43 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
import dev.afanasev.sekret.Secret import dev.afanasev.sekret.Secret
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class ClientSettings( class ClientSettings(
val maxCacheSizeInMebibytes: Long = 20480, val maxCacheSizeInMebibytes: Long = 20480,
@JsonUnwrapped
val serverSettings: ServerSettings = ServerSettings(),
val webSettings: WebSettings? = null, val webSettings: WebSettings? = null,
val devSettings: DevSettings = DevSettings(isDev = false) val devSettings: DevSettings = DevSettings(isDev = false)
) ) {
// FIXME: jackson doesn't work with data classes and JsonUnwrapped
// fix this in 2.0 when we can break the settings file
// and remove the `@JsonUnwrapped`
@field:JsonUnwrapped
lateinit var serverSettings: ServerSettings
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ClientSettings
if (maxCacheSizeInMebibytes != other.maxCacheSizeInMebibytes) return false
if (webSettings != other.webSettings) return false
if (devSettings != other.devSettings) return false
if (serverSettings != other.serverSettings) return false
return true
}
override fun hashCode(): Int {
var result = maxCacheSizeInMebibytes.hashCode()
result = 31 * result + (webSettings?.hashCode() ?: 0)
result = 31 * result + devSettings.hashCode()
result = 31 * result + serverSettings.hashCode()
return result
}
override fun toString(): String {
return "ClientSettings(maxCacheSizeInMebibytes=$maxCacheSizeInMebibytes, webSettings=$webSettings, devSettings=$devSettings, serverSettings=$serverSettings)"
}
}
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class ServerSettings( data class ServerSettings(

View File

@ -5,12 +5,12 @@
</filter> </filter>
<file>log/latest.log</file> <file>log/latest.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>log/logFile.%d{yyyy-MM-dd_HH}.log</fileNamePattern> <fileNamePattern>log/logFile.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<maxHistory>12</maxHistory> <maxHistory>12</maxHistory>
<maxFileSize>100MB</maxFileSize> <maxFileSize>100MB</maxFileSize>
<totalSizeCap>1GB</totalSizeCap> <totalSizeCap>1GB</totalSizeCap>
</rollingPolicy> </rollingPolicy>-->
<encoder> <encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
@ -37,6 +37,6 @@
<appender-ref ref="ASYNC"/> <appender-ref ref="ASYNC"/>
</root> </root>
<logger name="Exposed" level="ERROR"/ <logger name="Exposed" level="ERROR"/>
<logger name="io.netty" level="INFO"/> <logger name="io.netty" level="INFO"/>
</configuration> </configuration>