1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-24 01:09:19 +00:00

Initial version of moving to from ConfigUtils to Configurate

This commit is contained in:
Tim203
2024-02-13 22:47:39 +01:00
parent 868ff810bf
commit e98faac36a
8 changed files with 187 additions and 115 deletions

View File

@@ -9,8 +9,9 @@ dependencies {
api(libs.base.api)
compileOnlyApi(projects.isolation)
annotationProcessor(libs.config.utils.ap)
api(libs.config.utils)
annotationProcessor(libs.configurate.`interface`.ap)
api(libs.configurate.`interface`)
implementation(libs.configurate.yaml)
annotationProcessor(libs.database.utils.ap)
api(libs.database.utils)

View File

@@ -26,38 +26,36 @@
package org.geysermc.floodgate.core.config;
import io.micronaut.context.env.PropertySource;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.geysermc.configutils.node.codec.strategy.object.ProxyEmbodimentStrategy;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.util.NamingSchemes;
public final class ConfigAsPropertySource {
private ConfigAsPropertySource() {}
public static PropertySource toPropertySource(FloodgateConfig config) {
Objects.requireNonNull(config);
return PropertySource.of(flatten(Map.of("config", config)));
public static PropertySource toPropertySource(ConfigurationNode rootNode) {
Objects.requireNonNull(rootNode);
var root = CommentedConfigurationNode.root();
root.node("config").from(rootNode.copy());
return PropertySource.of(flatten(root));
}
private static Map<String, Object> flatten(Map<String, Object> map) {
return flatten0(Map.of("", map).get(""));
}
@SuppressWarnings("unchecked")
private static Map<String, Object> flatten0(Map<String, Object> map) {
private static Map<String, Object> flatten(ConfigurationNode node) {
var result = new HashMap<String, Object>();
map.forEach((key, value) -> {
if (Proxy.isProxyClass(value.getClass())) {
value = new ProxyEmbodimentStrategy().disembody(value);
}
if (value instanceof Map<?,?>) {
flatten0((Map<String, Object>) value).forEach(
(key1, value1) -> result.put(key + "." + key1, value1)
);
node.childrenMap().values().forEach(value -> {
// we expect the properties in camelCase
var key = NamingSchemes.CAMEL_CASE.coerce(value.key().toString());
if (value.isMap()) {
flatten(value).forEach((childKey, childValue) -> result.put(key + "." + childKey, childValue));
return;
}
result.put(key, value);
result.put(key, value.raw());
});
return result;
}

View File

@@ -25,12 +25,17 @@
package org.geysermc.floodgate.core.config;
import static org.spongepowered.configurate.NodePath.path;
import io.micronaut.context.ApplicationContext;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.geysermc.configutils.ConfigUtilities;
import org.geysermc.configutils.file.codec.PathFileCodec;
import org.geysermc.configutils.updater.change.Changes;
import org.geysermc.floodgate.core.util.Constants;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.interfaces.InterfaceDefaultOptions;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
public final class ConfigLoader {
private ConfigLoader() {}
@@ -39,33 +44,38 @@ public final class ConfigLoader {
public static <T extends FloodgateConfig> T load(Path dataDirectory, boolean isProxy, ApplicationContext context) {
var configClass = isProxy ? ProxyFloodgateConfig.class : FloodgateConfig.class;
// it would also be nice to have sections in versionBuilder so that you don't have to
// provide the path all the time
ConfigUtilities utilities =
ConfigUtilities.builder()
.fileCodec(PathFileCodec.of(dataDirectory))
.configFile("config.yml")
.changes(Changes.builder()
.version(1, Changes.versionBuilder()
.keyRenamed("playerLink.enable", "playerLink.enabled")
.keyRenamed("playerLink.allowLinking", "playerLink.allowed"))
.version(2, Changes.versionBuilder()
.keyRenamed("playerLink.useGlobalLinking", "playerLink.enableGlobalLinking"))
.version(3, Changes.versionBuilder()
.keyRenamed("playerLink.type", "database.type"))
.build())
.definePlaceholder("metrics.uuid", UUID::randomUUID)
.postInitializeCallbackArgument(dataDirectory)
.build();
ConfigurationNode node;
T config;
try {
config = (T) utilities.executeOn(configClass);
} catch (Throwable throwable) {
var loader = YamlConfigurationLoader.builder()
.path(dataDirectory.resolve("config.yml"))
.defaultOptions(InterfaceDefaultOptions.get())
.build();
node = loader.load();
// temp fix for node.virtual() being broken
var virtual = !Files.exists(dataDirectory.resolve("config.yml"));
var migrations = ConfigurationTransformation.versionedBuilder()
.addVersion(Constants.CONFIG_VERSION, twoToThree())
.addVersion(2, oneToTwo())
.addVersion(1, zeroToOne())
.build();
var startVersion = migrations.version(node);
migrations.apply(node);
var endVersion = migrations.version(node);
config = (T) node.get(configClass);
// save default config or save migrated config
if (virtual || startVersion != endVersion) {
loader.save(node);
}
} catch (ConfigurateException exception) {
throw new RuntimeException(
"Failed to load the config! Try to delete the config file if this error persists",
throwable
exception
);
}
@@ -73,7 +83,34 @@ public final class ConfigLoader {
context.registerSingleton(config);
context.registerSingleton(FloodgateConfig.class, config);
// make @Requires etc. work
context.getEnvironment().addPropertySource(ConfigAsPropertySource.toPropertySource(config));
context.getEnvironment().addPropertySource(ConfigAsPropertySource.toPropertySource(node));
return config;
}
private static ConfigurationTransformation zeroToOne() {
return ConfigurationTransformation.builder()
.addAction(path("playerLink", "enable"), (path, value) -> {
return new Object[]{"playerLink", "enabled"};
})
.addAction(path("playerLink", "allowLinking"), (path, value) -> {
return new Object[]{"playerLink", "allowed"};
})
.build();
}
private static ConfigurationTransformation oneToTwo() {
return ConfigurationTransformation.builder()
.addAction(path("playerLink", "useGlobalLinking"), (path, value) -> {
return new Object[]{"playerLink", "enableGlobalLinking"};
})
.build();
}
private static ConfigurationTransformation twoToThree() {
return ConfigurationTransformation.builder()
.addAction(path("playerLink", "type"), (path, value) -> {
return new Object[]{"database", "type"};
})
.build();
}
}

View File

@@ -25,43 +25,45 @@
package org.geysermc.floodgate.core.config;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
import org.geysermc.configutils.loader.callback.CallbackResult;
import org.geysermc.configutils.loader.callback.GenericPostInitializeCallback;
import org.geysermc.configutils.node.meta.Comment;
import org.geysermc.configutils.node.meta.ConfigSection;
import org.geysermc.configutils.node.meta.ConfigVersion;
import org.geysermc.configutils.node.meta.Defaults.DefaultBoolean;
import org.geysermc.configutils.node.meta.Defaults.DefaultNumeric;
import org.geysermc.configutils.node.meta.Defaults.DefaultString;
import org.geysermc.configutils.node.meta.Exclude;
import org.geysermc.configutils.node.meta.Hidden;
import org.geysermc.configutils.node.meta.Placeholder;
import java.util.UUID;
import org.geysermc.floodgate.core.util.Constants;
import org.spongepowered.configurate.interfaces.meta.Exclude;
import org.spongepowered.configurate.interfaces.meta.Field;
import org.spongepowered.configurate.interfaces.meta.Hidden;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultNumeric;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultString;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import org.spongepowered.configurate.objectmapping.meta.PostProcess;
/**
* The global Floodgate configuration file used in every platform. Some platforms have their own
* addition to the global configuration like {@link ProxyFloodgateConfig} for the proxies.
*/
@ConfigVersion(3)
public interface FloodgateConfig extends GenericPostInitializeCallback<Path> {
@ConfigSerializable
public interface FloodgateConfig {
@Exclude
default boolean proxy() {
return this instanceof ProxyFloodgateConfig;
}
@Override
default CallbackResult postInitialize(Path dataDirectory) {
Path keyPath = dataDirectory.resolve(keyFileName());
// don't assume that the key always exists with the existence of a config
if (!Files.exists(keyPath)) {
// TODO improve message and also link to article about corrupted keys/WinSCP/FTP
// Like, where is key.pem?
// TODO don't be so noisy with error. It makes it hard to understand what the error is.
return CallbackResult.failed("Floodgate requires a key file! " +
"Copy your key file from Geyser (key.pem) and paste it into " + keyPath);
}
@PostProcess
default void postInitialise() {
//todo add postInitialize with argument
// @PostProcess
// default void postInitialize(Path dataDirectory) throws SerializationException {
// Path keyPath = dataDirectory.resolve(keyFileName());
//
// // don't assume that the key always exists with the existence of a config
// if (!Files.exists(keyPath)) {
// // TODO improve message and also link to article about corrupted keys/WinSCP/FTP
// // Like, where is key.pem?
// // TODO don't be so noisy with error. It makes it hard to understand what the error is.
// throw new SerializationException("Floodgate requires a key file! " +
// "Copy your key file from Geyser (key.pem) and paste it into " + keyPath);
// }
//todo remove key file name config option
@@ -71,25 +73,29 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<Path> {
if (usernamePrefix().length() >= 16) {
usernamePrefix(".");
}
return CallbackResult.ok();
}
@Comment
@Comment("""
In Floodgate bedrock player data is send encrypted.
The following value should point to the key Floodgate generated.
The public key should be used for the Geyser(s) and the private key for the Floodgate(s)""")
@DefaultString("key.pem")
String keyFileName();
@Comment
@Comment("""
Floodgate prepends a prefix to bedrock usernames to avoid conflicts
However, certain conflicts can cause issues with some plugins so this prefix is configurable using the property below
It is recommended to use a prefix that does not contain alphanumerical to avoid the possibility of duplicate usernames.""")
@DefaultString(".")
String usernamePrefix();
void usernamePrefix(String usernamePrefix);
@Comment
@Comment("Should spaces be replaced with '_' in bedrock usernames?")
@DefaultBoolean(true)
boolean replaceSpaces();
@Comment
@Comment("The default locale for Floodgate. By default, Floodgate uses the system locale")
@DefaultString("system")
String defaultLocale();
@@ -97,78 +103,103 @@ public interface FloodgateConfig extends GenericPostInitializeCallback<Path> {
DatabaseConfig database();
@Comment
@Comment("Configuration for player linking")
PlayerLinkConfig playerLink();
MetricsConfig metrics();
default int version() {
return Constants.CONFIG_VERSION;
}
@Hidden
@DefaultBoolean
boolean debug();
@Exclude Key key();
@Field Key key();
void key(Key key);
@Field void key(Key key);
@Exclude String rawUsernamePrefix();
@Field String rawUsernamePrefix();
void rawUsernamePrefix(String usernamePrefix);
@Field void rawUsernamePrefix(String usernamePrefix);
@ConfigSection
@ConfigSerializable
interface DisconnectMessages {
@Comment
@Comment("The disconnect message Geyser users should get when connecting\n" +
"to the server with an invalid key")
@DefaultString("Please connect through the official Geyser")
String invalidKey();
@Comment
@Comment("The disconnect message Geyser users should get when connecting\n" +
"to the server with the correct key but not with the correct data format")
@DefaultString("Expected {} arguments, got {}. Is Geyser up-to-date?")
String invalidArgumentsLength();
}
@ConfigSection
@ConfigSerializable
interface DatabaseConfig {
@Comment
@Comment("Whether a database can be used (required for for example local linking)")
@DefaultBoolean(true)
boolean enabled();
@Comment
@Comment("The database type you want to use.")
@DefaultString("h2")
String type();
}
@ConfigSection
@ConfigSerializable
interface PlayerLinkConfig {
@Comment
@Comment("Whether to enable the linking system. Turning this off will prevent\n" +
"players from using the linking feature even if they are already linked.")
@DefaultBoolean(true)
boolean enabled();
@Comment
@Comment("Whether to require a linked account in order to be able to join the server.")
@DefaultBoolean
boolean requireLink();
@Comment
@Comment("""
Set the following option to true when you want to host your own linking database.
-> This can work in addition to global linking.
Note that you have to enable the database in the database section as well.
""")
@DefaultBoolean
boolean enableOwnLinking();
@Comment
@Comment("""
The following two options only apply when 'enable-own-linking' is set to 'true'
Whether to allow the use of /linkaccount and /unlinkaccount
You can also use allow specific people to use the commands using the
permissions floodgate.command.linkaccount and floodgate.command.unlinkaccount.
This is only for linking, already connected people will stay connected""")
@DefaultBoolean(true)
boolean allowed();
@Comment
@Comment("The amount of seconds until a link code expires.")
@DefaultNumeric(300)
long linkCodeTimeout();
@Comment
@Comment("""
Whether to enable global linking. Global Linking is a central server where people can link their
accounts (Java and Bedrock) and join on servers that have Global Linking enabled. The goal of
Global Linking is to make linking easier by not having to link your accounts on every server.
-> Your server-specific linking database will have priority over global linking.
Global Linking should normally only be disabled when you don't have internet access or when
you have limited internet access.
""")
@DefaultBoolean(true)
boolean enableGlobalLinking();
}
@ConfigSection
@ConfigSerializable
interface MetricsConfig {
@DefaultBoolean(true)
boolean enabled();
@Placeholder
String uuid();
default UUID uuid() {
return UUID.randomUUID();
}
}
}

View File

@@ -25,17 +25,19 @@
package org.geysermc.floodgate.core.config;
import org.geysermc.configutils.node.meta.Comment;
import org.geysermc.configutils.node.meta.ConfigVersion;
import org.geysermc.configutils.node.meta.Defaults.DefaultBoolean;
import org.geysermc.configutils.node.meta.Inherit;
import org.spongepowered.configurate.interfaces.meta.defaults.DefaultBoolean;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
/**
* The Floodgate configuration used by proxy platforms, currently Velocity and Bungeecord.
* The Floodgate configuration used by proxy platforms, currently Velocity and BungeeCord.
*/
@Inherit(ConfigVersion.class)
@ConfigSerializable
public interface ProxyFloodgateConfig extends FloodgateConfig {
@Comment
@Comment("""
Should the proxy send the bedrock player data to the servers it is connecting to?
This requires Floodgate to be installed on the servers.
You'll get kicked if you don't use the plugin. The default value is false because of it""")
@DefaultBoolean
boolean sendFloodgateData();
}

View File

@@ -63,7 +63,7 @@ public final class Metrics {
metricsBase = new MetricsBase(
"server-implementation",
metricsConfig.uuid(),
metricsConfig.uuid().toString(),
Constants.METRICS_ID,
metricsConfig.enabled(),
this::appendPlatformData,

View File

@@ -32,6 +32,8 @@ public final class Constants {
public static final String GIT_BRANCH = "@branch@";
public static final int METRICS_ID = 14649;
public static final int CONFIG_VERSION = 3;
public static final char COLOR_CHAR = '\u00A7';
public static final boolean DEBUG_MODE = false;

View File

@@ -11,7 +11,7 @@ gson = "2.8.5"
# core
base-api = "feature-floodgate-merge-1.1.0-SNAPSHOT"
config-utils = "development-2.0-SNAPSHOT"
configurate = "4.2.0-GeyserMC-SNAPSHOT"
database-utils = "1.0-SNAPSHOT"
fastutil = "8.5.3"
java-websocket = "1.5.2"
@@ -47,8 +47,9 @@ netty-transport = { module = "io.netty:netty-transport", version.ref = "netty" }
# core
base-api = { module = "org.geysermc.api:base-api", version.ref = "base-api" }
config-utils = { module = "org.geysermc.configutils:core", version.ref = "config-utils" }
config-utils-ap = { module = "org.geysermc.configutils:ap", version.ref = "config-utils" }
configurate-interface-ap = { module = "org.spongepowered:configurate-extra-interface-ap", version.ref = "configurate" }
configurate-interface = { module = "org.spongepowered:configurate-extra-interface", version.ref = "configurate" }
configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" }
database-utils = { module = "org.geysermc.databaseutils:core", version.ref = "database-utils" }
database-utils-sql = { module = "org.geysermc.databaseutils:database-sql", version.ref = "database-utils" }
database-utils-mongo = { module = "org.geysermc.databaseutils:database-mongo", version.ref = "database-utils" }