1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Switched to Hikari for MySQL

This commit is contained in:
Tim203
2022-08-31 03:03:42 +02:00
parent eca042dc82
commit 0f152141a2
9 changed files with 349 additions and 318 deletions

View File

@@ -32,6 +32,7 @@ import com.google.gson.JsonObject;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -42,18 +43,23 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nonnull; import java.util.stream.Stream;
import javax.inject.Named; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.PlayerLinkConfig;
import org.geysermc.floodgate.event.ShutdownEvent;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.InjectorHolder; import org.geysermc.floodgate.util.InjectorHolder;
import org.geysermc.floodgate.util.Utils; import org.geysermc.floodgate.util.Utils;
@Listener
@Singleton @Singleton
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final class PlayerLinkLoader { public final class PlayerLinkHolder {
@Inject private Injector injector; @Inject private Injector injector;
@Inject private FloodgateConfig config; @Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@@ -62,30 +68,38 @@ public final class PlayerLinkLoader {
@Named("dataDirectory") @Named("dataDirectory")
private Path dataDirectory; private Path dataDirectory;
@Nonnull private URLClassLoader classLoader;
private PlayerLink instance;
@NonNull
public PlayerLink load() { public PlayerLink load() {
if (instance != null) {
return instance;
}
if (config == null) { if (config == null) {
throw new IllegalStateException("Config cannot be null!"); throw new IllegalStateException("Config cannot be null!");
} }
FloodgateConfig.PlayerLinkConfig lConfig = config.getPlayerLink(); PlayerLinkConfig linkConfig = config.getPlayerLink();
if (!lConfig.isEnabled()) { if (!linkConfig.isEnabled()) {
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} }
List<Path> files; List<Path> files;
try { try (Stream<Path> list = Files.list(dataDirectory)) {
files = Files.list(dataDirectory) files = list
.filter(path -> Files.isRegularFile(path) && path.toString().endsWith("jar")) .filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".jar"))
.collect(Collectors.toList()); .collect(Collectors.toList());
} catch (IOException exception) { } catch (IOException exception) {
logger.error("Failed to list possible database implementations", exception); logger.error("Failed to list possible database implementations", exception);
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} }
// we can skip the rest if global linking is enabled and no database implementations has been // we can skip the rest if global linking is enabled and no database implementations has
// found, or when global linking is enabled and own player linking is disabled. // been found, or when global linking is enabled and own player linking is disabled.
if (lConfig.isEnableGlobalLinking() && (files.isEmpty() || !lConfig.isEnableOwnLinking())) { if (linkConfig.isEnableGlobalLinking() &&
(files.isEmpty() || !linkConfig.isEnableOwnLinking())) {
return injector.getInstance(GlobalPlayerLinking.class); return injector.getInstance(GlobalPlayerLinking.class);
} }
@@ -100,7 +114,7 @@ public final class PlayerLinkLoader {
// We only want to load one database implementation // We only want to load one database implementation
if (files.size() > 1) { if (files.size() > 1) {
boolean found = false; boolean found = false;
databaseName = lConfig.getType(); databaseName = linkConfig.getType();
String expectedName = "floodgate-" + databaseName + "-database.jar"; String expectedName = "floodgate-" + databaseName + "-database.jar";
for (Path path : files) { for (Path path : files) {
@@ -111,14 +125,18 @@ public final class PlayerLinkLoader {
} }
if (!found) { if (!found) {
logger.error("Failed to find an implementation for type: {}", lConfig.getType()); logger.error(
"Failed to find an implementation for type: {}", linkConfig.getType()
);
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} }
} else { } else {
String name = implementationPath.getFileName().toString(); String name = implementationPath.getFileName().toString();
if (!Utils.isValidDatabaseName(name)) { if (!Utils.isValidDatabaseName(name)) {
logger.error("Found database {} but the name doesn't match {}", logger.error(
name, Constants.DATABASE_NAME_FORMAT); "Found database {} but the name doesn't match {}",
name, Constants.DATABASE_NAME_FORMAT
);
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} }
int firstSplit = name.indexOf('-') + 1; int firstSplit = name.indexOf('-') + 1;
@@ -133,24 +151,22 @@ public final class PlayerLinkLoader {
// we don't have a way to close this properly since we have no stop method and we have // we don't have a way to close this properly since we have no stop method and we have
// to be able to load classes on the fly, but that doesn't matter anyway since Floodgate // to be able to load classes on the fly, but that doesn't matter anyway since Floodgate
// doesn't support reloading // doesn't support reloading
URLClassLoader classLoader = new URLClassLoader( classLoader = new URLClassLoader(
new URL[]{pluginUrl}, new URL[]{pluginUrl},
PlayerLinkLoader.class.getClassLoader() PlayerLinkHolder.class.getClassLoader()
); );
String mainClassName; String mainClassName;
JsonObject linkConfig; JsonObject dbInitConfig;
try (InputStream linkConfigStream =
classLoader.getResourceAsStream("init.json")) {
try (InputStream linkConfigStream = classLoader.getResourceAsStream("init.json")) {
requireNonNull(linkConfigStream, "Implementation should have an init file"); requireNonNull(linkConfigStream, "Implementation should have an init file");
linkConfig = new Gson().fromJson( dbInitConfig = new Gson().fromJson(
new InputStreamReader(linkConfigStream), JsonObject.class new InputStreamReader(linkConfigStream), JsonObject.class
); );
mainClassName = linkConfig.get("mainClass").getAsString(); mainClassName = dbInitConfig.get("mainClass").getAsString();
} }
Class<? extends PlayerLink> mainClass = Class<? extends PlayerLink> mainClass =
@@ -167,16 +183,16 @@ public final class PlayerLinkLoader {
Names.named("databaseClassLoader")).toInstance(classLoader); Names.named("databaseClassLoader")).toInstance(classLoader);
binder.bind(JsonObject.class) binder.bind(JsonObject.class)
.annotatedWith(Names.named("databaseInitData")) .annotatedWith(Names.named("databaseInitData"))
.toInstance(linkConfig); .toInstance(dbInitConfig);
binder.bind(InjectorHolder.class) binder.bind(InjectorHolder.class)
.toInstance(injectorHolder); .toInstance(injectorHolder);
}); });
injectorHolder.set(linkInjector); injectorHolder.set(linkInjector);
PlayerLink instance = linkInjector.getInstance(mainClass); instance = linkInjector.getInstance(mainClass);
// we use our own internal PlayerLinking when global linking is enabled // we use our own internal PlayerLinking when global linking is enabled
if (lConfig.isEnableGlobalLinking()) { if (linkConfig.isEnableGlobalLinking()) {
GlobalPlayerLinking linking = linkInjector.getInstance(GlobalPlayerLinking.class); GlobalPlayerLinking linking = linkInjector.getInstance(GlobalPlayerLinking.class);
linking.setDatabaseImpl(instance); linking.setDatabaseImpl(instance);
linking.load(); linking.load();
@@ -186,8 +202,10 @@ public final class PlayerLinkLoader {
return instance; return instance;
} }
} catch (ClassCastException exception) { } catch (ClassCastException exception) {
logger.error("The database implementation ({}) doesn't extend the PlayerLink class!", logger.error(
implementationPath.getFileName().toString(), exception); "The database implementation ({}) doesn't extend the PlayerLink class!",
implementationPath.getFileName().toString(), exception
);
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} catch (Exception exception) { } catch (Exception exception) {
if (init) { if (init) {
@@ -198,4 +216,10 @@ public final class PlayerLinkLoader {
return new DisabledPlayerLink(); return new DisabledPlayerLink();
} }
} }
@Subscribe
public void onShutdown(ShutdownEvent ignored) throws Exception {
instance.stop();
classLoader.close();
}
} }

View File

@@ -55,7 +55,7 @@ import org.geysermc.floodgate.crypto.KeyProducer;
import org.geysermc.floodgate.event.EventBus; import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.util.ListenerAnnotationMatcher; import org.geysermc.floodgate.event.util.ListenerAnnotationMatcher;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkHolder;
import org.geysermc.floodgate.packet.PacketHandlersImpl; import org.geysermc.floodgate.packet.PacketHandlersImpl;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
@@ -102,7 +102,7 @@ public class CommonModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public PlayerLink playerLink(PlayerLinkLoader linkLoader) { public PlayerLink playerLink(PlayerLinkHolder linkLoader) {
return linkLoader.load(); return linkLoader.load();
} }

View File

@@ -26,13 +26,13 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.inject.Named;
import org.bstats.MetricsBase; import org.bstats.MetricsBase;
import org.bstats.charts.DrilldownPie; import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimplePie; import org.bstats.charts.SimplePie;

View File

@@ -0,0 +1,4 @@
[*]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 4

View File

@@ -1,8 +1,10 @@
val mariadbClientVersion = "2.7.4" val mysqlClientVersion = "8.0.30"
val hikariVersion = "4.0.3"
dependencies { dependencies {
provided(projects.core) provided(projects.core)
implementation("org.mariadb.jdbc", "mariadb-java-client" , mariadbClientVersion) implementation("mysql", "mysql-connector-java", mysqlClientVersion)
implementation("com.zaxxer", "HikariCP", hikariVersion)
} }
description = "The Floodgate database extension for MySQL" description = "The Floodgate database extension for MySQL"

View File

@@ -25,6 +25,8 @@
package org.geysermc.floodgate.database; package org.geysermc.floodgate.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.sql.Connection; import java.sql.Connection;
@@ -43,309 +45,296 @@ import org.geysermc.floodgate.database.config.MysqlConfig;
import org.geysermc.floodgate.link.CommonPlayerLink; import org.geysermc.floodgate.link.CommonPlayerLink;
import org.geysermc.floodgate.link.LinkRequestImpl; import org.geysermc.floodgate.link.LinkRequestImpl;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.mariadb.jdbc.MariaDbPoolDataSource;
public class MysqlDatabase extends CommonPlayerLink { public class MysqlDatabase extends CommonPlayerLink {
private MariaDbPoolDataSource pool; private HikariDataSource dataSource;
@Override @Override
public void load() { public void load() {
getLogger().info("Connecting to a MySQL-like database..."); getLogger().info("Connecting to a MySQL-like database...");
try { try {
Class.forName("org.mariadb.jdbc.Driver"); MysqlConfig config = getConfig(MysqlConfig.class);
MysqlConfig databaseconfig = getConfig(MysqlConfig.class);
pool = new MariaDbPoolDataSource(); HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://" + config.getHostname() + "/" + config.getDatabase());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
hikariConfig.setPoolName("floodgate-linking-mysql");
hikariConfig.setMinimumIdle(5);
hikariConfig.setMaximumPoolSize(10);
String hostname = databaseconfig.getHostname(); dataSource = new HikariDataSource(hikariConfig);
if (hostname.contains(":")) {
String[] split = hostname.split(":");
pool.setServerName(split[0]); try (Connection connection = dataSource.getConnection()) {
try { try (Statement statement = connection.createStatement()) {
pool.setPortNumber(Integer.parseInt(split[1])); statement.executeUpdate(
} catch (NumberFormatException exception) { "CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " +
getLogger().info("{} is not a valid port! Will use the default port", split[1]); "`bedrockId` BINARY(16) NOT NULL , " +
} "`javaUniqueId` BINARY(16) NOT NULL , " +
} else { "`javaUsername` VARCHAR(16) NOT NULL , " +
pool.setServerName(hostname); " PRIMARY KEY (`bedrockId`) , " +
} " INDEX (`bedrockId`, `javaUniqueId`)" +
") ENGINE = InnoDB;"
pool.setUser(databaseconfig.getUsername()); );
pool.setPassword(databaseconfig.getPassword()); statement.executeUpdate(
pool.setDatabaseName(databaseconfig.getDatabase()); "CREATE TABLE IF NOT EXISTS `LinkedPlayersRequest` ( " +
pool.setMinPoolSize(2); "`javaUsername` VARCHAR(16) NOT NULL , `javaUniqueId` BINARY(16) NOT NULL , " +
pool.setMaxPoolSize(10); "`linkCode` VARCHAR(16) NOT NULL , " +
"`bedrockUsername` VARCHAR(16) NOT NULL ," +
try (Connection connection = pool.getConnection()) { "`requestTime` BIGINT NOT NULL , " +
try (Statement statement = connection.createStatement()) { " PRIMARY KEY (`javaUsername`), INDEX(`requestTime`)" +
statement.executeUpdate( " ) ENGINE = InnoDB;"
"CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " + );
"`bedrockId` BINARY(16) NOT NULL , " +
"`javaUniqueId` BINARY(16) NOT NULL , " +
"`javaUsername` VARCHAR(16) NOT NULL , " +
" PRIMARY KEY (`bedrockId`) , " +
" INDEX (`bedrockId`, `javaUniqueId`)" +
") ENGINE = InnoDB;"
);
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayersRequest` ( " +
"`javaUsername` VARCHAR(16) NOT NULL , `javaUniqueId` BINARY(16) NOT NULL , " +
"`linkCode` VARCHAR(16) NOT NULL , " +
"`bedrockUsername` VARCHAR(16) NOT NULL ," +
"`requestTime` BIGINT NOT NULL , " +
" PRIMARY KEY (`javaUsername`), INDEX(`requestTime`)" +
" ) ENGINE = InnoDB;"
);
}
}
getLogger().info("Connected to MySQL-like database.");
} catch (ClassNotFoundException exception) {
getLogger().error("The required class to load the MySQL database wasn't found");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
} }
}
getLogger().info("Connected to MySQL-like database.");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
} }
}
@Override @Override
public void stop() { public void stop() {
super.stop(); super.stop();
pool.close(); dataSource.close();
} }
@Override @Override
@NonNull @NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) { public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?" "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?"
)) { )) {
query.setBytes(1, uuidToBytes(bedrockId)); query.setBytes(1, uuidToBytes(bedrockId));
try (ResultSet result = query.executeQuery()) { try (ResultSet result = query.executeQuery()) {
if (!result.next()) { if (!result.next()) {
return null; return null;
}
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(result.getBytes("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
} catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
} }
}, getExecutorService()); String javaUsername = result.getString("javaUsername");
} UUID javaUniqueId = bytesToUUID(result.getBytes("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
@Override }
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(playerId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
try (ResultSet result = query.executeQuery()) {
return result.next();
}
}
} catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`javaUsername`=VALUES(`javaUsername`);"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
} }
} } catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override @Override
@NonNull @NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) { public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?" "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) { )) {
byte[] uuidBytes = uuidToBytes(javaId); byte[] uuidBytes = uuidToBytes(playerId);
query.setBytes(1, uuidBytes); query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes); query.setBytes(2, uuidBytes);
query.executeUpdate(); try (ResultSet result = query.executeQuery()) {
} return result.next();
} catch (SQLException exception) { }
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`linkCode`=VALUES(`linkCode`), " +
"`bedrockUsername`=VALUES(`bedrockUsername`), " +
"`requestTime`=VALUES(`requestTime`);"
)) {
query.setString(1, javaUsername);
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, linkCode);
query.setString(4, bedrockUsername);
query.setLong(5, Instant.now().getEpochSecond());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
} }
} } catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
private void removeLinkRequest(String javaUsername) { @Override
try (Connection connection = pool.getConnection()) { @NonNull
try (PreparedStatement query = connection.prepareStatement( public CompletableFuture<Void> linkPlayer(
"DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" @NonNull UUID bedrockId,
)) { @NonNull UUID javaId,
query.setString(1, javaUsername); @NonNull String javaUsername) {
query.executeUpdate(); return CompletableFuture.runAsync(
} () -> linkPlayer0(bedrockId, javaId, javaUsername),
} catch (SQLException exception) { getExecutorService());
getLogger().error("Error while cleaning up LinkRequest", exception); }
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`javaUsername`=VALUES(`javaUsername`);"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(javaId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
query.executeUpdate();
} }
} catch (SQLException exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername
) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername
) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`linkCode`=VALUES(`linkCode`), " +
"`bedrockUsername`=VALUES(`bedrockUsername`), " +
"`requestTime`=VALUES(`requestTime`);"
)) {
query.setString(1, javaUsername);
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, linkCode);
query.setString(4, bedrockUsername);
query.setLong(5, Instant.now().getEpochSecond());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
} }
}
@Override private void removeLinkRequest(String javaUsername) {
@NonNull try (Connection connection = dataSource.getConnection()) {
public CompletableFuture<LinkRequestResult> verifyLinkRequest( try (PreparedStatement query = connection.prepareStatement(
@NonNull UUID bedrockId, "DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
@NonNull String javaUsername, )) {
@NonNull String bedrockUsername, query.setString(1, javaUsername);
@NonNull String code) { query.executeUpdate();
return CompletableFuture.supplyAsync(() -> { }
LinkRequest request = getLinkRequest0(javaUsername); } catch (SQLException exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
} }
}
private LinkRequest getLinkRequest0(String javaUsername) { @Override
try (Connection connection = pool.getConnection()) { @NonNull
try (PreparedStatement query = connection.prepareStatement( public CompletableFuture<LinkRequestResult> verifyLinkRequest(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" @NonNull UUID bedrockId,
)) { @NonNull String javaUsername,
query.setString(1, javaUsername); @NonNull String bedrockUsername,
@NonNull String code
) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
try (ResultSet result = query.executeQuery()) { if (request == null || !isRequestedPlayer(request, bedrockId)) {
if (result.next()) { return LinkRequestResult.NO_LINK_REQUESTED;
UUID javaId = bytesToUUID(result.getBytes(2)); }
String linkCode = result.getString(3);
String bedrockUsername = result.getString(4); if (!request.getLinkCode().equals(code)) {
long requestTime = result.getLong(5); return LinkRequestResult.INVALID_CODE;
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername, }
requestTime);
} // link request can be removed. Doesn't matter if the request is expired or not
} removeLinkRequest(javaUsername);
}
} catch (SQLException exception) { if (request.isExpired(getVerifyLinkTimeout())) {
getLogger().error("Error while getLinkRequest", exception); return LinkRequestResult.REQUEST_EXPIRED;
throw new CompletionException("Error while getLinkRequest", exception); }
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
private LinkRequest getLinkRequest0(String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
try (ResultSet result = query.executeQuery()) {
if (result.next()) {
UUID javaId = bytesToUUID(result.getBytes(2));
String linkCode = result.getString(3);
String bedrockUsername = result.getString(4);
long requestTime = result.getLong(5);
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
} }
return null; }
} catch (SQLException exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
} }
return null;
}
public void cleanLinkRequests() { public void cleanLinkRequests() {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?" "DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?"
)) { )) {
query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout()); query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout());
query.executeUpdate(); query.executeUpdate();
} }
} catch (SQLException exception) { } catch (SQLException exception) {
getLogger().error("Error while cleaning up link requests", exception); getLogger().error("Error while cleaning up link requests", exception);
}
} }
}
private byte[] uuidToBytes(UUID uuid) { private byte[] uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16]; byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes) ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN) .order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits()) .putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits()); .putLong(uuid.getLeastSignificantBits());
return uuidBytes; return uuidBytes;
} }
private UUID bytesToUUID(byte[] uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong());
}
private UUID bytesToUUID(byte[] uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong());
}
} }

View File

@@ -29,8 +29,8 @@ import lombok.Getter;
@Getter @Getter
public class MysqlConfig implements DatabaseConfig { public class MysqlConfig implements DatabaseConfig {
private String hostname = "localhost"; private String hostname = "localhost";
private String database = "floodgate"; private String database = "floodgate";
private String username = "floodgate"; private String username = "floodgate";
private String password; private String password;
} }

View File

@@ -14,7 +14,19 @@ dependencyResolutionManagement {
} }
// Paper, Velocity // Paper, Velocity
maven("https://papermc.io/repo/repository/maven-public") // maven("https://repo.papermc.io/repository/maven-releases") {
// mavenContent { releasesOnly() }
// }
// maven("https://repo.papermc.io/repository/maven-snapshots") {
// mavenContent { snapshotsOnly() }
// }
maven("https://repo.papermc.io/repository/maven-public") {
content {
includeGroupByRegex(
"(io\\.papermc\\..*|com\\.destroystokyo\\..*|com\\.velocitypowered)"
)
}
}
// Spigot // Spigot
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
mavenContent { snapshotsOnly() } mavenContent { snapshotsOnly() }

View File

@@ -1,4 +1,4 @@
var velocityVersion = "3.0.1" var velocityVersion = "3.1.1"
var log4jVersion = "2.11.2" var log4jVersion = "2.11.2"
var gsonVersion = "2.8.8" var gsonVersion = "2.8.8"
var guavaVersion = "25.1-jre" var guavaVersion = "25.1-jre"