Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ kotlin {
languageSettings {
optIn("kotlin.RequiresOptIn")
optIn("kotlin.ExperimentalStdlibApi")
optIn("kotlinx.serialization.ExperimentalSerializationApi")
optIn("kotlin.contracts.ExperimentalContracts")
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
Expand Down
35 changes: 28 additions & 7 deletions src/main/kotlin/com/github/rushyverse/api/RushyServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import com.github.rushyverse.api.command.GamemodeCommand
import com.github.rushyverse.api.command.GiveCommand
import com.github.rushyverse.api.command.KickCommand
import com.github.rushyverse.api.command.StopCommand
import com.github.rushyverse.api.configuration.IBungeeCordConfiguration
import com.github.rushyverse.api.configuration.IConfiguration
import com.github.rushyverse.api.configuration.IVelocityConfiguration
import com.github.rushyverse.api.configuration.*
import com.github.rushyverse.api.translation.ResourceBundleTranslationsProvider
import com.github.rushyverse.api.translation.SupportedLanguage
import com.github.rushyverse.api.translation.TranslationsProvider
import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales
import com.github.rushyverse.api.utils.workingDirectory
Expand All @@ -22,6 +21,7 @@ import net.minestom.server.instance.AnvilLoader
import net.minestom.server.instance.InstanceContainer
import java.io.File
import java.util.*
import kotlin.reflect.KClass

public val logger: KLogger = KotlinLogging.logger { }

Expand Down Expand Up @@ -82,6 +82,10 @@ public abstract class RushyServer {
minecraftServer.start("0.0.0.0", serverConfig.port)
}

/**
* Enable the online mode if [enabled] is true.
* @param enabled If the online mode should be enabled.
*/
protected open suspend fun applyOnlineMode(enabled: Boolean) {
if (enabled) {
logger.info { "Enabling Online mode" }
Expand Down Expand Up @@ -110,7 +114,7 @@ public abstract class RushyServer {
if (bungeeCord.enabled) {
logger.info { "Enabling BungeeCord support" }
BungeeCordProxy.enable()
BungeeCordProxy.setBungeeGuardTokens(setOf(bungeeCord.secret))
BungeeCordProxy.setBungeeGuardTokens(bungeeCord.secrets)
logger.info { "BungeeCord support enabled" }
}
}
Expand All @@ -134,16 +138,33 @@ public abstract class RushyServer {

/**
* Load the configuration using the file or the default config file.
* Will use the [HoconConfigurationReader] to load the configuration.
* @param configFile Path of the configuration file.
* @return The configuration of the server.
*/
protected suspend inline fun <reified T : Any> loadConfiguration(
configFile: String?
): T {
return loadConfiguration(T::class, configFile)
}

/**
* Load the configuration using the file or the default config file.
* @param clazz Type of configuration class to load.
* @param configFile Path of the configuration file.
* @return The configuration of the server.
*/
protected inline fun <reified T> loadConfiguration(configFile: String?): T {
val configurationFile = IConfiguration.getOrCreateConfigurationFile(configFile)
return IConfiguration.readHoconConfigurationFile(configurationFile)
protected open suspend fun <T : Any> loadConfiguration(
clazz: KClass<T>,
configFile: String?
): T {
val configurationFile = IConfigurationReader.getOrCreateConfigurationFile(configFile)
return HoconConfigurationReader().readConfigurationFile(clazz, configurationFile)
}

/**
* Create a translation provider to provide translations for the [supported languages][SupportedLanguage].
* @param bundles Bundles to load.
* @return New translation provider.
*/
protected open suspend fun createTranslationsProvider(bundles: Iterable<String>): TranslationsProvider {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.github.rushyverse.api.configuration

import com.github.rushyverse.api.serializer.PosSerializer
import com.typesafe.config.ConfigFactory
import kotlinx.serialization.KSerializer
import kotlinx.serialization.hocon.Hocon
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer
import net.minestom.server.coordinate.Pos
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.full.createType

/**
* Read configuration from HOCON file.
* @receiver Configuration reader.
* @param configFile Configuration file to load.
* @return The configuration loaded from the given file.
*/
public inline fun <reified T> HoconConfigurationReader.readConfigurationFile(configFile: File): T =
readConfigurationFile(hocon.serializersModule.serializer(), configFile)

/**
* Read configuration from HOCON file.
* @property hocon [Hocon] configuration to use.
*/
public class HoconConfigurationReader(public val hocon: Hocon = hoconDefault) : IConfigurationReader {

public companion object {
/**
* Default [Hocon] configuration using custom serializer.
* @see PosSerializer
*/
public val hoconDefault: Hocon = Hocon {
serializersModule = SerializersModule {
contextual(Pos::class, PosSerializer)
}
}
}

override fun <T : Any> readConfigurationFile(clazz: KClass<T>, configFile: File): T {
val serializer = hocon.serializersModule.serializer(clazz.createType())
@Suppress("UNCHECKED_CAST")
return readConfigurationFile(serializer as KSerializer<T>, configFile)
}

override fun <T> readConfigurationFile(serializer: KSerializer<T>, configFile: File): T {
val config = ConfigFactory.parseFile(configFile)
return hocon.decodeFromConfig(serializer, config)
}
}
Original file line number Diff line number Diff line change
@@ -1,83 +1,15 @@
package com.github.rushyverse.api.configuration

import com.github.rushyverse.api.utils.workingDirectory
import com.typesafe.config.ConfigFactory
import kotlinx.serialization.Serializable
import kotlinx.serialization.hocon.Hocon
import kotlinx.serialization.hocon.decodeFromConfig
import java.io.File
import java.io.FileNotFoundException

/**
* Configuration of the application.
* @property server Configuration of server.
*/
public interface IConfiguration {

public companion object {

/**
* Default name of the config file.
* This name is used to create the default config file when the user does not provide one.
*/
public const val DEFAULT_CONFIG_FILE_NAME: String = "server.conf"

/**
* Get the configuration file from the given path.
* If the path is null, the default config file will be used.
* If the default config file does not exist, it will be created with the default configuration from resources folder.
* @param filePath Path of the configuration file.
* @return The configuration file that must be used to load application configuration.
*/
public fun getOrCreateConfigurationFile(filePath: String? = null): File {
if (filePath != null) {
val configFile = File(filePath)
if (!configFile.isFile) {
throw FileNotFoundException("Config file $filePath does not exist or is not a regular file")
}
return configFile
}

return getOrCreateDefaultConfigurationFile(workingDirectory)
}

/**
* Search for the default config file in the current directory.
* If the file does not exist, it will be created with the default configuration from resources folder.
* @return The default config file.
*/
private fun getOrCreateDefaultConfigurationFile(parent: File): File =
File(parent, DEFAULT_CONFIG_FILE_NAME).apply {
if (exists()) {
return this
}

val defaultConfiguration =
IConfiguration::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME)
?: error("Unable to find default configuration file in server resources")

defaultConfiguration.use { inputStream ->
if (!createNewFile()) {
throw FileSystemException(this, null, "Unable to create configuration file $absolutePath")
}

outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}

/**
* Load the configuration from the given file with HOCON format.
* @param configFile Configuration file to load.
* @return The configuration loaded from the given file.
*/
public inline fun <reified T> readHoconConfigurationFile(configFile: File): T =
Hocon.decodeFromConfig(ConfigFactory.parseFile(configFile))

}

public val server: IServerConfiguration

}

/**
Expand Down Expand Up @@ -127,13 +59,13 @@ public data class VelocityConfiguration(
/**
* Configuration to connect the server to the bungeeCord proxy.
* @property enabled Whether the server should connect to the bungeeCord proxy.
* @property secret Secret to verify if the client comes from the proxy.
* @property secrets Secrets to verify if the client comes from the proxy.
*/
public interface IBungeeCordConfiguration {

public val enabled: Boolean

public val secret: String
public val secrets: Set<String>

}

Expand All @@ -143,5 +75,5 @@ public interface IBungeeCordConfiguration {
@Serializable
public data class BungeeCordConfiguration(
override val enabled: Boolean,
override val secret: String
override val secrets: Set<String>
) : IBungeeCordConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.github.rushyverse.api.configuration

import com.github.rushyverse.api.utils.workingDirectory
import kotlinx.serialization.KSerializer
import java.io.File
import java.io.FileNotFoundException
import kotlin.reflect.KClass

/**
* Configuration reader.
*/
public interface IConfigurationReader {

public companion object {

/**
* Default name of the config file.
* This name is used to create the default config file when the user does not provide one.
*/
public const val DEFAULT_CONFIG_FILE_NAME: String = "server.conf"

/**
* Get the configuration file from the given path.
* If the path is null, the default config file will be used.
* If the default config file does not exist, it will be created with the default configuration from resources folder.
* @param filePath Path of the configuration file.
* @return The configuration file that must be used to load application configuration.
*/
public fun getOrCreateConfigurationFile(filePath: String? = null): File {
if (filePath != null) {
val configFile = File(filePath)
if (!configFile.isFile) {
throw FileNotFoundException("Config file $filePath does not exist or is not a regular file")
}
return configFile
}

return getOrCreateDefaultConfigurationFile(workingDirectory)
}

/**
* Search for the default config file in the current directory.
* If the file does not exist, it will be created with the default configuration from resources folder.
* @param parent Parent directory of the default config file.
* @return The default config file.
*/
private fun getOrCreateDefaultConfigurationFile(parent: File): File =
File(parent, DEFAULT_CONFIG_FILE_NAME).apply {
if (exists()) {
return this
}

val defaultConfiguration =
IConfigurationReader::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME)
?: error("Unable to find default configuration file in server resources")

defaultConfiguration.use { inputStream ->
if (!createNewFile()) {
throw FileSystemException(this, null, "Unable to create configuration file $absolutePath")
}

outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}
}

/**
* Load the configuration from the given file.
* @param clazz Type of configuration class to load.
* @param configFile Configuration file to load.
* @return The configuration loaded from the given file.
*/
public fun <T : Any> readConfigurationFile(clazz: KClass<T>, configFile: File): T

/**
* Load the configuration from the given file.
* @param serializer Serializer to deserialize the configuration to the given type.
* @param configFile Configuration file to load.
* @return The configuration loaded from the given file.
*/
public fun <T> readConfigurationFile(serializer: KSerializer<T>, configFile: File): T
}
4 changes: 2 additions & 2 deletions src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ abstract class AbstractTest {
DEFAULT_WORLD,
false,
VelocityConfiguration(false, ""),
BungeeCordConfiguration(false, "")
BungeeCordConfiguration(false, emptySet())
)
)

Expand All @@ -66,7 +66,7 @@ abstract class AbstractTest {

protected fun configurationToHoconFile(
configuration: TestConfiguration,
file: File = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME)
file: File = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME)
) =
file.writeText(configurationToHocon(configuration).root().render())

Expand Down
10 changes: 4 additions & 6 deletions src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import com.github.rushyverse.api.command.GamemodeCommand
import com.github.rushyverse.api.command.GiveCommand
import com.github.rushyverse.api.command.KickCommand
import com.github.rushyverse.api.command.StopCommand
import com.github.rushyverse.api.configuration.BungeeCordConfiguration
import com.github.rushyverse.api.configuration.IConfiguration
import com.github.rushyverse.api.configuration.VelocityConfiguration
import com.github.rushyverse.api.configuration.*
import com.github.rushyverse.api.utils.randomString
import kotlinx.coroutines.test.runTest
import net.minestom.server.MinecraftServer
Expand Down Expand Up @@ -51,10 +49,10 @@ class RushyServerTest : AbstractTest() {
assertThrows<IOException> {
TestServer().start()
}
val configurationFile = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME)
val configurationFile = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME)
assertTrue { configurationFile.isFile }

val configuration = IConfiguration.readHoconConfigurationFile<TestConfiguration>(configurationFile)
val configuration = HoconConfigurationReader().readConfigurationFile<TestConfiguration>(configurationFile)
assertEquals(expectedDefaultConfiguration, configuration)
}

Expand Down Expand Up @@ -180,7 +178,7 @@ class RushyServerTest : AbstractTest() {
val defaultConfiguration = expectedDefaultConfiguration
val configuration = expectedDefaultConfiguration.copy(
server = defaultConfiguration.server.copy(
bungeeCord = BungeeCordConfiguration(enabled, secret)
bungeeCord = BungeeCordConfiguration(enabled, setOf(secret))
)
)

Expand Down
Loading