diff --git a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java similarity index 74% rename from core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java rename to core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java index b8febe29..618c9b27 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java +++ b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java @@ -32,6 +32,7 @@ import com.google.gson.JsonObject; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; +import com.google.inject.name.Named; import com.google.inject.name.Names; import java.io.IOException; import java.io.InputStream; @@ -42,18 +43,23 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.inject.Named; +import java.util.stream.Stream; +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.logger.FloodgateLogger; 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.InjectorHolder; import org.geysermc.floodgate.util.Utils; +@Listener @Singleton @SuppressWarnings("unchecked") -public final class PlayerLinkLoader { +public final class PlayerLinkHolder { @Inject private Injector injector; @Inject private FloodgateConfig config; @Inject private FloodgateLogger logger; @@ -62,30 +68,38 @@ public final class PlayerLinkLoader { @Named("dataDirectory") private Path dataDirectory; - @Nonnull + private URLClassLoader classLoader; + private PlayerLink instance; + + @NonNull public PlayerLink load() { + if (instance != null) { + return instance; + } + if (config == null) { throw new IllegalStateException("Config cannot be null!"); } - FloodgateConfig.PlayerLinkConfig lConfig = config.getPlayerLink(); - if (!lConfig.isEnabled()) { + PlayerLinkConfig linkConfig = config.getPlayerLink(); + if (!linkConfig.isEnabled()) { return new DisabledPlayerLink(); } List files; - try { - files = Files.list(dataDirectory) - .filter(path -> Files.isRegularFile(path) && path.toString().endsWith("jar")) + try (Stream list = Files.list(dataDirectory)) { + files = list + .filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".jar")) .collect(Collectors.toList()); } catch (IOException exception) { logger.error("Failed to list possible database implementations", exception); return new DisabledPlayerLink(); } - // we can skip the rest if global linking is enabled and no database implementations has been - // found, or when global linking is enabled and own player linking is disabled. - if (lConfig.isEnableGlobalLinking() && (files.isEmpty() || !lConfig.isEnableOwnLinking())) { + // we can skip the rest if global linking is enabled and no database implementations has + // been found, or when global linking is enabled and own player linking is disabled. + if (linkConfig.isEnableGlobalLinking() && + (files.isEmpty() || !linkConfig.isEnableOwnLinking())) { return injector.getInstance(GlobalPlayerLinking.class); } @@ -100,7 +114,7 @@ public final class PlayerLinkLoader { // We only want to load one database implementation if (files.size() > 1) { boolean found = false; - databaseName = lConfig.getType(); + databaseName = linkConfig.getType(); String expectedName = "floodgate-" + databaseName + "-database.jar"; for (Path path : files) { @@ -111,14 +125,18 @@ public final class PlayerLinkLoader { } 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(); } } else { String name = implementationPath.getFileName().toString(); if (!Utils.isValidDatabaseName(name)) { - logger.error("Found database {} but the name doesn't match {}", - name, Constants.DATABASE_NAME_FORMAT); + logger.error( + "Found database {} but the name doesn't match {}", + name, Constants.DATABASE_NAME_FORMAT + ); return new DisabledPlayerLink(); } 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 // to be able to load classes on the fly, but that doesn't matter anyway since Floodgate // doesn't support reloading - URLClassLoader classLoader = new URLClassLoader( + classLoader = new URLClassLoader( new URL[]{pluginUrl}, - PlayerLinkLoader.class.getClassLoader() + PlayerLinkHolder.class.getClassLoader() ); String mainClassName; - JsonObject linkConfig; - - try (InputStream linkConfigStream = - classLoader.getResourceAsStream("init.json")) { + JsonObject dbInitConfig; + try (InputStream linkConfigStream = classLoader.getResourceAsStream("init.json")) { requireNonNull(linkConfigStream, "Implementation should have an init file"); - linkConfig = new Gson().fromJson( + dbInitConfig = new Gson().fromJson( new InputStreamReader(linkConfigStream), JsonObject.class ); - mainClassName = linkConfig.get("mainClass").getAsString(); + mainClassName = dbInitConfig.get("mainClass").getAsString(); } Class mainClass = @@ -167,16 +183,16 @@ public final class PlayerLinkLoader { Names.named("databaseClassLoader")).toInstance(classLoader); binder.bind(JsonObject.class) .annotatedWith(Names.named("databaseInitData")) - .toInstance(linkConfig); + .toInstance(dbInitConfig); binder.bind(InjectorHolder.class) .toInstance(injectorHolder); }); injectorHolder.set(linkInjector); - PlayerLink instance = linkInjector.getInstance(mainClass); + instance = linkInjector.getInstance(mainClass); // we use our own internal PlayerLinking when global linking is enabled - if (lConfig.isEnableGlobalLinking()) { + if (linkConfig.isEnableGlobalLinking()) { GlobalPlayerLinking linking = linkInjector.getInstance(GlobalPlayerLinking.class); linking.setDatabaseImpl(instance); linking.load(); @@ -186,8 +202,10 @@ public final class PlayerLinkLoader { return instance; } } catch (ClassCastException exception) { - logger.error("The database implementation ({}) doesn't extend the PlayerLink class!", - implementationPath.getFileName().toString(), exception); + logger.error( + "The database implementation ({}) doesn't extend the PlayerLink class!", + implementationPath.getFileName().toString(), exception + ); return new DisabledPlayerLink(); } catch (Exception exception) { if (init) { @@ -198,4 +216,10 @@ public final class PlayerLinkLoader { return new DisabledPlayerLink(); } } + + @Subscribe + public void onShutdown(ShutdownEvent ignored) throws Exception { + instance.stop(); + classLoader.close(); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java index 72cfd4bd..2ce7341b 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -55,7 +55,7 @@ import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.event.EventBus; import org.geysermc.floodgate.event.util.ListenerAnnotationMatcher; 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.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.pluginmessage.PluginMessageManager; @@ -102,7 +102,7 @@ public class CommonModule extends AbstractModule { @Provides @Singleton - public PlayerLink playerLink(PlayerLinkLoader linkLoader) { + public PlayerLink playerLink(PlayerLinkHolder linkLoader) { return linkLoader.load(); } diff --git a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java index 474d6e7a..4ef585b7 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java @@ -26,13 +26,13 @@ package org.geysermc.floodgate.util; import com.google.inject.Inject; +import com.google.inject.name.Named; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.inject.Named; import org.bstats.MetricsBase; import org.bstats.charts.DrilldownPie; import org.bstats.charts.SimplePie; diff --git a/database/mysql/.editorconfig b/database/mysql/.editorconfig new file mode 100644 index 00000000..1f0feacf --- /dev/null +++ b/database/mysql/.editorconfig @@ -0,0 +1,4 @@ +[*] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 \ No newline at end of file diff --git a/database/mysql/build.gradle.kts b/database/mysql/build.gradle.kts index cc59658b..3b6f25a3 100644 --- a/database/mysql/build.gradle.kts +++ b/database/mysql/build.gradle.kts @@ -1,8 +1,10 @@ -val mariadbClientVersion = "2.7.4" +val mysqlClientVersion = "8.0.30" +val hikariVersion = "4.0.3" dependencies { 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" diff --git a/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java b/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java index 16797c6b..60c19716 100644 --- a/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java +++ b/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java @@ -25,6 +25,8 @@ package org.geysermc.floodgate.database; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import java.nio.ByteBuffer; import java.nio.ByteOrder; 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.LinkRequestImpl; import org.geysermc.floodgate.util.LinkedPlayer; -import org.mariadb.jdbc.MariaDbPoolDataSource; public class MysqlDatabase extends CommonPlayerLink { - private MariaDbPoolDataSource pool; + private HikariDataSource dataSource; - @Override - public void load() { - getLogger().info("Connecting to a MySQL-like database..."); - try { - Class.forName("org.mariadb.jdbc.Driver"); - MysqlConfig databaseconfig = getConfig(MysqlConfig.class); + @Override + public void load() { + getLogger().info("Connecting to a MySQL-like database..."); + try { + MysqlConfig config = 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(); - if (hostname.contains(":")) { - String[] split = hostname.split(":"); + dataSource = new HikariDataSource(hikariConfig); - pool.setServerName(split[0]); - try { - pool.setPortNumber(Integer.parseInt(split[1])); - } catch (NumberFormatException exception) { - getLogger().info("{} is not a valid port! Will use the default port", split[1]); - } - } else { - pool.setServerName(hostname); - } - - pool.setUser(databaseconfig.getUsername()); - pool.setPassword(databaseconfig.getPassword()); - pool.setDatabaseName(databaseconfig.getDatabase()); - pool.setMinPoolSize(2); - pool.setMaxPoolSize(10); - - try (Connection connection = pool.getConnection()) { - try (Statement statement = connection.createStatement()) { - statement.executeUpdate( - "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); + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( + "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 (SQLException exception) { + getLogger().error("Error while loading database", exception); } + } - @Override - public void stop() { - super.stop(); - pool.close(); - } + @Override + public void stop() { + super.stop(); + dataSource.close(); + } - @Override - @NonNull - public CompletableFuture getLinkedPlayer(@NonNull UUID bedrockId) { - return CompletableFuture.supplyAsync(() -> { - try (Connection connection = pool.getConnection()) { - try (PreparedStatement query = connection.prepareStatement( - "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?" - )) { - query.setBytes(1, uuidToBytes(bedrockId)); - try (ResultSet result = query.executeQuery()) { - if (!result.next()) { - 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); + @Override + @NonNull + public CompletableFuture getLinkedPlayer(@NonNull UUID bedrockId) { + return CompletableFuture.supplyAsync(() -> { + try (Connection connection = dataSource.getConnection()) { + try (PreparedStatement query = connection.prepareStatement( + "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?" + )) { + query.setBytes(1, uuidToBytes(bedrockId)); + try (ResultSet result = query.executeQuery()) { + if (!result.next()) { + return null; } - }, getExecutorService()); - } - - @Override - @NonNull - public CompletableFuture 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 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); + 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()); + } - @Override - @NonNull - public CompletableFuture unlinkPlayer(@NonNull UUID javaId) { - return CompletableFuture.runAsync(() -> { - try (Connection connection = pool.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 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); + @Override + @NonNull + public CompletableFuture isLinkedPlayer(@NonNull UUID playerId) { + return CompletableFuture.supplyAsync(() -> { + try (Connection connection = dataSource.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()); + } - private void removeLinkRequest(String javaUsername) { - try (Connection connection = pool.getConnection()) { - try (PreparedStatement query = connection.prepareStatement( - "DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" - )) { - query.setString(1, javaUsername); - query.executeUpdate(); - } - } catch (SQLException exception) { - getLogger().error("Error while cleaning up LinkRequest", exception); + @Override + @NonNull + public CompletableFuture 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 = 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 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 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 - @NonNull - public CompletableFuture verifyLinkRequest( - @NonNull UUID bedrockId, - @NonNull String javaUsername, - @NonNull String bedrockUsername, - @NonNull String code) { - return CompletableFuture.supplyAsync(() -> { - LinkRequest request = getLinkRequest0(javaUsername); - - 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 void removeLinkRequest(String javaUsername) { + try (Connection connection = dataSource.getConnection()) { + try (PreparedStatement query = connection.prepareStatement( + "DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" + )) { + query.setString(1, javaUsername); + query.executeUpdate(); + } + } catch (SQLException exception) { + getLogger().error("Error while cleaning up LinkRequest", exception); } + } - private LinkRequest getLinkRequest0(String javaUsername) { - try (Connection connection = pool.getConnection()) { - try (PreparedStatement query = connection.prepareStatement( - "SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" - )) { - query.setString(1, javaUsername); + @Override + @NonNull + public CompletableFuture verifyLinkRequest( + @NonNull UUID bedrockId, + @NonNull String javaUsername, + @NonNull String bedrockUsername, + @NonNull String code + ) { + return CompletableFuture.supplyAsync(() -> { + LinkRequest request = getLinkRequest0(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); - } - } - } - } catch (SQLException exception) { - getLogger().error("Error while getLinkRequest", exception); - throw new CompletionException("Error while getLinkRequest", 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) { + 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() { - try (Connection connection = pool.getConnection()) { - try (PreparedStatement query = connection.prepareStatement( - "DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?" - )) { - query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout()); - query.executeUpdate(); - } - } catch (SQLException exception) { - getLogger().error("Error while cleaning up link requests", exception); - } + public void cleanLinkRequests() { + try (Connection connection = dataSource.getConnection()) { + try (PreparedStatement query = connection.prepareStatement( + "DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?" + )) { + query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout()); + query.executeUpdate(); + } + } catch (SQLException exception) { + getLogger().error("Error while cleaning up link requests", exception); } + } - private byte[] uuidToBytes(UUID uuid) { - byte[] uuidBytes = new byte[16]; - ByteBuffer.wrap(uuidBytes) - .order(ByteOrder.BIG_ENDIAN) - .putLong(uuid.getMostSignificantBits()) - .putLong(uuid.getLeastSignificantBits()); - return uuidBytes; - } - - private UUID bytesToUUID(byte[] uuidBytes) { - ByteBuffer buf = ByteBuffer.wrap(uuidBytes); - return new UUID(buf.getLong(), buf.getLong()); - } + private byte[] uuidToBytes(UUID uuid) { + byte[] uuidBytes = new byte[16]; + ByteBuffer.wrap(uuidBytes) + .order(ByteOrder.BIG_ENDIAN) + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()); + return uuidBytes; + } + private UUID bytesToUUID(byte[] uuidBytes) { + ByteBuffer buf = ByteBuffer.wrap(uuidBytes); + return new UUID(buf.getLong(), buf.getLong()); + } } diff --git a/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java b/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java index 0a2e0c26..b85748e7 100644 --- a/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java +++ b/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java @@ -29,8 +29,8 @@ import lombok.Getter; @Getter public class MysqlConfig implements DatabaseConfig { - private String hostname = "localhost"; - private String database = "floodgate"; - private String username = "floodgate"; - private String password; + private String hostname = "localhost"; + private String database = "floodgate"; + private String username = "floodgate"; + private String password; } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4a44a9d9..bcd33b65 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,19 @@ dependencyResolutionManagement { } // 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 maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { mavenContent { snapshotsOnly() } diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index 2ddcfae5..5cd7064a 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -1,4 +1,4 @@ -var velocityVersion = "3.0.1" +var velocityVersion = "3.1.1" var log4jVersion = "2.11.2" var gsonVersion = "2.8.8" var guavaVersion = "25.1-jre"