mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-20 07:19:26 +00:00
Initial work on loading libraries on runtime
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
`java-library`
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("listDependencyInfo") {
|
||||||
|
doLast {
|
||||||
|
listDependencies().forEach {
|
||||||
|
println(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("writeDependencyInfo") {
|
||||||
|
doLast {
|
||||||
|
Files.writeString(
|
||||||
|
dependenciesInfoFile(),
|
||||||
|
dependenciesToString(listDependencies())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("checkDependencyInfoFile") {
|
||||||
|
doLast {
|
||||||
|
val asString = dependenciesToString(listDependencies())
|
||||||
|
if (!asString.contentEquals(Files.readString(dependenciesInfoFile()))) {
|
||||||
|
throw IllegalStateException("The dependency hashes file is outdated!")
|
||||||
|
}
|
||||||
|
println("The dependency hashes file is up-to-date")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("build") {
|
||||||
|
dependsOn("checkDependencyHashesFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependenciesToString(dependencies: Collection<Dependency>): String {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
dependencies.forEach {
|
||||||
|
if (builder.length > 1) {
|
||||||
|
builder.append("\n")
|
||||||
|
}
|
||||||
|
builder.append(it)
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependenciesInfoFile(): Path {
|
||||||
|
return project.projectDir.toPath().resolve("src/main/resources/dependencyInfo.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listDependencies(): Collection<Dependency> {
|
||||||
|
return listConfigurationDependencies(configurations.default.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listConfigurationDependencies(configuration: Configuration): Collection<Dependency> {
|
||||||
|
val deps = configuration.resolvedConfiguration.firstLevelModuleDependencies
|
||||||
|
.associateBy({"${it.moduleGroup}:${it.moduleName}"}, {it})
|
||||||
|
|
||||||
|
return configuration.resolvedConfiguration.resolvedArtifacts
|
||||||
|
.mapNotNull {
|
||||||
|
val id = it.id.componentIdentifier
|
||||||
|
if (id is ModuleComponentIdentifier && deps.containsKey("${id.group}:${id.module}")) {
|
||||||
|
convertDependency(id, it.file)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertDependency(id: ModuleComponentIdentifier, file: File): Dependency {
|
||||||
|
return Dependency(id.group, id.module, id.version, calculateSha256(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateSha256(file: File): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
return Base64.getEncoder().encodeToString(digest.digest(file.readBytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Dependency(
|
||||||
|
val groupId: String,
|
||||||
|
val artifactId: String,
|
||||||
|
val version: String,
|
||||||
|
val sha256: String
|
||||||
|
) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return arrayOf(groupId, artifactId, version, sha256).joinToString(":")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("floodgate.generate-templates")
|
id("floodgate.generate-templates")
|
||||||
|
id("floodgate.dependency-hash")
|
||||||
id("io.micronaut.library") version "3.7.4"
|
id("io.micronaut.library") version "3.7.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
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;
|
||||||
@@ -42,6 +43,10 @@ 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;
|
||||||
|
|
||||||
public abstract class FloodgatePlatform {
|
public abstract class FloodgatePlatform {
|
||||||
@@ -56,6 +61,21 @@ public abstract class FloodgatePlatform {
|
|||||||
public void load() {
|
public void load() {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
var infoLoader = DependencyInfoLoader.load(
|
||||||
|
getClass().getClassLoader().getResource("dependencyInfo.txt")
|
||||||
|
);
|
||||||
|
|
||||||
|
new LibraryManager(ClassLoader.getSystemClassLoader().getParent(), Paths.get("./libs"))
|
||||||
|
.addLibrary(
|
||||||
|
Library.builder(infoLoader)
|
||||||
|
.id("guava")
|
||||||
|
.repository(Repository.MAVEN_CENTRAL)
|
||||||
|
.groupId("com.google.guava")
|
||||||
|
.artifactId("guava")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.apply();
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
context = ApplicationContext.builder()
|
context = ApplicationContext.builder()
|
||||||
.properties(Map.of(
|
.properties(Map.of(
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.geysermc.floodgate.core.library.info.DependencyInfo;
|
||||||
|
import org.geysermc.floodgate.core.library.info.DependencyInfoLoader;
|
||||||
|
import org.geysermc.floodgate.core.util.Utils;
|
||||||
|
|
||||||
|
public record Library(
|
||||||
|
@NonNull String id,
|
||||||
|
@NonNull Repository repository,
|
||||||
|
@NonNull String groupId,
|
||||||
|
@NonNull String artifactId,
|
||||||
|
@NonNull String version,
|
||||||
|
byte[] sha256
|
||||||
|
) {
|
||||||
|
public Library {
|
||||||
|
Objects.requireNonNull(id);
|
||||||
|
Objects.requireNonNull(repository);
|
||||||
|
Objects.requireNonNull(groupId);
|
||||||
|
Objects.requireNonNull(artifactId);
|
||||||
|
Objects.requireNonNull(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String path() {
|
||||||
|
return "%s/%s/%s/%s-%s.jar".formatted(
|
||||||
|
groupId.replace('.', '/'),
|
||||||
|
artifactId,
|
||||||
|
version,
|
||||||
|
artifactId,
|
||||||
|
version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LibraryBuilder builder() {
|
||||||
|
return builder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LibraryBuilder builder(DependencyInfoLoader info) {
|
||||||
|
return new LibraryBuilder(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path filePath() {
|
||||||
|
return Path.of(id(), id() + "-" + version() + ".jar");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateChecksum(byte[] data) {
|
||||||
|
byte[] hash;
|
||||||
|
try {
|
||||||
|
hash = MessageDigest.getInstance("SHA-256").digest(data);
|
||||||
|
} catch (NoSuchAlgorithmException exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arrays.equals(hash, sha256)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"Downloaded library hash (%s) didn't match expected hash (%s)!",
|
||||||
|
Utils.base64Encode(data),
|
||||||
|
Utils.base64Encode(sha256())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LibraryBuilder {
|
||||||
|
private final DependencyInfoLoader dependencyInfoLoader;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private Repository repository;
|
||||||
|
private String groupId;
|
||||||
|
private String artifactId;
|
||||||
|
|
||||||
|
private String version;
|
||||||
|
private byte[] sha256;
|
||||||
|
|
||||||
|
LibraryBuilder(DependencyInfoLoader info) {
|
||||||
|
this.dependencyInfoLoader = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder id(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder repository(@NonNull Repository repository) {
|
||||||
|
this.repository = Objects.requireNonNull(repository);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder groupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder artifactId(String artifactId) {
|
||||||
|
this.artifactId = artifactId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder version(String version) {
|
||||||
|
this.version = version;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder sha256(byte[] sha256) {
|
||||||
|
this.sha256 = sha256;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryBuilder sha256(String sha256) {
|
||||||
|
return sha256(Base64.getDecoder().decode(sha256));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Library build() {
|
||||||
|
if (version == null || sha256 == null) {
|
||||||
|
if (dependencyInfoLoader == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Provide a version and hash or provide a DependencyInfoLoader"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DependencyInfo dependencyInfo = dependencyInfoLoader.byCombinedId(groupId, artifactId);
|
||||||
|
version = dependencyInfo.version();
|
||||||
|
sha256 = dependencyInfo.sha256();
|
||||||
|
}
|
||||||
|
return new Library(id, repository, groupId, artifactId, version, sha256);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import org.geysermc.floodgate.core.library.classloader.LibraryClassLoader;
|
||||||
|
|
||||||
|
// Credits for the idea of downloading dependencies on runtime go to LuckPerms
|
||||||
|
public final class LibraryManager {
|
||||||
|
private final LibraryClassLoader classLoader;
|
||||||
|
private final Path cacheDirectory;
|
||||||
|
|
||||||
|
private final Set<Library> toApply = new HashSet<>();
|
||||||
|
|
||||||
|
public LibraryManager(ClassLoader parent, Path cacheDirectory) {
|
||||||
|
this.classLoader = new LibraryClassLoader(parent);
|
||||||
|
this.cacheDirectory = cacheDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LibraryManager addLibrary(Library library) {
|
||||||
|
toApply.add(library);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply() {
|
||||||
|
CompletableFuture.allOf(
|
||||||
|
toApply.stream()
|
||||||
|
.map(library -> CompletableFuture.runAsync(() -> loadLibrary(library)))
|
||||||
|
.toArray(CompletableFuture[]::new)
|
||||||
|
).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLibrary(Library library) {
|
||||||
|
var libPath = cacheDirectory.resolve(library.filePath());
|
||||||
|
if (classLoader.isLoaded(libPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(libPath)) {
|
||||||
|
library.repository().downloadTo(library, libPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
classLoader.addPath(libPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.geysermc.floodgate.core.util.HttpClient;
|
||||||
|
import org.geysermc.floodgate.core.util.HttpClient.HttpResponse;
|
||||||
|
|
||||||
|
public enum Repository {
|
||||||
|
MAVEN_CENTRAL("https://repo1.maven.org/maven2/");
|
||||||
|
|
||||||
|
private final String baseUrl;
|
||||||
|
|
||||||
|
Repository(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] justDownload(Library library) {
|
||||||
|
try {
|
||||||
|
HttpResponse<byte[]> response = new HttpClient().getRawData(baseUrl + library.path());
|
||||||
|
|
||||||
|
if (!response.isCodeOk()) {
|
||||||
|
throw new RuntimeException(String.format(
|
||||||
|
"Got an invalid response code (%s) while downloading library %s",
|
||||||
|
response.getHttpCode(), library.id()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getResponse();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new IllegalStateException("Failed to download library " + library.id(), exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] download(Library library) {
|
||||||
|
byte[] data = justDownload(library);
|
||||||
|
library.validateChecksum(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadTo(Library library, Path location) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(location.getParent());
|
||||||
|
Files.write(location, download(library));
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new IllegalStateException("Failed to save library!", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library.classloader;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class LibraryClassLoader extends URLClassLoader {
|
||||||
|
private final Set<Path> loadedPaths = new HashSet<>();
|
||||||
|
|
||||||
|
public LibraryClassLoader(ClassLoader parent) {
|
||||||
|
super(new URL[0], parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addURL(URL url) {
|
||||||
|
super.addURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPath(Path path) {
|
||||||
|
try {
|
||||||
|
addURL(path.toUri().toURL());
|
||||||
|
loadedPaths.add(path);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Path> loadedPaths() {
|
||||||
|
return Collections.unmodifiableSet(loadedPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoaded(Path path) {
|
||||||
|
return loadedPaths.contains(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library.info;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public record DependencyInfo(String groupId, String artifactId, String version, byte[] sha256) {
|
||||||
|
public static DependencyInfo fromString(String string) {
|
||||||
|
var split = string.split(":");
|
||||||
|
if (split.length != 4) {
|
||||||
|
throw new IllegalArgumentException("Invalid LibraryInfo string: " + string);
|
||||||
|
}
|
||||||
|
return new DependencyInfo(split[0], split[1], split[2], Base64.getDecoder().decode(split[3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.library.info;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class DependencyInfoLoader {
|
||||||
|
private final Map<String, DependencyInfo> infoMap = new HashMap<>();
|
||||||
|
|
||||||
|
private DependencyInfoLoader() {}
|
||||||
|
|
||||||
|
public static DependencyInfoLoader load(URL infoUrl) {
|
||||||
|
var loader = new DependencyInfoLoader();
|
||||||
|
|
||||||
|
try (var reader = new BufferedReader(new InputStreamReader(infoUrl.openStream()))) {
|
||||||
|
reader.lines().forEach(line -> {
|
||||||
|
var info = DependencyInfo.fromString(line);
|
||||||
|
loader.infoMap.put(info.groupId() + ":" + info.artifactId(), info);
|
||||||
|
});
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DependencyInfo byCombinedId(String groupId, String artifactId) {
|
||||||
|
return infoMap.get(groupId + ":" + artifactId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,9 +30,12 @@ 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;
|
||||||
@@ -46,8 +49,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
@SuppressWarnings({"PMD.CloseResource", "PMD.PreserveStackTrace"})
|
@SuppressWarnings({"PMD.CloseResource", "PMD.PreserveStackTrace"})
|
||||||
@Singleton
|
@Singleton
|
||||||
public class HttpClient {
|
public class HttpClient {
|
||||||
private static final String USER_AGENT = "GeyserMC/Floodgate";
|
|
||||||
|
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
@Inject
|
@Inject
|
||||||
@Named("commonPool")
|
@Named("commonPool")
|
||||||
@@ -86,7 +87,7 @@ public class HttpClient {
|
|||||||
try {
|
try {
|
||||||
connection.setRequestMethod("GET");
|
connection.setRequestMethod("GET");
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
connection.setRequestProperty("User-Agent", Constants.USER_AGENT);
|
||||||
connection.setConnectTimeout(3000);
|
connection.setConnectTimeout(3000);
|
||||||
connection.setReadTimeout(5000);
|
connection.setReadTimeout(5000);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
@@ -96,6 +97,26 @@ 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);
|
||||||
|
|||||||
@@ -28,21 +28,18 @@ package org.geysermc.floodgate.core.util;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
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.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$");
|
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$");
|
||||||
@@ -138,41 +135,7 @@ public class Utils {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String base64Encode(byte[] data) {
|
||||||
* Returns a set of all the classes that are annotated by a given annotation.
|
return Base64.getEncoder().encodeToString(data);
|
||||||
* Keep in mind that these are from a set of generated annotations generated
|
|
||||||
* at compile time by the annotation processor, meaning that arbitrary annotations
|
|
||||||
* cannot be passed into this method and expected to get a set of classes back.
|
|
||||||
*
|
|
||||||
* @param annotationClass the annotation class
|
|
||||||
* @return a set of all the classes annotated by the given annotation
|
|
||||||
*/
|
|
||||||
public static Set<Class<?>> getGeneratedClassesForAnnotation(Class<? extends Annotation> annotationClass) {
|
|
||||||
return getGeneratedClassesForAnnotation(annotationClass.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of all the classes that are annotated by a given annotation.
|
|
||||||
* Keep in mind that these are from a set of generated annotations generated
|
|
||||||
* at compile time by the annotation processor, meaning that arbitrary annotations
|
|
||||||
* cannot be passed into this method and expected to have a set of classes
|
|
||||||
* returned back.
|
|
||||||
*
|
|
||||||
* @param input the fully qualified name of the annotation
|
|
||||||
* @return a set of all the classes annotated by the given annotation
|
|
||||||
*/
|
|
||||||
public static Set<Class<?>> getGeneratedClassesForAnnotation(String input) {
|
|
||||||
try (InputStream annotatedClass = Utils.class.getClassLoader().getResourceAsStream(input);
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(annotatedClass))) {
|
|
||||||
return reader.lines().map(className -> {
|
|
||||||
try {
|
|
||||||
return Class.forName(className);
|
|
||||||
} catch (ClassNotFoundException ex) {
|
|
||||||
throw new RuntimeException("Failed to find class for annotation " + input, ex);
|
|
||||||
}
|
|
||||||
}).collect(Collectors.toSet());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
core/src/main/resources/dependencyInfo.txt
Normal file
15
core/src/main/resources/dependencyInfo.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
org.geysermc.configutils:configutils:1.0-SNAPSHOT:Xyh6oCOT1RwP5hw1gRM77M0mOJ86nNPf4J9Hs9PL4Ek=
|
||||||
|
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-int-object-maps:8.5.3:Z0iKxf9OmoIcQtmqV9Lo9Z39XSpBXKjL8tavAdVCKBY=
|
||||||
|
org.java-websocket:Java-WebSocket:1.5.2:/4adGYqNxdAJZzkuGHrtqTuDO3zdO8zvs9f7WDQJi4U=
|
||||||
|
cloud.commandframework:cloud-core:1.5.0:+hDJlLwU84P6sf0gkDq+xStIqAHhMXUCI+Wb8VsD1Zk=
|
||||||
|
io.micronaut.sql:micronaut-jdbc-hikari:4.7.2:w2N1dH4vY/QjjQGsViMOwgVYxAnZNGgMm1doCa19wOA=
|
||||||
|
io.micronaut.data:micronaut-data-hibernate-jpa:3.9.6:kFYugtQeXfLjqUCWrf3MFFDoUdYhXrPQmSh0pu2Zxzo=
|
||||||
|
io.micronaut:micronaut-context:3.8.7:ir020Tq3UavnPZhXOUNHCl/XdGcVrOzhM1lU5NT6YEc=
|
||||||
|
io.micronaut:micronaut-inject-java:3.8.7:V7Dxe3ctZ8+c7ZUDQ3Cwtsqc6S7l2SE8+qTVhvhTHQs=
|
||||||
|
io.micronaut:micronaut-inject:3.8.7:6C+sHfQBffYWV/LnD+Vc5WhqRbkWZIgJXqxThca2tXE=
|
||||||
|
org.yaml:snakeyaml:2.0:iAydiW5LdKBsVJwVyklkUBZdaQn6FdfmYr7o9qZtevo=
|
||||||
|
org.bstats:bstats-base:3.0.1:vdyqrd84GBpK0MnfDnSXXMTauyzL9nkMSfAiMOrVU2Q=
|
||||||
|
com.google.guava:guava:31.1-jre:pC7cnKt5Ljn+ObuU8/ymVe0Vf/h6iveOHWulsHxKAKs=
|
||||||
|
com.h2database:h2:1.4.200:OtmsS2qunNnTrBxEdGXh7QYBm4UbiT3WqNdt222FvKY=
|
||||||
@@ -39,6 +39,7 @@ public final class Constants {
|
|||||||
public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$";
|
public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$";
|
||||||
|
|
||||||
|
|
||||||
|
public static final String USER_AGENT = "GeyserMC/Floodgate";
|
||||||
private static final String API_BASE_URL = "s://api.geysermc.org";
|
private static final String API_BASE_URL = "s://api.geysermc.org";
|
||||||
public static final String HEALTH_URL = "http" + API_BASE_URL + "/health";
|
public static final String HEALTH_URL = "http" + API_BASE_URL + "/health";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user