1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-20 15:29:20 +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

View File

@@ -1,9 +0,0 @@
plugins {
id("floodgate.shadow-conventions")
}
tasks {
shadowJar {
archiveBaseName.set(archiveBaseName.get() + "-database")
}
}

View File

@@ -17,10 +17,9 @@ tasks.register("listDependencyInfo") {
tasks.register("writeDependencyInfo") { tasks.register("writeDependencyInfo") {
doLast { doLast {
Files.writeString( val path = dependenciesInfoFile()
dependenciesInfoFile(), Files.createDirectories(path.parent)
dependenciesToString(listDependencies()) Files.writeString(path, dependenciesToString(listDependencies()))
)
} }
} }
@@ -34,9 +33,9 @@ tasks.register("checkDependencyInfoFile") {
} }
} }
tasks.named("build") { //tasks.named("build") {
dependsOn("checkDependencyHashesFile") // dependsOn("checkDependencyInfoFile")
} //}
fun dependenciesToString(dependencies: Collection<Dependency>): String { fun dependenciesToString(dependencies: Collection<Dependency>): String {
val builder = StringBuilder() val builder = StringBuilder()

View File

@@ -10,6 +10,7 @@ tasks {
archiveClassifier.set("unshaded") archiveClassifier.set("unshaded")
from(project.rootProject.file("LICENSE")) from(project.rootProject.file("LICENSE"))
} }
val shadowJar = named<ShadowJar>("shadowJar") { val shadowJar = named<ShadowJar>("shadowJar") {
archiveBaseName.set("floodgate-${project.name}") archiveBaseName.set("floodgate-${project.name}")
archiveVersion.set("") archiveVersion.set("")
@@ -39,6 +40,7 @@ tasks {
destinationDirectory.set(file(destinationDir)) destinationDirectory.set(file(destinationDir))
} }
} }
named("build") { named("build") {
dependsOn(shadowJar) dependsOn(shadowJar)
} }

View File

@@ -1,7 +1,8 @@
plugins { plugins {
`java-library` `java-library`
id("floodgate.build-logic") id("floodgate.build-logic")
id("io.freefair.lombok") version "6.3.0" apply false id("io.freefair.lombok") version "8.0.1" apply false
id("io.micronaut.library") version "3.7.8" apply false
} }
allprojects { allprojects {
@@ -16,7 +17,7 @@ val deployProjects = setOf(
projects.core, projects.core,
projects.bungee, projects.bungee,
projects.spigot, projects.spigot,
projects.velocity, projects.velocityIsolated,
projects.universal projects.universal
).map { it.dependencyProject } ).map { it.dependencyProject }
@@ -30,13 +31,6 @@ subprojects {
plugin("floodgate.build-logic") plugin("floodgate.build-logic")
} }
val relativePath = projectDir.relativeTo(rootProject.projectDir).path
if (relativePath.startsWith("database" + File.separator)) {
group = rootProject.group as String + ".database"
plugins.apply("floodgate.database-conventions")
}
when (this) { when (this) {
in deployProjects -> plugins.apply("floodgate.publish-conventions") in deployProjects -> plugins.apply("floodgate.publish-conventions")
else -> plugins.apply("floodgate.base-conventions") else -> plugins.apply("floodgate.base-conventions")

View File

@@ -1,11 +1,12 @@
plugins { plugins {
id("floodgate.generate-templates") id("floodgate.generate-templates")
id("floodgate.dependency-hash") id("floodgate.dependency-hash")
id("io.micronaut.library") version "3.7.4" id("io.micronaut.library")
} }
dependencies { dependencies {
api(projects.api) api(projects.api)
compileOnlyApi(projects.isolation)
api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion) api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion)
api("com.google.inject", "guice", Versions.guiceVersion) api("com.google.inject", "guice", Versions.guiceVersion)
@@ -23,9 +24,17 @@ dependencies {
api("io.micronaut", "micronaut-context") api("io.micronaut", "micronaut-context")
annotationProcessor("io.micronaut.data:micronaut-data-processor") annotationProcessor("io.micronaut.data:micronaut-data-processor")
implementation("io.micronaut.data:micronaut-data-hibernate-jpa") implementation("io.micronaut.data:micronaut-data-model")
implementation("io.micronaut.sql:micronaut-jdbc-hikari") implementation("jakarta.persistence:jakarta.persistence-api:2.2.3")
runtimeOnly("com.h2database:h2")
// compileOnlyApi("io.micronaut.data:micronaut-data-hibernate-jpa")
//todo add these as libs
//compileOnly("io.micronaut.data:micronaut-data-hibernate-jpa")
//compileOnly("io.micronaut.sql:micronaut-jdbc-hikari")
//compileOnly("com.h2database:h2")
//implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
//implementation("io.micronaut.sql:micronaut-jdbc-hikari")
//runtimeOnly("com.h2database:h2")
// annotationProcessor("io.micronaut.data:micronaut-data-document-processor") // annotationProcessor("io.micronaut.data:micronaut-data-document-processor")
// compileOnly("io.micronaut.data:micronaut-data-mongodb") // compileOnly("io.micronaut.data:micronaut-data-mongodb")

View File

@@ -26,7 +26,6 @@
package org.geysermc.floodgate.core; package org.geysermc.floodgate.core;
import io.micronaut.context.ApplicationContext; import io.micronaut.context.ApplicationContext;
import java.nio.file.Paths;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
@@ -43,18 +42,23 @@ import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
import org.geysermc.floodgate.core.event.EventBus; import org.geysermc.floodgate.core.event.EventBus;
import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent; import org.geysermc.floodgate.core.event.lifecycle.PostEnableEvent;
import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.core.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.core.library.Library;
import org.geysermc.floodgate.core.library.LibraryManager;
import org.geysermc.floodgate.core.library.Repository;
import org.geysermc.floodgate.core.library.info.DependencyInfoLoader;
import org.geysermc.floodgate.core.util.EagerSingleton; import org.geysermc.floodgate.core.util.EagerSingleton;
import org.geysermc.floodgate.isolation.library.Library;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.library.Repository;
import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader;
public abstract class FloodgatePlatform { public abstract class FloodgatePlatform {
private static final UUID KEY = UUID.randomUUID(); private static final UUID KEY = UUID.randomUUID();
private final LibraryManager manager;
private ApplicationContext context; private ApplicationContext context;
private PlatformInjector injector; private PlatformInjector injector;
protected FloodgatePlatform(LibraryManager manager) {
this.manager = manager;
}
protected void onContextCreated(ApplicationContext context) { protected void onContextCreated(ApplicationContext context) {
} }
@@ -65,19 +69,28 @@ public abstract class FloodgatePlatform {
getClass().getClassLoader().getResource("dependencyInfo.txt") getClass().getClassLoader().getResource("dependencyInfo.txt")
); );
new LibraryManager(ClassLoader.getSystemClassLoader().getParent(), Paths.get("./libs")) manager
// .addLibrary(
// Library.builder(infoLoader)
// .id("guava")
// .repository(Repository.MAVEN_CENTRAL)
// .groupId("com.google.guava")
// .artifactId("guava")
// .build()
// )
.addLibrary( .addLibrary(
Library.builder(infoLoader) Library.builder()
.id("guava") .id("local-linking")
.repository(Repository.MAVEN_CENTRAL) .repository(Repository.OPEN_COLLAB)
.groupId("com.google.guava") .groupId("org.geysermc.floodgate")
.artifactId("guava") .artifactId("database")
.version("a-version")
.build() .build()
) )
.apply(); .apply();
//noinspection unchecked //noinspection unchecked
context = ApplicationContext.builder() context = ApplicationContext.builder(manager.classLoader())
.properties(Map.of( .properties(Map.of(
"platform.proxy", isProxy() "platform.proxy", isProxy()
)) ))
@@ -140,8 +153,6 @@ public abstract class FloodgatePlatform {
throw new RuntimeException("Failed to inject the packet listener!", exception); throw new RuntimeException("Failed to inject the packet listener!", exception);
} }
// this.guice = guice.createChildInjector(new PostEnableModules(postEnableStageModules()));
context.getBean(EventBus.class).fire(new PostEnableEvent()); context.getBean(EventBus.class).fire(new PostEnableEvent());
} }

View File

@@ -30,12 +30,9 @@ import com.google.gson.JsonObject;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Named; import jakarta.inject.Named;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -97,26 +94,6 @@ public class HttpClient {
return connection; return connection;
} }
@NonNull
public HttpResponse<byte[]> getRawData(String urlString) throws IOException {
HttpURLConnection connection = request(urlString);
try (InputStream inputStream = connection.getInputStream()) {
int responseCode = connection.getResponseCode();
byte[] buffer = new byte[8196];
int len;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer, 0, len);
}
return new HttpResponse<>(responseCode, outputStream.toByteArray());
}
} catch (SocketTimeoutException | NullPointerException exception) {
return new HttpResponse<>(-1, null);
}
}
@NonNull @NonNull
private <T> HttpResponse<T> readResponse(HttpURLConnection connection, Class<T> clazz) { private <T> HttpResponse<T> readResponse(HttpURLConnection connection, Class<T> clazz) {
InputStreamReader streamReader = createReader(connection); InputStreamReader streamReader = createReader(connection);

View File

@@ -34,7 +34,6 @@ import java.io.InputStreamReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.UUID; import java.util.UUID;
@@ -134,8 +133,4 @@ public class Utils {
future.completeExceptionally(ex); future.completeExceptionally(ex);
return future; return future;
} }
public static String base64Encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
} }

View File

@@ -1,9 +1,10 @@
org.geysermc.configutils:configutils:1.0-SNAPSHOT:Xyh6oCOT1RwP5hw1gRM77M0mOJ86nNPf4J9Hs9PL4Ek= org.geysermc.configutils:configutils:2.0-SNAPSHOT:HiYf2qgXat5WOQOScSXPk8FBIKZDWYowCJPVJ+Uuv+0=
com.google.inject:guice:5.1.0:QTDlC/rEgJnIYPDZA7kYYMgaJJyQ84JF+P7Vj8gXvCY= com.google.inject:guice:5.1.0:QTDlC/rEgJnIYPDZA7kYYMgaJJyQ84JF+P7Vj8gXvCY=
com.nukkitx.fastutil:fastutil-short-object-maps:8.5.3:nTF5o2PCLMeBm8gaPeU4Q41FG2CNpmC9PxeJVHZot+U= com.nukkitx.fastutil:fastutil-short-object-maps:8.5.3:nTF5o2PCLMeBm8gaPeU4Q41FG2CNpmC9PxeJVHZot+U=
com.nukkitx.fastutil:fastutil-int-object-maps:8.5.3:Z0iKxf9OmoIcQtmqV9Lo9Z39XSpBXKjL8tavAdVCKBY= com.nukkitx.fastutil:fastutil-int-object-maps:8.5.3:Z0iKxf9OmoIcQtmqV9Lo9Z39XSpBXKjL8tavAdVCKBY=
org.java-websocket:Java-WebSocket:1.5.2:/4adGYqNxdAJZzkuGHrtqTuDO3zdO8zvs9f7WDQJi4U= org.java-websocket:Java-WebSocket:1.5.2:/4adGYqNxdAJZzkuGHrtqTuDO3zdO8zvs9f7WDQJi4U=
cloud.commandframework:cloud-core:1.5.0:+hDJlLwU84P6sf0gkDq+xStIqAHhMXUCI+Wb8VsD1Zk= cloud.commandframework:cloud-core:1.5.0:+hDJlLwU84P6sf0gkDq+xStIqAHhMXUCI+Wb8VsD1Zk=
io.micronaut.data:micronaut-data-model:3.9.6:Hdt2G2LRuAtIs1K2DayRz7OoiqZcbIfCUEstVIpQnUE=
io.micronaut.sql:micronaut-jdbc-hikari:4.7.2:w2N1dH4vY/QjjQGsViMOwgVYxAnZNGgMm1doCa19wOA= io.micronaut.sql:micronaut-jdbc-hikari:4.7.2:w2N1dH4vY/QjjQGsViMOwgVYxAnZNGgMm1doCa19wOA=
io.micronaut.data:micronaut-data-hibernate-jpa:3.9.6:kFYugtQeXfLjqUCWrf3MFFDoUdYhXrPQmSh0pu2Zxzo= io.micronaut.data:micronaut-data-hibernate-jpa:3.9.6:kFYugtQeXfLjqUCWrf3MFFDoUdYhXrPQmSh0pu2Zxzo=
io.micronaut:micronaut-context:3.8.7:ir020Tq3UavnPZhXOUNHCl/XdGcVrOzhM1lU5NT6YEc= io.micronaut:micronaut-context:3.8.7:ir020Tq3UavnPZhXOUNHCl/XdGcVrOzhM1lU5NT6YEc=

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,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,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"
}

View File

@@ -3,4 +3,4 @@ org.gradle.caching=true
org.gradle.parallel=true org.gradle.parallel=true
version=2.2.2-SNAPSHOT version=2.2.2-SNAPSHOT
micronautVersion=3.8.7 micronautVersion=3.9.0

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

28
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright <EFBFBD> 2015-2021 the original authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
# * functions; # * functions;
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>, # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>; # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>; # * compound commands having a testable exit status, especially «case»;
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>. # * various built-in commands including «command», «set», and «ulimit».
# #
# Important for patching: # Important for patching:
# #
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@@ -0,0 +1,7 @@
plugins {
id("floodgate.base-conventions")
}
dependencies {
api(projects.api)
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library; package org.geysermc.floodgate.isolation.library;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
@@ -32,24 +32,26 @@ import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Objects; import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.core.library.info.DependencyInfo; import org.geysermc.floodgate.isolation.library.info.DependencyInfo;
import org.geysermc.floodgate.core.library.info.DependencyInfoLoader; import org.geysermc.floodgate.isolation.library.info.DependencyInfoLoader;
import org.geysermc.floodgate.core.util.Utils;
public record Library( public record Library(
@NonNull String id, @NonNull String id,
@NonNull Repository repository, @NonNull Repository repository,
@NonNull String groupId, String groupId,
@NonNull String artifactId, @NonNull String artifactId,
@NonNull String version, String version,
byte[] sha256 byte[] sha256,
boolean forceOverride
) { ) {
public Library { public Library {
Objects.requireNonNull(id); Objects.requireNonNull(id);
Objects.requireNonNull(repository); Objects.requireNonNull(repository);
Objects.requireNonNull(groupId);
Objects.requireNonNull(artifactId); Objects.requireNonNull(artifactId);
Objects.requireNonNull(version); if (repository != Repository.BUNDLED) {
Objects.requireNonNull(groupId);
Objects.requireNonNull(version);
}
} }
public String path() { public String path() {
@@ -71,10 +73,18 @@ public record Library(
} }
public Path filePath() { public Path filePath() {
return Path.of(id(), id() + "-" + version() + ".jar"); var fileName = id();
if (!forceOverride) {
fileName += "-" + version();
}
return Path.of(id(), fileName + ".jar");
} }
public void validateChecksum(byte[] data) { public void validateChecksum(byte[] data) {
if (sha256 == null) {
return;
}
byte[] hash; byte[] hash;
try { try {
hash = MessageDigest.getInstance("SHA-256").digest(data); hash = MessageDigest.getInstance("SHA-256").digest(data);
@@ -88,8 +98,8 @@ public record Library(
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"Downloaded library hash (%s) didn't match expected hash (%s)!", "Downloaded library hash (%s) didn't match expected hash (%s)!",
Utils.base64Encode(data), Base64.getEncoder().encodeToString(data),
Utils.base64Encode(sha256()) Base64.getEncoder().encodeToString(sha256())
)); ));
} }
@@ -103,6 +113,7 @@ public record Library(
private String version; private String version;
private byte[] sha256; private byte[] sha256;
private boolean forceOverride;
LibraryBuilder(DependencyInfoLoader info) { LibraryBuilder(DependencyInfoLoader info) {
this.dependencyInfoLoader = info; this.dependencyInfoLoader = info;
@@ -142,18 +153,18 @@ public record Library(
return sha256(Base64.getDecoder().decode(sha256)); return sha256(Base64.getDecoder().decode(sha256));
} }
public LibraryBuilder forceOverride(boolean forceOverride) {
this.forceOverride = forceOverride;
return this;
}
public Library build() { public Library build() {
if (version == null || sha256 == null) { if (dependencyInfoLoader != null) {
if (dependencyInfoLoader == null) { DependencyInfo info = dependencyInfoLoader.byCombinedId(groupId, artifactId);
throw new IllegalStateException( version = info.version();
"Provide a version and hash or provide a DependencyInfoLoader" sha256 = info.sha256();
);
}
DependencyInfo dependencyInfo = dependencyInfoLoader.byCombinedId(groupId, artifactId);
version = dependencyInfo.version();
sha256 = dependencyInfo.sha256();
} }
return new Library(id, repository, groupId, artifactId, version, sha256); return new Library(id, repository, groupId, artifactId, version, sha256, forceOverride);
} }
} }
} }

View File

@@ -23,14 +23,15 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library; package org.geysermc.floodgate.isolation.library;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.geysermc.floodgate.core.library.classloader.LibraryClassLoader; import org.geysermc.floodgate.isolation.library.classloader.LibraryClassLoader;
import org.geysermc.floodgate.isolation.util.ChildFirstClassLoader;
// Credits for the idea of downloading dependencies on runtime go to LuckPerms // Credits for the idea of downloading dependencies on runtime go to LuckPerms
public final class LibraryManager { public final class LibraryManager {
@@ -39,8 +40,10 @@ public final class LibraryManager {
private final Set<Library> toApply = new HashSet<>(); private final Set<Library> toApply = new HashSet<>();
public LibraryManager(ClassLoader parent, Path cacheDirectory) { public LibraryManager(ClassLoader parent, Path cacheDirectory, boolean childFirst) {
this.classLoader = new LibraryClassLoader(parent); this.classLoader = childFirst ?
new ChildFirstClassLoader(parent) :
new LibraryClassLoader(parent);
this.cacheDirectory = cacheDirectory; this.cacheDirectory = cacheDirectory;
} }
@@ -49,12 +52,14 @@ public final class LibraryManager {
return this; return this;
} }
public void apply() { public LibraryManager apply() {
CompletableFuture.allOf( CompletableFuture.allOf(
toApply.stream() toApply.stream()
.map(library -> CompletableFuture.runAsync(() -> loadLibrary(library))) .map(library -> CompletableFuture.runAsync(() -> loadLibrary(library)))
.toArray(CompletableFuture[]::new) .toArray(CompletableFuture[]::new)
).join(); ).join();
toApply.clear();
return this;
} }
private void loadLibrary(Library library) { private void loadLibrary(Library library) {
@@ -63,10 +68,14 @@ public final class LibraryManager {
return; return;
} }
if (!Files.exists(libPath)) { if (library.forceOverride() || !Files.exists(libPath)) {
library.repository().downloadTo(library, libPath); library.repository().downloadTo(library, libPath);
} }
classLoader.addPath(libPath); classLoader.addPath(libPath);
} }
public LibraryClassLoader classLoader() {
return classLoader;
}
} }

View File

@@ -23,16 +23,32 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library; package org.geysermc.floodgate.isolation.library;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import org.geysermc.floodgate.core.util.HttpClient; import org.geysermc.floodgate.isolation.util.HttpUtil;
import org.geysermc.floodgate.core.util.HttpClient.HttpResponse; import org.geysermc.floodgate.isolation.util.HttpUtil.HttpResponse;
import org.geysermc.floodgate.isolation.util.StreamUtil;
public enum Repository { public enum Repository {
MAVEN_CENTRAL("https://repo1.maven.org/maven2/"); MAVEN_CENTRAL("https://repo1.maven.org/maven2/"),
OPEN_COLLAB("https://repo.opencollab.dev/main/"),
BUNDLED(null) {
@Override
public byte[] download(Library library) {
var loader = getClass().getClassLoader();
try (var resource = loader.getResourceAsStream("bundled/" + library.id() + ".jar")) {
return StreamUtil.readStream(resource);
} catch (Exception exception) {
throw new IllegalStateException(
"Could not load bundled jar " + library.id(),
exception
);
}
}
};
private final String baseUrl; private final String baseUrl;
@@ -42,16 +58,16 @@ public enum Repository {
private byte[] justDownload(Library library) { private byte[] justDownload(Library library) {
try { try {
HttpResponse<byte[]> response = new HttpClient().getRawData(baseUrl + library.path()); HttpResponse<byte[]> result = HttpUtil.getRawData(baseUrl + library.path());
if (!response.isCodeOk()) { if (!result.isCodeOk()) {
throw new RuntimeException(String.format( throw new RuntimeException(String.format(
"Got an invalid response code (%s) while downloading library %s", "Got an invalid response code (%s) while downloading library %s",
response.getHttpCode(), library.id() result.httpCode(), library.id()
)); ));
} }
return response.getResponse(); return result.response();
} catch (IOException exception) { } catch (IOException exception) {
throw new IllegalStateException("Failed to download library " + library.id(), exception); throw new IllegalStateException("Failed to download library " + library.id(), exception);
} }
@@ -65,6 +81,22 @@ public enum Repository {
public void downloadTo(Library library, Path location) { public void downloadTo(Library library, Path location) {
try { try {
// delete old versions of a given library
if (Files.isDirectory(location.getParent())) {
try (var list = Files.list(location.getParent())) {
list.forEach(path -> {
try {
Files.delete(path);
} catch (IOException exception) {
throw new IllegalStateException(
"Could not delete old version of library " + library.id(),
exception
);
}
});
}
}
Files.createDirectories(location.getParent()); Files.createDirectories(location.getParent());
Files.write(location, download(library)); Files.write(location, download(library));
} catch (IOException exception) { } catch (IOException exception) {

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library.classloader; package org.geysermc.floodgate.isolation.library.classloader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@@ -49,8 +49,8 @@ public class LibraryClassLoader extends URLClassLoader {
try { try {
addURL(path.toUri().toURL()); addURL(path.toUri().toURL());
loadedPaths.add(path); loadedPaths.add(path);
} catch (MalformedURLException e) { } catch (MalformedURLException exception) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(exception);
} }
} }

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library.info; package org.geysermc.floodgate.isolation.library.info;
import java.util.Base64; import java.util.Base64;

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.core.library.info; package org.geysermc.floodgate.isolation.library.info;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -23,15 +23,32 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.database.config; package org.geysermc.floodgate.isolation.loader;
import lombok.Getter; import java.lang.reflect.InvocationTargetException;
import org.geysermc.floodgate.core.database.config.DatabaseConfig;
@Getter public class LoaderUtil {
public class MysqlConfig implements DatabaseConfig { public static void invokeLoad(Object platform) {
private String hostname = "localhost"; try {
private String database = "floodgate"; platform.getClass().getMethod("load").invoke(platform);
private String username = "floodgate"; } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
private String password; throw new RuntimeException(exception);
}
}
public static void invokeEnable(Object platform) {
try {
platform.getClass().getMethod("enable").invoke(platform);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
}
public static void invokeDisable(Object platform) {
try {
platform.getClass().getMethod("disable").invoke(platform);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
}
} }

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2019-2023 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.isolation.loader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.geysermc.floodgate.isolation.library.LibraryManager;
public class PlatformHolder {
private final Class<?> platformClass;
private final LibraryManager manager;
private Object platformInstance;
public PlatformHolder(Class<?> platformClass, LibraryManager manager) {
this.platformClass = platformClass;
this.manager = manager;
}
public void init(Class<?>[] argumentTypes, Object... argumentValues)
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
platformInstance = platformClass
.getConstructor(argumentTypes)
.newInstance(argumentValues);
}
public void load() {
LoaderUtil.invokeLoad(platformInstance);
}
public void enable() {
LoaderUtil.invokeEnable(platformInstance);
}
public void disable() {
LoaderUtil.invokeDisable(platformInstance);
close();
}
public void close() {
try {
manager.classLoader().close();
} catch (IOException exception) {
throw new IllegalStateException("Failed to close classloader!", exception);
}
}
public PlatformHolder platformInstance(Object platformInstance) {
if (this.platformInstance == null) {
this.platformInstance = platformInstance;
}
return this;
}
public LibraryManager manager() {
return manager;
}
public Class<?> platformClass() {
return platformClass;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2023 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.isolation.loader;
import java.nio.file.Path;
import org.geysermc.floodgate.isolation.library.Library;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.library.Repository;
import org.geysermc.floodgate.isolation.util.UrlUtil;
public class PlatformLoader {
public static LibraryManager createLibraryManager(ClassLoader loader, Path cacheDirectory) {
return new LibraryManager(loader, cacheDirectory, true)
.addLibrary(
Library.builder()
.id("platform-base")
.repository(Repository.BUNDLED)
.artifactId("platform-base")
.forceOverride(true)
.build()
)
.apply();
}
public static PlatformHolder load(LibraryManager manager) {
var classLoader = manager.classLoader();
String mainClassString =
UrlUtil.readSingleLine(classLoader.getResource("org.geysermc.mainClass"));
try {
var mainClass = classLoader.loadClass(mainClassString);
return new PlatformHolder(mainClass, manager);
} catch (Exception exception) {
throw new IllegalStateException("Failed to load platform!", exception);
}
}
public static PlatformHolder loadDefault(ClassLoader pluginClassLoader, Path cacheDirectory) {
return load(createLibraryManager(pluginClassLoader, cacheDirectory));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2019-2023 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.isolation.util;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import org.geysermc.floodgate.isolation.library.classloader.LibraryClassLoader;
// Based of a Medium article https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305
public class ChildFirstClassLoader extends LibraryClassLoader {
private final ClassLoader systemClassLoader;
public ChildFirstClassLoader(ClassLoader parent) {
super(Objects.requireNonNull(parent));
systemClassLoader = getSystemClassLoader();
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
if (systemClassLoader != null) {
loadedClass = systemClassLoader.loadClass(name);
}
} catch (ClassNotFoundException ignored) {}
try {
if (loadedClass == null) {
loadedClass = findClass(name);
}
} catch (ClassNotFoundException e) {
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> allResources = new LinkedList<>();
Enumeration<URL> systemResources = systemClassLoader.getResources(name);
if (systemResources != null) {
while (systemResources.hasMoreElements()) {
allResources.add(systemResources.nextElement());
}
}
Enumeration<URL> thisResources = findResources(name);
if (thisResources != null) {
while (thisResources.hasMoreElements()) {
allResources.add(thisResources.nextElement());
}
}
Enumeration<URL> parentResources = getParent().getResources(name);
if (parentResources != null) {
while (parentResources.hasMoreElements()) {
allResources.add(parentResources.nextElement());
}
}
return new Enumeration<>() {
final Iterator<URL> it = allResources.iterator();
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public URL nextElement() {
return it.next();
}
};
}
@Override
public URL getResource(String name) {
URL resource = null;
if (systemClassLoader != null) {
resource = systemClassLoader.getResource(name);
}
if (resource == null) {
resource = findResource(name);
}
if (resource == null) {
resource = getParent().getResource(name);
}
return resource;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2019-2023 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.isolation.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import org.checkerframework.checker.nullness.qual.NonNull;
public class HttpUtil {
private static final String USER_AGENT = "GeyserMC-isolation";
public static @NonNull HttpResponse<byte[]> getRawData(String urlString) throws IOException {
HttpURLConnection connection = request(urlString);
try (InputStream inputStream = connection.getInputStream()) {
return new HttpResponse<>(
connection.getResponseCode(),
StreamUtil.readStream(inputStream)
);
} catch (SocketTimeoutException | NullPointerException exception) {
return new HttpResponse<>(-1, null);
}
}
private static HttpURLConnection request(String urlString) {
HttpURLConnection connection;
try {
URL url = new URL(urlString.replace(" ", "%20")); // Encode spaces correctly
connection = (HttpURLConnection) url.openConnection();
} catch (Exception exception) {
throw new RuntimeException("Failed to create connection", exception);
}
try {
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setRequestProperty("User-Agent", USER_AGENT);
connection.setConnectTimeout(3000);
connection.setReadTimeout(10_000);
} catch (Exception exception) {
throw new RuntimeException("Failed to create request", exception);
}
return connection;
}
public static class HttpResponse<T> {
private final int httpCode;
private final T response;
private HttpResponse(int httpCode, T response) {
this.httpCode = httpCode;
this.response = response;
}
public int httpCode() {
return httpCode;
}
public T response() {
return response;
}
public boolean isCodeOk() {
return httpCode >= 200 && httpCode < 300;
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -23,16 +23,21 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.database.config; package org.geysermc.floodgate.isolation.util;
import lombok.Getter; import java.io.ByteArrayOutputStream;
import org.geysermc.floodgate.core.database.config.DatabaseConfig; import java.io.IOException;
import java.io.InputStream;
@Getter public class StreamUtil {
public class MongoConfig implements DatabaseConfig { public static byte[] readStream(InputStream inputStream) throws IOException {
private String hostname = "localhost"; byte[] buffer = new byte[8196];
private String database = "floodgate"; int len;
private String username = "floodgate"; try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
private String password; while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
private String mongouri = ""; outputStream.write(buffer, 0, len);
}
return outputStream.toByteArray();
}
}
} }

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2023 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.isolation.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class UrlUtil {
public static List<String> readAllLines(URL url) {
try {
var connection = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
var list = new ArrayList<String>();
String line;
while ((line = reader.readLine()) != null) {
list.add(line);
}
reader.close();
return list;
} catch (IOException exception) {
throw new IllegalStateException("Unable to read url %s".formatted(url), exception);
}
}
public static String readSingleLine(URL url) {
var lines = readAllLines(url);
if (lines.size() != 1) {
throw new IllegalStateException(
"Url %s didn't return one line of data, got %s".formatted(url, lines.size())
);
}
return lines.get(0);
}
}

View File

@@ -68,11 +68,16 @@ include(":api")
include(":core") include(":core")
include(":bungee") include(":bungee")
include(":spigot") include(":spigot")
include(":velocity")
include(":universal") include(":universal")
include(":sqlite") include(":sqlite")
include(":mysql") include(":mysql")
include(":mongo") include(":mongo")
project(":sqlite").projectDir = file("database/sqlite") include(":database")
project(":mysql").projectDir = file("database/mysql") include(":isolation")
project(":mongo").projectDir = file("database/mongo")
arrayOf("velocity").forEach { platform ->
arrayOf("base", "isolated").forEach {
include(":$platform-$it")
project(":$platform-$it").projectDir = file("$platform/$it")
}
}

View File

@@ -35,6 +35,7 @@ import io.micronaut.inject.qualifiers.Qualifiers;
import java.nio.file.Path; import java.nio.file.Path;
import org.geysermc.floodgate.core.FloodgatePlatform; import org.geysermc.floodgate.core.FloodgatePlatform;
import org.geysermc.floodgate.core.util.ReflectionUtils; import org.geysermc.floodgate.core.util.ReflectionUtils;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.slf4j.Logger; import org.slf4j.Logger;
public class VelocityPlatform extends FloodgatePlatform { public class VelocityPlatform extends FloodgatePlatform {
@@ -46,7 +47,9 @@ public class VelocityPlatform extends FloodgatePlatform {
@Inject PluginContainer container; @Inject PluginContainer container;
@Inject Logger logger; @Inject Logger logger;
public VelocityPlatform() { @Inject
public VelocityPlatform(LibraryManager manager) {
super(manager);
ReflectionUtils.setPrefix("com.velocitypowered.proxy"); ReflectionUtils.setPrefix("com.velocitypowered.proxy");
} }

View File

@@ -0,0 +1,36 @@
var log4jVersion = "2.11.2"
var gsonVersion = "2.8.8"
var guavaVersion = "25.1-jre"
plugins {
java
}
dependencies {
api(projects.isolation)
}
tasks {
jar {
dependsOn(":velocity-base:build", configurations.runtimeClasspath)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from (configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
archiveBaseName.set("floodgate-${project.name}")
archiveVersion.set("")
archiveClassifier.set("")
val velocityBaseJar = project.projects
.velocityBase.dependencyProject
.buildDir
.resolve("libs")
.resolve("floodgate-velocity-base.jar")
from(velocityBaseJar.parentFile) {
include(velocityBaseJar.name)
rename("floodgate-velocity-base.jar", "platform-base.jar")
into("bundled/")
}
}
}

View File

@@ -25,28 +25,45 @@
package org.geysermc.floodgate.velocity; package org.geysermc.floodgate.velocity;
import com.google.inject.AbstractModule;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import java.nio.file.Paths;
import org.geysermc.floodgate.isolation.library.LibraryManager;
import org.geysermc.floodgate.isolation.loader.PlatformHolder;
import org.geysermc.floodgate.isolation.loader.PlatformLoader;
public final class VelocityPlugin { public final class IsolatedVelocityPlugin {
private final VelocityPlatform platform; private final PlatformHolder holder;
@Inject @Inject
public VelocityPlugin(Injector guice) { public IsolatedVelocityPlugin(Injector guice) {
platform = guice.getInstance(VelocityPlatform.class); try {
platform.load(); holder = PlatformLoader.loadDefault(getClass().getClassLoader(), Paths.get("./libs"));
Injector child = guice.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(LibraryManager.class).toInstance(holder.manager());
}
});
holder.platformInstance(child.getInstance(holder.platformClass()));
holder.load();
} catch (Exception exception) {
throw new RuntimeException("Failed to load Floodgate", exception);
}
} }
@Subscribe @Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) { public void onProxyInitialization(ProxyInitializeEvent event) {
platform.enable(); holder.enable();
} }
@Subscribe @Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) { public void onProxyShutdown(ProxyShutdownEvent event) {
platform.disable(); holder.disable();
} }
} }

View File

@@ -0,0 +1 @@
org.geysermc.floodgate.velocity.VelocityPlatform

View File

@@ -1 +1 @@
{"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "$url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.velocity.VelocityPlugin"} {"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "$url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.velocity.IsolatedVelocityPlugin"}