From 69c834a2dddccd30bfd1c1ffe7f9f64e948909fa Mon Sep 17 00:00:00 2001 From: tunikakeks Date: Wed, 21 Aug 2024 21:09:46 +0000 Subject: [PATCH 1/4] feat: Base plugin + plugin manager --- pom.xml | 2 +- src/main/java/org/sculk/Server.java | 20 +- src/main/java/org/sculk/plugin/Plugin.java | 96 +++++++ .../org/sculk/plugin/PluginClassLoader.java | 42 +++ .../java/org/sculk/plugin/PluginData.java | 35 +++ .../java/org/sculk/plugin/PluginLoader.java | 77 ++++++ .../java/org/sculk/plugin/PluginManager.java | 249 ++++++++++++++++++ 7 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/sculk/plugin/Plugin.java create mode 100644 src/main/java/org/sculk/plugin/PluginClassLoader.java create mode 100644 src/main/java/org/sculk/plugin/PluginData.java create mode 100644 src/main/java/org/sculk/plugin/PluginLoader.java create mode 100644 src/main/java/org/sculk/plugin/PluginManager.java diff --git a/pom.xml b/pom.xml index c52f3cf..e8bf360 100644 --- a/pom.xml +++ b/pom.xml @@ -145,7 +145,7 @@ it.unimi.dsi fastutil - 8.5.12 + 8.5.13 compile diff --git a/src/main/java/org/sculk/Server.java b/src/main/java/org/sculk/Server.java index 4a92c64..d367a44 100644 --- a/src/main/java/org/sculk/Server.java +++ b/src/main/java/org/sculk/Server.java @@ -19,9 +19,9 @@ import org.sculk.network.Network; import org.sculk.network.SourceInterface; import org.sculk.network.protocol.ProtocolInfo; +import org.sculk.plugin.PluginManager; import org.sculk.scheduler.Scheduler; import org.sculk.utils.TextFormat; -import org.sculk.utils.Utils; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -55,6 +55,7 @@ public class Server { private final Logger logger; private final TerminalConsole console; private final EventManager eventManager; + private final PluginManager pluginManager; private final Injector injector; private Scheduler scheduler; @@ -111,6 +112,7 @@ public Server(Logger logger, String dataPath) { this.injector = Guice.createInjector(Stage.PRODUCTION, new SculkModule(this)); this.eventManager = injector.getInstance(EventManager.class); this.scheduler = injector.getInstance(Scheduler.class); + this.pluginManager = new PluginManager(this); this.operators = new Config(this.dataPath.resolve("op.txt").toString(), Config.ENUM); this.whitelist = new Config(this.dataPath.resolve("whitelist.txt").toString(), Config.ENUM); @@ -127,6 +129,10 @@ public Server(Logger logger, String dataPath) { public void start() { this.console.getConsoleThread().start(); + logger.info("Loading all plugins..."); + pluginManager.loadAllPlugins(); + logger.info("All plugins loaded successfully"); + InetSocketAddress bindAddress = new InetSocketAddress(this.getProperties().get(ServerPropertiesKeys.SERVER_IP, "0.0.0.0"), this.getProperties().get(ServerPropertiesKeys.SERVER_PORT, 19132)); this.serverId = UUID.randomUUID(); this.network = new Network(this); @@ -150,6 +156,10 @@ public void start() { logger.info("This server is running on version {}",TextFormat.AQUA + Sculk.CODE_VERSION); logger.info("Sculk is distributed undex the {}",TextFormat.AQUA + "GNU GENERAL PUBLIC LICENSE"); + logger.info("Enable all plugins..."); + pluginManager.enableAllPlugins(); + logger.info("All plugins enabled successfully"); + getLogger().info("Done ({}s)! For help, type \"help\" or \"?", (double) (System.currentTimeMillis() - Sculk.START_TIME) / 1000); this.tickProcessor(); } @@ -161,6 +171,10 @@ public void shutdown() { this.logger.info("Stopping the server"); this.shutdown = true; + logger.info("Disabling all plugins..."); + pluginManager.disableAllPlugins(); + logger.info("Disabled all plugins"); + Sculk.shutdown(); this.logger.info("Stopping network interfaces"); @@ -180,6 +194,10 @@ public EventManager getEventManager() { return eventManager; } + public PluginManager getPluginManager() { + return pluginManager; + } + public Injector getInjector() { return injector; } diff --git a/src/main/java/org/sculk/plugin/Plugin.java b/src/main/java/org/sculk/plugin/Plugin.java new file mode 100644 index 0000000..782cef6 --- /dev/null +++ b/src/main/java/org/sculk/plugin/Plugin.java @@ -0,0 +1,96 @@ +package org.sculk.plugin; + +import com.google.common.base.Preconditions; +import org.apache.logging.log4j.Logger; +import org.sculk.Server; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public abstract class Plugin { + protected boolean enabled = false; + private PluginData description; + private Server server; + private Logger logger; + private File pluginFile; + private File dataFolder; + private boolean initialized = false; + + public Plugin() { + } + + protected final void init(PluginData description, Server server, File pluginFile) { + Preconditions.checkArgument(!this.initialized, "Plugin has been already initialized!"); + this.initialized = true; + this.description = description; + this.server = server; + this.logger = server.getLogger(); + + this.pluginFile = pluginFile; + this.dataFolder = new File(Server.getInstance().getDataPath() + "/plugins/" + description.getName().toLowerCase() + "/"); + if (!this.dataFolder.exists()) { + this.dataFolder.mkdirs(); + } + } + + public void onLoad() { + } + + public abstract void onEnable(); + + public void onDisable() { + } + + public InputStream getResourceFile(String filename) { + try { + JarFile pluginJar = new JarFile(this.pluginFile); + JarEntry entry = pluginJar.getJarEntry(filename); + return pluginJar.getInputStream(entry); + } catch (IOException e) { + } + return null; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + if (this.enabled == enabled) { + return; + } + this.enabled = enabled; + try { + if (enabled) { + this.onEnable(); + } else { + this.onDisable(); + } + } catch (Exception e) { + this.logger.error("Error while enabling/disabling plugin " + this.getName() + ": " + e); + } + } + + public PluginData getDescription() { + return this.description; + } + + public String getName() { + return this.description.getName(); + } + + public Server getServer() { + return this.server; + } + + public Logger getLogger() { + return this.logger; + } + + public File getDataFolder() { + return this.dataFolder; + } +} \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginClassLoader.java b/src/main/java/org/sculk/plugin/PluginClassLoader.java new file mode 100644 index 0000000..73a2479 --- /dev/null +++ b/src/main/java/org/sculk/plugin/PluginClassLoader.java @@ -0,0 +1,42 @@ +package org.sculk.plugin; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public class PluginClassLoader extends URLClassLoader { + + private final PluginManager pluginManager; + private final Object2ObjectOpenHashMap> classes = new Object2ObjectOpenHashMap<>(); + + public PluginClassLoader(PluginManager pluginManager, ClassLoader parent, File file) throws MalformedURLException { + super(new URL[]{file.toURI().toURL()}, parent); + this.pluginManager = pluginManager; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return this.findClass(name, true); + } + + protected Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { + Class result = this.classes.get(name); + if (result != null) { + return result; + } + + if (checkGlobal) { + result = this.pluginManager.getClassFromCache(name); + } + + if (result == null && (result = super.findClass(name)) != null) { + this.pluginManager.cacheClass(name, result); + } + this.classes.put(name, result); + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginData.java b/src/main/java/org/sculk/plugin/PluginData.java new file mode 100644 index 0000000..8b29f1a --- /dev/null +++ b/src/main/java/org/sculk/plugin/PluginData.java @@ -0,0 +1,35 @@ +package org.sculk.plugin; + +import lombok.ToString; + +import java.util.List; + +@ToString +public class PluginData{ + + public String name; + public String version; + public String author; + public String main; + public List depends; + + public String getAuthor() { + return this.author; + } + + public String getMain() { + return this.main; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + public List getDepends() { + return this.depends; + } +} \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginLoader.java b/src/main/java/org/sculk/plugin/PluginLoader.java new file mode 100644 index 0000000..fbac861 --- /dev/null +++ b/src/main/java/org/sculk/plugin/PluginLoader.java @@ -0,0 +1,77 @@ +package org.sculk.plugin; + +import lombok.extern.log4j.Log4j2; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +@Log4j2 +public class PluginLoader { + + private final PluginManager pluginManager; + + public PluginLoader(PluginManager pluginManager) { + this.pluginManager = pluginManager; + } + + protected static boolean isJarFile(Path file) { + return file.getFileName().toString().endsWith(".jar"); + } + + protected PluginClassLoader loadClassLoader(PluginData pluginConfig, File pluginJar) { + try { + return new PluginClassLoader(this.pluginManager, this.getClass().getClassLoader(), pluginJar); + } catch (MalformedURLException e) { + log.error("Error while creating class loader(plugin={})", pluginConfig.getName()); + } + return null; + } + + protected Plugin loadPluginJAR(PluginData pluginConfig, File pluginJar, PluginClassLoader loader) { + try { + Class mainClass = loader.loadClass(pluginConfig.getMain()); + if (!Plugin.class.isAssignableFrom(mainClass)) { + return null; + } + + Class castedMain = mainClass.asSubclass(Plugin.class); + Plugin plugin = castedMain.getDeclaredConstructor().newInstance(); + plugin.init(pluginConfig, this.pluginManager.getServer(), pluginJar); + return plugin; + } catch (Exception e) { + log.error("Error while loading plugin main class(main={}, plugin={})", pluginConfig.getMain(), pluginConfig.getName(), e); + } + return null; + } + + protected PluginData loadPluginData(File file, Yaml yaml) { + try (JarFile pluginJar = new JarFile(file)) { + JarEntry configEntry = pluginJar.getJarEntry("redstonecloud.yml"); + if (configEntry == null) { + configEntry = pluginJar.getJarEntry("plugin.yml"); + } + + if (configEntry == null) { + log.warn("Jar file " + file.getName() + " doesnt contain a waterdog.yml or plugin.yml!"); + return null; + } + + try (InputStream fileStream = pluginJar.getInputStream(configEntry)) { + PluginData pluginConfig = yaml.loadAs(fileStream, PluginData.class); + if (pluginConfig.getMain() != null && pluginConfig.getName() != null) { + // Valid plugin.yml, main and name set + return pluginConfig; + } + } + log.warn("Invalid plugin.yml for " + file.getName() + ": main and/or name property missing"); + } catch (Exception e) { + log.error("Can not load plugin files in " + file.getPath(), e); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginManager.java b/src/main/java/org/sculk/plugin/PluginManager.java new file mode 100644 index 0000000..e84fcf9 --- /dev/null +++ b/src/main/java/org/sculk/plugin/PluginManager.java @@ -0,0 +1,249 @@ +package org.sculk.plugin; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; +import org.sculk.Server; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Stream; + +public class PluginManager { + public static final Yaml yamlLoader; + static { + Representer representer = new Representer(new DumperOptions()); + representer.getPropertyUtils().setSkipMissingProperties(true); + yamlLoader = new Yaml(new CustomClassLoaderConstructor(PluginManager.class.getClassLoader(), new LoaderOptions()), representer); + } + + private final Server server; + private final PluginLoader pluginLoader; + + protected final Object2ObjectMap pluginClassLoaders = new Object2ObjectArrayMap<>(); + private final Object2ObjectMap pluginMap = new Object2ObjectArrayMap<>(); + private final Object2ObjectMap> cachedClasses = new Object2ObjectArrayMap<>(); + + private final List> pluginsToLoad = new ObjectArrayList<>(); + + public PluginManager(Server server) { + this.server = server; + this.pluginLoader = new PluginLoader(this); + try { + this.loadPluginsInside(Paths.get(server.getDataPath() + "/plugins/")); + } catch (IOException e) { + server.getLogger().error("Error while filtering plugin files: " + e); + } + } + + private void loadPluginsInside(Path folderPath) throws IOException { + Comparator comparator = (o1, o2) -> { + if (o2.getName().equals(o1.getName())) { + return 0; + } + if (o2.getDepends() == null) { + return 1; + } + return o2.getDepends().contains(o1.getName()) ? -1 : 1; + }; + + Map plugins = new TreeMap<>(comparator); + try (Stream stream = Files.walk(folderPath)){ + stream.filter(Files::isRegularFile).filter(PluginLoader::isJarFile).forEach(jarPath -> { + PluginData config = this.loadPluginConfig(jarPath); + if (config != null) { + plugins.put(config, jarPath); + } + }); + } + plugins.forEach(this::registerClassLoader); + } + + private PluginData loadPluginConfig(Path path) { + if (!Files.isRegularFile(path) || !PluginLoader.isJarFile(path)) { + server.getLogger().warn("Cannot load plugin: Provided file is no jar file: " + path.getFileName()); + return null; + } + + File pluginFile = path.toFile(); + if (!pluginFile.exists()) { + return null; + } + return this.pluginLoader.loadPluginData(pluginFile, yamlLoader); + } + + private PluginClassLoader registerClassLoader(PluginData config, Path path) { + if (this.getPluginByName(config.getName()) != null) { + server.getLogger().warn("Plugin is already loaded: " + config.getName()); + return null; + } + + PluginClassLoader classLoader = this.pluginLoader.loadClassLoader(config, path.toFile()); + if (classLoader != null) { + this.pluginClassLoaders.put(config.getName(), classLoader); + this.pluginsToLoad.add(ObjectObjectImmutablePair.of(config, path)); + server.getLogger().debug("Loaded class loader from " + path.getFileName()); + } + return classLoader; + } + + public void loadAllPlugins() { + for (Pair pair : this.pluginsToLoad) { + this.loadPlugin(pair.key(), pair.value()); + } + this.pluginsToLoad.clear(); + } + + public Plugin loadPlugin(PluginData config, Path path) { + File pluginFile = path.toFile(); + if (this.getPluginByName(config.getName()) != null) { + server.getLogger().warn("Plugin is already loaded: " + config.getName()); + return null; + } + + PluginClassLoader classLoader = this.pluginClassLoaders.get(config.getName()); + if (classLoader == null) { + classLoader = this.registerClassLoader(config, path); + } + + if (classLoader == null) { + return null; + } + + Plugin plugin = this.pluginLoader.loadPluginJAR(config, pluginFile, classLoader); + if (plugin == null) { + return null; + } + + try { + plugin.onLoad(); + } catch (Exception e) { + server.getLogger().error("Failed to load plugin " + config.getName() + ": " + e); + return null; + } + + server.getLogger().info("Loaded plugin " + config.getName() + " successfully!"); + this.pluginMap.put(config.getName(), plugin); + return plugin; + } + + public void enableAllPlugins() { + LinkedList failed = new LinkedList<>(); + + for (Plugin plugin : this.pluginMap.values()) { + if (!this.enablePlugin(plugin, null)) { + failed.add(plugin); + } + } + + if (failed.isEmpty()) { + return; + } + + StringBuilder builder = new StringBuilder("§cFailed to load plugins: §e"); + while (failed.peek() != null) { + Plugin plugin = failed.poll(); + builder.append(plugin.getName()); + if (failed.peek() != null) { + builder.append(", "); + } + } + server.getLogger().warn(builder.toString()); + } + + public boolean enablePlugin(Plugin plugin, String parent) { + if (plugin.isEnabled()) return true; + String pluginName = plugin.getName(); + + if (plugin.getDescription().getDepends() != null) { + for (String depend : plugin.getDescription().getDepends()) { + if (depend.equals(parent)) { + server.getLogger().warn("§cCan not enable plugin " + pluginName + " circular dependency " + parent + "!"); + return false; + } + + Plugin dependPlugin = this.getPluginByName(depend); + if (dependPlugin == null) { + server.getLogger().warn("§cCan not enable plugin " + pluginName + " missing dependency " + depend + "!"); + return false; + } + + if (!dependPlugin.isEnabled() && !this.enablePlugin(dependPlugin, pluginName)) { + return false; + } + } + } + + try { + plugin.setEnabled(true); + } catch (Exception e) { + server.getLogger().error(e.getMessage()); + return false; + } + return true; + } + + public void disableAllPlugins() { + for (Plugin plugin : this.pluginMap.values()) { + server.getLogger().info("Disabling plugin " + plugin.getName() + "!"); + try { + plugin.setEnabled(false); + } catch (Exception e) { + server.getLogger().error(e.getMessage()); + } + } + } + + public Class getClassFromCache(String className) { + Class clazz = this.cachedClasses.get(className); + if (clazz != null) { + return clazz; + } + + for (PluginClassLoader loader : this.pluginClassLoaders.values()) { + try { + if ((clazz = loader.findClass(className, false)) != null) { + return clazz; + } + } catch (ClassNotFoundException e) { + //ignore + } + } + return null; + } + + protected void cacheClass(String className, Class clazz) { + this.cachedClasses.putIfAbsent(className, clazz); + } + + public Map getPluginMap() { + return Collections.unmodifiableMap(this.pluginMap); + } + + public Collection getPlugins() { + return Collections.unmodifiableCollection(this.pluginMap.values()); + } + + public Collection getPluginClassLoaders() { + return Collections.unmodifiableCollection(this.pluginClassLoaders.values()); + } + + public Plugin getPluginByName(String pluginName) { + return this.pluginMap.getOrDefault(pluginName, null); + } + + public Server getServer() { + return this.server; + } +} \ No newline at end of file From 2192ee229914e15befeead37d190dcaf99d0ed3b Mon Sep 17 00:00:00 2001 From: PleaseInsertNameHere <73995049+PleaseInsertNameHere@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:34:08 +0200 Subject: [PATCH 2/4] Try to reduce complexity --- src/main/java/org/sculk/plugin/Plugin.java | 62 +++++++------------ .../org/sculk/plugin/PluginClassLoader.java | 1 + .../java/org/sculk/plugin/PluginData.java | 23 +------ .../java/org/sculk/plugin/PluginLoader.java | 5 +- .../java/org/sculk/plugin/PluginManager.java | 59 ++++++++++-------- 5 files changed, 59 insertions(+), 91 deletions(-) diff --git a/src/main/java/org/sculk/plugin/Plugin.java b/src/main/java/org/sculk/plugin/Plugin.java index 782cef6..113958c 100644 --- a/src/main/java/org/sculk/plugin/Plugin.java +++ b/src/main/java/org/sculk/plugin/Plugin.java @@ -1,6 +1,7 @@ package org.sculk.plugin; import com.google.common.base.Preconditions; +import lombok.Getter; import org.apache.logging.log4j.Logger; import org.sculk.Server; @@ -10,6 +11,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; +@Getter public abstract class Plugin { protected boolean enabled = false; private PluginData description; @@ -19,11 +21,9 @@ public abstract class Plugin { private File dataFolder; private boolean initialized = false; - public Plugin() { - } - protected final void init(PluginData description, Server server, File pluginFile) { Preconditions.checkArgument(!this.initialized, "Plugin has been already initialized!"); + this.initialized = true; this.description = description; this.server = server; @@ -31,8 +31,9 @@ protected final void init(PluginData description, Server server, File pluginFile this.pluginFile = pluginFile; this.dataFolder = new File(Server.getInstance().getDataPath() + "/plugins/" + description.getName().toLowerCase() + "/"); - if (!this.dataFolder.exists()) { - this.dataFolder.mkdirs(); + + if (this.dataFolder.mkdirs()) { + this.logger.info("Created plugin data folder"); } } @@ -45,52 +46,31 @@ public void onDisable() { } public InputStream getResourceFile(String filename) { - try { - JarFile pluginJar = new JarFile(this.pluginFile); - JarEntry entry = pluginJar.getJarEntry(filename); - return pluginJar.getInputStream(entry); + try(JarFile jar = new JarFile(this.pluginFile)) { + JarEntry entry = jar.getJarEntry(filename); + return jar.getInputStream(entry); } catch (IOException e) { + return null; } - return null; - } - - public boolean isEnabled() { - return this.enabled; } public void setEnabled(boolean enabled) { - if (this.enabled == enabled) { - return; - } - this.enabled = enabled; - try { - if (enabled) { - this.onEnable(); - } else { - this.onDisable(); + if (this.enabled != enabled) { + this.enabled = enabled; + + try { + if (enabled) { + this.onEnable(); + } else { + this.onDisable(); + } + } catch (Exception e) { + this.logger.error("Error while enabling/disabling plugin " + this.getName() + ": " + e); } - } catch (Exception e) { - this.logger.error("Error while enabling/disabling plugin " + this.getName() + ": " + e); } } - public PluginData getDescription() { - return this.description; - } - public String getName() { return this.description.getName(); } - - public Server getServer() { - return this.server; - } - - public Logger getLogger() { - return this.logger; - } - - public File getDataFolder() { - return this.dataFolder; - } } \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginClassLoader.java b/src/main/java/org/sculk/plugin/PluginClassLoader.java index 73a2479..8eeb5fe 100644 --- a/src/main/java/org/sculk/plugin/PluginClassLoader.java +++ b/src/main/java/org/sculk/plugin/PluginClassLoader.java @@ -35,6 +35,7 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo if (result == null && (result = super.findClass(name)) != null) { this.pluginManager.cacheClass(name, result); } + this.classes.put(name, result); return result; } diff --git a/src/main/java/org/sculk/plugin/PluginData.java b/src/main/java/org/sculk/plugin/PluginData.java index 8b29f1a..d0460f3 100644 --- a/src/main/java/org/sculk/plugin/PluginData.java +++ b/src/main/java/org/sculk/plugin/PluginData.java @@ -1,11 +1,13 @@ package org.sculk.plugin; +import lombok.Getter; import lombok.ToString; import java.util.List; +@Getter @ToString -public class PluginData{ +public class PluginData { public String name; public String version; @@ -13,23 +15,4 @@ public class PluginData{ public String main; public List depends; - public String getAuthor() { - return this.author; - } - - public String getMain() { - return this.main; - } - - public String getName() { - return this.name; - } - - public String getVersion() { - return this.version; - } - - public List getDepends() { - return this.depends; - } } \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginLoader.java b/src/main/java/org/sculk/plugin/PluginLoader.java index fbac861..d75ec35 100644 --- a/src/main/java/org/sculk/plugin/PluginLoader.java +++ b/src/main/java/org/sculk/plugin/PluginLoader.java @@ -1,7 +1,6 @@ package org.sculk.plugin; import lombok.extern.log4j.Log4j2; -import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.InputStream; @@ -49,7 +48,7 @@ protected Plugin loadPluginJAR(PluginData pluginConfig, File pluginJar, PluginCl return null; } - protected PluginData loadPluginData(File file, Yaml yaml) { + protected PluginData loadPluginData(File file) { try (JarFile pluginJar = new JarFile(file)) { JarEntry configEntry = pluginJar.getJarEntry("redstonecloud.yml"); if (configEntry == null) { @@ -62,7 +61,7 @@ protected PluginData loadPluginData(File file, Yaml yaml) { } try (InputStream fileStream = pluginJar.getInputStream(configEntry)) { - PluginData pluginConfig = yaml.loadAs(fileStream, PluginData.class); + PluginData pluginConfig = PluginManager.yamlLoader.loadAs(fileStream, PluginData.class); if (pluginConfig.getMain() != null && pluginConfig.getName() != null) { // Valid plugin.yml, main and name set return pluginConfig; diff --git a/src/main/java/org/sculk/plugin/PluginManager.java b/src/main/java/org/sculk/plugin/PluginManager.java index e84fcf9..852e2e5 100644 --- a/src/main/java/org/sculk/plugin/PluginManager.java +++ b/src/main/java/org/sculk/plugin/PluginManager.java @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; +import lombok.Getter; import org.sculk.Server; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; @@ -22,12 +23,14 @@ public class PluginManager { public static final Yaml yamlLoader; + static { Representer representer = new Representer(new DumperOptions()); representer.getPropertyUtils().setSkipMissingProperties(true); yamlLoader = new Yaml(new CustomClassLoaderConstructor(PluginManager.class.getClassLoader(), new LoaderOptions()), representer); } + @Getter private final Server server; private final PluginLoader pluginLoader; @@ -59,7 +62,7 @@ private void loadPluginsInside(Path folderPath) throws IOException { }; Map plugins = new TreeMap<>(comparator); - try (Stream stream = Files.walk(folderPath)){ + try (Stream stream = Files.walk(folderPath)) { stream.filter(Files::isRegularFile).filter(PluginLoader::isJarFile).forEach(jarPath -> { PluginData config = this.loadPluginConfig(jarPath); if (config != null) { @@ -80,7 +83,7 @@ private PluginData loadPluginConfig(Path path) { if (!pluginFile.exists()) { return null; } - return this.pluginLoader.loadPluginData(pluginFile, yamlLoader); + return this.pluginLoader.loadPluginData(pluginFile); } private PluginClassLoader registerClassLoader(PluginData config, Path path) { @@ -151,31 +154,41 @@ public void enableAllPlugins() { return; } - StringBuilder builder = new StringBuilder("§cFailed to load plugins: §e"); - while (failed.peek() != null) { - Plugin plugin = failed.poll(); - builder.append(plugin.getName()); - if (failed.peek() != null) { - builder.append(", "); - } - } - server.getLogger().warn(builder.toString()); + server.getLogger().warn("§cFailed to load plugins: §e" + String.join(", ", failed.stream() + .map(Plugin::getName) + .toList())); } public boolean enablePlugin(Plugin plugin, String parent) { - if (plugin.isEnabled()) return true; - String pluginName = plugin.getName(); + if (plugin.isEnabled()) { + return true; + } + + if (plugin.getDescription().getDepends() != null && !this.checkDependencies(plugin, parent)) { + return false; + } + + try { + plugin.setEnabled(true); + } catch (Exception e) { + server.getLogger().error(e.getMessage()); + return false; + } + return true; + } + private boolean checkDependencies(Plugin plugin, String parent) { + String pluginName = plugin.getName(); if (plugin.getDescription().getDepends() != null) { for (String depend : plugin.getDescription().getDepends()) { if (depend.equals(parent)) { - server.getLogger().warn("§cCan not enable plugin " + pluginName + " circular dependency " + parent + "!"); + server.getLogger().warn("§cCannot enable plugin " + pluginName + ", circular dependency " + parent + "!"); return false; } Plugin dependPlugin = this.getPluginByName(depend); if (dependPlugin == null) { - server.getLogger().warn("§cCan not enable plugin " + pluginName + " missing dependency " + depend + "!"); + server.getLogger().warn("§cCannot enable plugin " + pluginName + ", missing dependency " + depend + "!"); return false; } @@ -184,13 +197,6 @@ public boolean enablePlugin(Plugin plugin, String parent) { } } } - - try { - plugin.setEnabled(true); - } catch (Exception e) { - server.getLogger().error(e.getMessage()); - return false; - } return true; } @@ -213,11 +219,13 @@ public Class getClassFromCache(String className) { for (PluginClassLoader loader : this.pluginClassLoaders.values()) { try { - if ((clazz = loader.findClass(className, false)) != null) { + clazz = loader.findClass(className, false); + if (clazz != null) { + this.cachedClasses.put(className, clazz); // Cache the found class return clazz; } } catch (ClassNotFoundException e) { - //ignore + // Ignore } } return null; @@ -243,7 +251,4 @@ public Plugin getPluginByName(String pluginName) { return this.pluginMap.getOrDefault(pluginName, null); } - public Server getServer() { - return this.server; - } } \ No newline at end of file From d89f6312b1ffa0d055751025439a5a67997b0ae2 Mon Sep 17 00:00:00 2001 From: tunikakeks Date: Wed, 21 Aug 2024 21:39:26 +0000 Subject: [PATCH 3/4] feat: plugin api version check --- src/main/java/org/sculk/plugin/PluginData.java | 10 ++++++++++ src/main/java/org/sculk/plugin/PluginLoader.java | 11 +++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/sculk/plugin/PluginData.java b/src/main/java/org/sculk/plugin/PluginData.java index 8b29f1a..04aa7ff 100644 --- a/src/main/java/org/sculk/plugin/PluginData.java +++ b/src/main/java/org/sculk/plugin/PluginData.java @@ -2,6 +2,7 @@ import lombok.ToString; +import java.util.ArrayList; import java.util.List; @ToString @@ -11,6 +12,8 @@ public class PluginData{ public String version; public String author; public String main; + public String api; + public List apis; public List depends; public String getAuthor() { @@ -32,4 +35,11 @@ public String getVersion() { public List getDepends() { return this.depends; } + + public List getApi() { + if(apis == null) apis = new ArrayList<>(); + if(api != null && !apis.contains(api)) apis.add(api); + + return apis; + } } \ No newline at end of file diff --git a/src/main/java/org/sculk/plugin/PluginLoader.java b/src/main/java/org/sculk/plugin/PluginLoader.java index fbac861..0089c95 100644 --- a/src/main/java/org/sculk/plugin/PluginLoader.java +++ b/src/main/java/org/sculk/plugin/PluginLoader.java @@ -1,6 +1,8 @@ package org.sculk.plugin; import lombok.extern.log4j.Log4j2; +import org.sculk.Sculk; +import org.sculk.Server; import org.yaml.snakeyaml.Yaml; import java.io.File; @@ -51,24 +53,25 @@ protected Plugin loadPluginJAR(PluginData pluginConfig, File pluginJar, PluginCl protected PluginData loadPluginData(File file, Yaml yaml) { try (JarFile pluginJar = new JarFile(file)) { - JarEntry configEntry = pluginJar.getJarEntry("redstonecloud.yml"); + JarEntry configEntry = pluginJar.getJarEntry("sculk.yml"); if (configEntry == null) { configEntry = pluginJar.getJarEntry("plugin.yml"); } if (configEntry == null) { - log.warn("Jar file " + file.getName() + " doesnt contain a waterdog.yml or plugin.yml!"); + log.warn("Jar file " + file.getName() + " doesnt contain a sculk.yml or plugin.yml!"); return null; } try (InputStream fileStream = pluginJar.getInputStream(configEntry)) { PluginData pluginConfig = yaml.loadAs(fileStream, PluginData.class); - if (pluginConfig.getMain() != null && pluginConfig.getName() != null) { + if (pluginConfig.getMain() != null && pluginConfig.getName() != null && pluginConfig.getApi().contains(Sculk.CODE_VERSION.replace("v", ""))) { // Valid plugin.yml, main and name set return pluginConfig; } } - log.warn("Invalid plugin.yml for " + file.getName() + ": main and/or name property missing"); + + log.warn("Invalid plugin.yml for " + file.getName() + ": main and/or name property missing, incompactible api version"); } catch (Exception e) { log.error("Can not load plugin files in " + file.getPath(), e); } From 8e1c66ee3f79a06d1ce5e76d711f153b5263f3d9 Mon Sep 17 00:00:00 2001 From: tunikakeks Date: Wed, 21 Aug 2024 21:51:30 +0000 Subject: [PATCH 4/4] comment unfinished line of code --- src/main/java/org/sculk/network/packets/LoginPacketHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/sculk/network/packets/LoginPacketHandler.java b/src/main/java/org/sculk/network/packets/LoginPacketHandler.java index ecbd4e6..3282606 100644 --- a/src/main/java/org/sculk/network/packets/LoginPacketHandler.java +++ b/src/main/java/org/sculk/network/packets/LoginPacketHandler.java @@ -129,7 +129,7 @@ public void onCompletion(Server server) { ServerToClientHandshakePacket serverToClientHandshakePacket = new ServerToClientHandshakePacket(); serverToClientHandshakePacket.handle(this); - serverToClientHandshakePacket.setJwt(); + // serverToClientHandshakePacket.setJwt(); //idk why this one here got pushed, it needs an argument but doesnt have one? weird System.out.println(serverToClientHandshakePacket.getJwt()); session.sendPacket(serverToClientHandshakePacket);