diff --git a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java index 6318e3de7..03a25a774 100644 --- a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java +++ b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java @@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.net.URL; +import java.nio.file.Path; import java.util.LinkedList; import java.util.List; @@ -75,6 +76,8 @@ public final class DiscoveredExtension { /** The original jar this is from. */ transient private File originalJar; + transient private Path dataDirectory; + /** The class loader that powers it. */ transient private MinestomExtensionClassLoader minestomExtensionClassLoader; @@ -130,6 +133,15 @@ public final class DiscoveredExtension { return originalJar; } + @NotNull + public Path getDataDirectory() { + return dataDirectory; + } + + public void setDataDirectory(@NotNull Path dataDirectory) { + this.dataDirectory = dataDirectory; + } + MinestomExtensionClassLoader removeMinestomExtensionClassLoader() { MinestomExtensionClassLoader oldClassLoader = getMinestomExtensionClassLoader(); setMinestomExtensionClassLoader(null); diff --git a/src/main/java/net/minestom/server/extensions/Extension.java b/src/main/java/net/minestom/server/extensions/Extension.java index cd9b52028..cd43cfe44 100644 --- a/src/main/java/net/minestom/server/extensions/Extension.java +++ b/src/main/java/net/minestom/server/extensions/Extension.java @@ -1,11 +1,19 @@ package net.minestom.server.extensions; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; +import java.io.IOException; +import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -77,6 +85,122 @@ public abstract class Extension { return logger; } + @NotNull + public Path getDataDirectory() { + return getOrigin().getDataDirectory(); + } + + /** + * Gets a resource from the extension directory, or from inside the jar if it does not + * exist in the extension directory. + *
+ * If it does not exist in the extension directory, it will be copied from inside the jar. + *
+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param fileName The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + @Nullable + public InputStream getResource(@NotNull String fileName) { + return getResource(Paths.get(fileName)); + } + + /** + * Gets a resource from the extension directory, or from inside the jar if it does not + * exist in the extension directory. + *
+ * If it does not exist in the extension directory, it will be copied from inside the jar. + *
+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param target The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + @Nullable + public InputStream getResource(@NotNull Path target) { + final Path targetFile = getDataDirectory().resolve(target); + try { + // Copy from jar if the file does not exist in the extension data directory. + if (!Files.exists(targetFile)) { + savePackagedResource(target); + } + + return Files.newInputStream(targetFile); + } catch (IOException ex) { + getLogger().info("Failed to read resource {}.", target, ex); + return null; + } + } + + /** + * Gets a resource from inside the extension jar. + *
+ * The caller is responsible for closing the returned {@link InputStream}. + * + * @param fileName The file to read + * @return The file contents, or null if there was an issue reading the file. + */ + public InputStream getPackagedResource(@NotNull String fileName) { + return getPackagedResource(Paths.get(fileName)); + } + + /** + * Gets a resource from inside the extension jar. + *
+ * The caller is responsible for closing the returned {@link InputStream}.
+ *
+ * @param target The file to read
+ * @return The file contents, or null if there was an issue reading the file.
+ */
+ @Nullable
+ public InputStream getPackagedResource(@NotNull Path target) {
+ try {
+ final URL url = getOrigin().getMinestomExtensionClassLoader().getResource(target.toString());
+ if (url == null) {
+ getLogger().debug("Resource not found: {}", target);
+ return null;
+ }
+
+ return url.openConnection().getInputStream();
+ } catch (IOException ex) {
+ getLogger().debug("Failed to load resource {}.", target, ex);
+ return null;
+ }
+ }
+
+ /**
+ * Copies a resource file to the extension directory, replacing any existing copy.
+ *
+ * @param fileName The resource to save
+ * @return True if the resource was saved successfully, null otherwise
+ */
+ public boolean savePackagedResource(@NotNull String fileName) {
+ return savePackagedResource(Paths.get(fileName));
+ }
+
+ /**
+ * Copies a resource file to the extension directory, replacing any existing copy.
+ *
+ * @param target The resource to save
+ * @return True if the resource was saved successfully, null otherwise
+ */
+ public boolean savePackagedResource(@NotNull Path target) {
+ final Path targetFile = getDataDirectory().resolve(target);
+ try (InputStream is = getPackagedResource(target)) {
+ if (is == null) {
+ return false;
+ }
+
+ Files.createDirectories(targetFile.getParent());
+ Files.copy(is, targetFile, StandardCopyOption.REPLACE_EXISTING);
+ return true;
+ } catch (IOException ex) {
+ getLogger().debug("Failed to save resource {}.", target, ex);
+ return false;
+ }
+ }
+
/**
* Adds a new observer to this extension.
* Will be kept as a WeakReference.
diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java
index 2de27ece5..b9ad3b0b9 100644
--- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java
+++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java
@@ -24,6 +24,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
@@ -44,6 +46,7 @@ public class ExtensionManager {
private final File extensionFolder = new File("extensions");
private final File dependenciesFolder = new File(extensionFolder, ".libs");
+ private Path extensionDataRoot = extensionFolder.toPath();
private boolean loaded;
// Option
@@ -336,6 +339,7 @@ public class ExtensionManager {
DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class);
extension.files.add(new File(extensionClasses).toURI().toURL());
extension.files.add(new File(extensionResources).toURI().toURL());
+ extension.setDataDirectory(getExtensionDataRoot().resolve(extension.getName()));
// Verify integrity and ensure defaults
DiscoveredExtension.verifyIntegrity(extension);
@@ -365,6 +369,7 @@ public class ExtensionManager {
DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class);
extension.setOriginalJar(file);
extension.files.add(file.toURI().toURL());
+ extension.setDataDirectory(getExtensionDataRoot().resolve(extension.getName()));
// Verify integrity and ensure defaults
DiscoveredExtension.verifyIntegrity(extension);
@@ -569,6 +574,15 @@ public class ExtensionManager {
return extensionFolder;
}
+ @NotNull
+ public Path getExtensionDataRoot() {
+ return extensionDataRoot;
+ }
+
+ public void setExtensionDataRoot(@NotNull Path dataRoot) {
+ this.extensionDataRoot = dataRoot;
+ }
+
@NotNull
public Collection