1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-04 15:31:48 +00:00

Gradle 8, platform isolation, bundled libraries, work on local linking

This commit is contained in:
Tim203
2023-04-24 00:18:03 +02:00
parent ef7251b933
commit 184389f11f
61 changed files with 753 additions and 1119 deletions

20
database/build.gradle.kts Normal file
View File

@@ -0,0 +1,20 @@
plugins {
id("floodgate.shadow-conventions")
id("io.micronaut.library")
id("floodgate.dependency-hash")
}
configurations.runtimeClasspath.get()
.exclude("org.slf4j", "slf4j-api")
.exclude("javax.validation", "validation-api")
.exclude("io.micronaut", "micronaut-aop")
.exclude("io.micronaut", "micronaut-core")
.exclude("io.micronaut", "micronaut-runtime")
.exclude("io.micronaut", "micronaut-inject")
.exclude("io.micronaut", "micronaut-context")
dependencies {
implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("com.h2database:h2")
}

View File

@@ -1,11 +0,0 @@
val mongoClientVersion = "4.4.1"
dependencies {
provided(projects.core)
implementation("org.mongodb", "mongodb-driver-sync" , mongoClientVersion)
}
description = "The Floodgate database extension for MongoDB"
relocate("com.mongodb")
relocate("org.bson")

View File

@@ -1,343 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.UpdateOptions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.internal.Base64;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.database.config.MongoConfig;
import org.geysermc.floodgate.util.LinkedPlayer;
public class MongoDbDatabase extends CommonPlayerLink {
private MongoClient client;
private MongoDatabase database;
private MongoCollection<Document> linkedPlayer;
private MongoCollection<Document> linkedPlayerRequests;
@Override
public void load() {
getLogger().info("Connecting to MongoDB database...");
try {
MongoConfig databaseConfig = getConfig(MongoConfig.class);
MongoClientSettings.Builder settings = MongoClientSettings.builder();
settings.applyToConnectionPoolSettings(builder -> {
builder.maxSize(10);
builder.minSize(2);
});
if (databaseConfig.getMongouri().isEmpty()) {
settings.credential(
MongoCredential.createCredential(
databaseConfig.getUsername(),
databaseConfig.getDatabase(),
databaseConfig.getPassword().toCharArray()
)
);
} else {
settings.applyConnectionString(new ConnectionString(databaseConfig.getMongouri()));
}
client = MongoClients.create(settings.build());
database = client.getDatabase(databaseConfig.getDatabase());
linkedPlayer = database.getCollection("LinkedPlayers");
if (collectionNotExists("LinkedPlayers")) {
database.createCollection("LinkedPlayers");
linkedPlayer.createIndex(new Document("bedrockId", 1),
new IndexOptions().unique(true)); // primary key equivalent
linkedPlayer.createIndex(Indexes.ascending("javaUniqueId"));
}
linkedPlayerRequests = database.getCollection("LinkedPlayerRequests");
if (collectionNotExists("LinkedPlayerRequests")) {
database.createCollection("LinkedPlayerRequests");
linkedPlayerRequests.createIndex(new Document("bedrockId", 1),
new IndexOptions().unique(true)); // primary key equivalent
linkedPlayerRequests.createIndex(Indexes.ascending("requestTime"));
}
getLogger().info("Connected to MongoDB database.");
} catch (Exception exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
client.close();
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try {
Bson filter = Filters.eq("bedrockId", uuidToBytes(bedrockId));
try (MongoCursor<Document> cursor = linkedPlayer.find(filter).cursor()) {
if (cursor.hasNext()) {
Document document = cursor.next();
String javaUsername = document.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(document.getString("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
return null;
} catch (Exception exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try {
String uuidBytes = uuidToBytes(playerId);
Bson filter = Filters.or(
Filters.eq("bedrockId", uuidBytes),
Filters.eq("javaUniqueId", uuidBytes)
);
try (MongoCursor<Document> cursor = linkedPlayer.find(filter).cursor()) {
return cursor.hasNext();
}
} catch (Exception 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 {
Bson filter = Filters.eq("javaUsername", javaUsername);
Document create = new Document("bedrockId", uuidToBytes(bedrockId))
.append("javaUniqueId", uuidToBytes(javaId))
.append("javaUsername", javaUsername);
Document update = new Document("$set", create);
linkedPlayer.updateOne(filter, update, new UpdateOptions().upsert(true));
// The upsert option will create a new document if the filter doesn't match anything.
// Or will update the document if it does match.
} catch (Exception 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 {
String uuidBytes = uuidToBytes(javaId);
Bson filter = Filters.and(
Filters.eq("javaUniqueId", uuidBytes),
Filters.eq("bedrockId", uuidBytes)
);
linkedPlayer.deleteMany(filter);
} catch (Exception 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 {
Bson filter = Filters.eq("javaUsername", javaUsername);
Document create = new Document("javaUsername", javaUsername)
.append("javaUniqueId", uuidToBytes(javaId))
.append("linkCode", linkCode)
.append("bedrockUsername", bedrockUsername)
.append("requestTime", Instant.now().getEpochSecond());
Document update = new Document("$set", create);
linkedPlayerRequests.updateOne(filter, update, new UpdateOptions().upsert(true));
// The upsert option will create a new document if the filter doesn't match anything.
// Or will update the document if it does match.
} catch (Exception exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
private void removeLinkRequest(String javaUsername) {
try {
Document filter = new Document("javaUsername", javaUsername);
linkedPlayerRequests.deleteMany(filter);
} catch (Exception exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> 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 LinkRequest getLinkRequest0(String javaUsername) {
try {
Bson filter = Filters.eq("javaUsername", javaUsername);
try (MongoCursor<Document> cursor = linkedPlayerRequests.find(filter).cursor()) {
if (cursor.hasNext()) {
Document document = cursor.next();
UUID javaId = bytesToUUID(document.getString("javaUniqueId"));
String linkCode = document.getString("linkCode");
String bedrockUsername = document.getString("bedrockUsername");
long requestTime = document.getLong("requestTime");
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
}
} catch (Exception exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
}
return null;
}
public void cleanLinkRequests() {
try {
Document filter = new Document("requestTime",
new Document("$lt", Instant.now().getEpochSecond() - getVerifyLinkTimeout()));
linkedPlayerRequests.deleteMany(filter);
} catch (Exception exception) {
getLogger().error("Error while cleaning up link requests", exception);
}
}
private String uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return Base64.encode(uuidBytes);
}
private UUID bytesToUUID(String uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(Base64.decode(uuidBytes));
return new UUID(buf.getLong(), buf.getLong());
}
public boolean collectionNotExists(final String collectionName) {
return !database.listCollectionNames().into(new ArrayList<>()).contains(collectionName);
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database.config;
import lombok.Getter;
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
@Getter
public class MongoConfig implements DatabaseConfig {
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
private String mongouri = "";
}

View File

@@ -1,4 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.MongoDbDatabase",
"config": "mongo.yml"
}

View File

@@ -1,5 +0,0 @@
hostname: "localhost"
database: "floodgate"
username: "floodgate"
password: ""
mongouri: ""

View File

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

View File

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

View File

@@ -1,340 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
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;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.database.config.MysqlConfig;
import org.geysermc.floodgate.util.LinkedPlayer;
public class MysqlDatabase extends CommonPlayerLink {
private HikariDataSource dataSource;
@Override
public void load() {
getLogger().info("Connecting to a MySQL-like database...");
try {
MysqlConfig config = getConfig(MysqlConfig.class);
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);
dataSource = new HikariDataSource(hikariConfig);
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();
dataSource.close();
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> 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;
}
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<Boolean> 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());
}
@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 = 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);
}
}
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);
}
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> 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 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);
}
}
}
} catch (SQLException exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
}
return null;
}
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());
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database.config;
import lombok.Getter;
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
@Getter
public class MysqlConfig implements DatabaseConfig {
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
}

View File

@@ -1,4 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.MysqlDatabase",
"config": "mysql.yml"
}

View File

@@ -1,4 +0,0 @@
hostname: "localhost"
database: "floodgate"
username: "floodgate"
password: ""

View File

@@ -1,8 +0,0 @@
val sqliteJdbcVersion = "3.36.0.3"
dependencies {
provided(projects.core)
implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion)
}
description = "The Floodgate database extension for SQLite"

View File

@@ -1,217 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.database;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import javax.inject.Inject;
import javax.inject.Named;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.LinkRequestImpl;
import org.geysermc.floodgate.util.LinkedPlayer;
public class SqliteDatabase extends CommonPlayerLink {
private final Map<String, LinkRequest> activeLinkRequests = new HashMap<>();
private Connection connection;
/* These are DELIBERATELY javax imports so Guice relocations can't break it */
@Inject
@Named("dataDirectory")
private Path dataDirectory;
@Override
public void load() {
Path databasePath = dataDirectory.resolve("linked-players.db");
try {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath);
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(
"create table if not exists LinkedPlayers (bedrockId string, javaUniqueId string, javaUsername string)"
);
}
} catch (ClassNotFoundException exception) {
getLogger().error("The required class to load the SQLite database wasn't found");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
try {
connection.close();
} catch (SQLException exception) {
getLogger().error("Error while closing database connection", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try (PreparedStatement query = connection.prepareStatement(
"select * from LinkedPlayers where bedrockId = ?")) {
query.setString(1, bedrockId.toString());
try (ResultSet result = query.executeQuery()) {
if (!result.next()) {
return null;
}
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = UUID.fromString(result.getString("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<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (PreparedStatement query = connection.prepareStatement(
"select javaUniqueId from LinkedPlayers where bedrockId = ? or javaUniqueId = ?")) {
query.setString(1, playerId.toString());
query.setString(2, playerId.toString());
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 username) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, username),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String username) {
try (PreparedStatement query =
connection.prepareStatement("insert into LinkedPlayers values(?, ?, ?)")) {
query.setString(1, bedrockId.toString());
query.setString(2, javaId.toString());
query.setString(3, username);
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 (PreparedStatement query = connection.prepareStatement(
"delete from LinkedPlayers where javaUniqueId = ? or bedrockId = ?")) {
query.setString(1, javaId.toString());
query.setString(2, javaId.toString());
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(() -> {
LinkRequest request =
new LinkRequestImpl(javaUsername, javaId, createCode(), bedrockUsername);
activeLinkRequests.put(javaUsername, request);
return request.getLinkCode();
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = activeLinkRequests.get(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
activeLinkRequests.remove(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
}

View File

@@ -1,3 +0,0 @@
{
"mainClass": "org.geysermc.floodgate.database.SqliteDatabase"
}