From 052ebc3c7bbd613599d71309060ac7463f399c7a Mon Sep 17 00:00:00 2001 From: swazrgb <65694696+swazrgb@users.noreply.github.com> Date: Sun, 24 May 2020 23:07:37 +0200 Subject: [PATCH 1/2] client: Clean up ExternalPluginManager & friends --- .../client/config/OpenOSRSConfig.java | 3 +- .../plugins/ExternalPf4jPluginManager.java | 370 ++++++++++++++ .../plugins/ExternalPluginClasspath.java | 13 + .../plugins/ExternalPluginFileFilter.java | 61 +++ .../client/plugins/ExternalPluginManager.java | 452 ++---------------- .../client/plugins/config/PluginListItem.java | 5 +- 6 files changed, 483 insertions(+), 421 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/ExternalPf4jPluginManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginClasspath.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginFileFilter.java diff --git a/runelite-client/src/main/java/net/runelite/client/config/OpenOSRSConfig.java b/runelite-client/src/main/java/net/runelite/client/config/OpenOSRSConfig.java index 82887aa36e..1aa56505cf 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/OpenOSRSConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/config/OpenOSRSConfig.java @@ -30,6 +30,7 @@ import java.awt.Color; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import net.runelite.client.plugins.ExternalPluginManager; @ConfigGroup("openosrs") public interface OpenOSRSConfig extends Config @@ -347,7 +348,7 @@ public interface OpenOSRSConfig extends Config ) default String getExternalRepositories() { - return "OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/"; + return ExternalPluginManager.DEFAULT_PLUGIN_REPO; } @ConfigItem( diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPf4jPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPf4jPluginManager.java new file mode 100644 index 0000000000..ca62c22289 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPf4jPluginManager.java @@ -0,0 +1,370 @@ +package net.runelite.client.plugins; + +import java.io.Closeable; +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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLite; +import net.runelite.client.RuneLiteProperties; +import org.pf4j.BasePluginLoader; +import org.pf4j.CompoundPluginLoader; +import org.pf4j.CompoundPluginRepository; +import org.pf4j.DefaultPluginManager; +import org.pf4j.DependencyResolver; +import org.pf4j.DevelopmentPluginRepository; +import org.pf4j.JarPluginLoader; +import org.pf4j.JarPluginRepository; +import org.pf4j.ManifestPluginDescriptorFinder; +import org.pf4j.PluginAlreadyLoadedException; +import org.pf4j.PluginDescriptorFinder; +import org.pf4j.PluginLoader; +import org.pf4j.PluginRepository; +import org.pf4j.PluginRuntimeException; +import org.pf4j.PluginState; +import org.pf4j.PluginStateEvent; +import org.pf4j.PluginWrapper; +import org.pf4j.RuntimeMode; + +/** + * A pf4j {@link DefaultPluginManager} which can load openosrs external plugins, either from jar files in a directory, + * or from locally compiled classes depending on whether {@link ExternalPluginManager#isDevelopmentMode()} is true + */ +@Slf4j +class ExternalPf4jPluginManager extends DefaultPluginManager +{ + private final ExternalPluginManager externalPluginManager; + + public ExternalPf4jPluginManager(ExternalPluginManager externalPluginManager) + { + super(RuneLiteProperties.getPluginPath() != null ? Paths.get(RuneLiteProperties.getPluginPath()) + : RuneLite.EXTERNALPLUGIN_DIR.toPath()); + this.externalPluginManager = externalPluginManager; + } + + @Override + protected PluginDescriptorFinder createPluginDescriptorFinder() + { + return new ManifestPluginDescriptorFinder() + { + protected Path getManifestPath(Path pluginPath) + { + if (isDevelopment()) + { + // The superclass performs a find, which is slow in development mode since we're pointing + // at a sources directory, which can have a lot of files. The external plugin template + // will always output the manifest at the following location, so we can hardcode this path. + return pluginPath.resolve(ExternalPluginManager.DEVELOPMENT_MANIFEST_PATH); + } + + return super.getManifestPath(pluginPath); + } + }; + } + + @Override + protected PluginRepository createPluginRepository() + { + CompoundPluginRepository compoundPluginRepository = new CompoundPluginRepository(); + + if (isNotDevelopment()) + { + JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot()) + { + /** + * Identical to {@link JarPluginRepository#getPluginPaths()} but does not sort by modtime + * @return the list of plugin paths + */ + @Override + public List getPluginPaths() + { + File[] files = pluginsRoot.toFile().listFiles(filter); + + if ((files == null) || files.length == 0) + { + return Collections.emptyList(); + } + + List paths = new ArrayList<>(files.length); + for (File file : files) + { + paths.add(file.toPath()); + } + + return paths; + } + }; + + compoundPluginRepository.add(jarPluginRepository); + } + + if (isDevelopment()) + { + for (String developmentPluginPath : RuneLiteProperties.getPluginDevelopmentPath()) + { + DevelopmentPluginRepository developmentPluginRepository = new DevelopmentPluginRepository(Paths.get(developmentPluginPath)) + { + @Override + public boolean deletePluginPath(Path pluginPath) + { + // Do nothing, because we'd be deleting our sources! + return filter.accept(pluginPath.toFile()); + } + }; + + developmentPluginRepository.setFilter(new ExternalPluginFileFilter()); + compoundPluginRepository.add(developmentPluginRepository); + } + } + + return compoundPluginRepository; + } + + @Override + protected PluginLoader createPluginLoader() + { + return new CompoundPluginLoader() + .add(new BasePluginLoader(this, new ExternalPluginClasspath()), this::isDevelopment) + .add(new JarPluginLoader(this), this::isNotDevelopment); + } + + @Override + public void loadPlugins() + { + if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) + { + log.warn("No '{}' root", pluginsRoot); + return; + } + + List pluginPaths = pluginRepository.getPluginPaths(); + + if (pluginPaths.isEmpty()) + { + log.warn("No plugins"); + return; + } + + log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths); + + for (Path pluginPath : pluginPaths) + { + try + { + loadPluginFromPath(pluginPath); + } + catch (PluginRuntimeException e) + { + if (!(e instanceof PluginAlreadyLoadedException)) + { + log.error("Could not load plugin {}", pluginPath, e); + } + } + } + + try + { + resolvePlugins(); + } + catch (PluginRuntimeException e) + { + if (e instanceof DependencyResolver.DependenciesNotFoundException) + { + throw e; + } + + log.error("Could not resolve plugins", e); + } + } + + @Override + protected void resolvePlugins() + { + // retrieves the plugins descriptors + List descriptors = new ArrayList<>(); + for (PluginWrapper plugin : plugins.values()) + { + descriptors.add(plugin.getDescriptor()); + } + + // retrieves the plugins descriptors from the resolvedPlugins list. This allows to load plugins that have already loaded dependencies. + for (PluginWrapper plugin : resolvedPlugins) + { + descriptors.add(plugin.getDescriptor()); + } + + DependencyResolver.Result result = dependencyResolver.resolve(descriptors); + + if (result.hasCyclicDependency()) + { + throw new DependencyResolver.CyclicDependencyException(); + } + + List notFoundDependencies = result.getNotFoundDependencies(); + if (!notFoundDependencies.isEmpty()) + { + throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies); + } + + List wrongVersionDependencies = result.getWrongVersionDependencies(); + if (!wrongVersionDependencies.isEmpty()) + { + throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies); + } + + List sortedPlugins = result.getSortedPlugins(); + + // move plugins from "unresolved" to "resolved" + for (String pluginId : sortedPlugins) + { + PluginWrapper pluginWrapper = plugins.get(pluginId); + + //The plugin is already resolved. Don't put a copy in the resolvedPlugins. + if (resolvedPlugins.contains(pluginWrapper)) + { + continue; + } + + if (unresolvedPlugins.remove(pluginWrapper)) + { + PluginState pluginState = pluginWrapper.getPluginState(); + if (pluginState != PluginState.DISABLED) + { + pluginWrapper.setPluginState(PluginState.RESOLVED); + } + + resolvedPlugins.add(pluginWrapper); + + firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); + } + } + } + + @Override + public RuntimeMode getRuntimeMode() + { + return ExternalPluginManager.isDevelopmentMode() ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT; + } + + @Override + public PluginState stopPlugin(String pluginId) + { + if (!plugins.containsKey(pluginId)) + { + throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); + } + + PluginWrapper pluginWrapper = getPlugin(pluginId); + org.pf4j.PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + PluginState pluginState = pluginWrapper.getPluginState(); + if (PluginState.STOPPED == pluginState) + { + log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor)); + return PluginState.STOPPED; + } + + // test for disabled plugin + if (PluginState.DISABLED == pluginState) + { + // do nothing + return pluginState; + } + + pluginWrapper.getPlugin().stop(); + pluginWrapper.setPluginState(PluginState.STOPPED); + startedPlugins.remove(pluginWrapper); + + firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); + + return pluginWrapper.getPluginState(); + } + + @Override + public boolean unloadPlugin(String pluginId) + { + try + { + PluginState pluginState = stopPlugin(pluginId); + if (PluginState.STARTED == pluginState) + { + return false; + } + + PluginWrapper pluginWrapper = getPlugin(pluginId); + + // remove the plugin + plugins.remove(pluginId); + getResolvedPlugins().remove(pluginWrapper); + + firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); + + // remove the classloader + Map pluginClassLoaders = getPluginClassLoaders(); + if (pluginClassLoaders.containsKey(pluginId)) + { + ClassLoader classLoader = pluginClassLoaders.remove(pluginId); + if (classLoader instanceof Closeable) + { + try + { + ((Closeable) classLoader).close(); + } + catch (IOException e) + { + throw new PluginRuntimeException(e, "Cannot close classloader"); + } + } + } + + return true; + } + catch (IllegalArgumentException e) + { + // ignore not found exceptions because this method is recursive + } + + return false; + } + + @Override + public boolean deletePlugin(String pluginId) + { + if (!plugins.containsKey(pluginId)) + { + throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); + } + + PluginWrapper pluginWrapper = getPlugin(pluginId); + // stop the plugin if it's started + PluginState pluginState = stopPlugin(pluginId); + if (PluginState.STARTED == pluginState) + { + log.error("Failed to stop plugin '{}' on delete", pluginId); + return false; + } + + // get an instance of plugin before the plugin is unloaded + // for reason see https://github.com/pf4j/pf4j/issues/309 + + org.pf4j.Plugin plugin = pluginWrapper.getPlugin(); + + if (!unloadPlugin(pluginId)) + { + log.error("Failed to unload plugin '{}' on delete", pluginId); + return false; + } + + // notify the plugin as it's deleted + plugin.delete(); + + Path pluginPath = pluginWrapper.getPluginPath(); + + return pluginRepository.deletePluginPath(pluginPath); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginClasspath.java b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginClasspath.java new file mode 100644 index 0000000000..841f1b3be7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginClasspath.java @@ -0,0 +1,13 @@ +package net.runelite.client.plugins; + +import org.pf4j.DevelopmentPluginClasspath; + +class ExternalPluginClasspath extends DevelopmentPluginClasspath +{ + static final String GRADLE_DEPS_PATH = "build/deps"; + + ExternalPluginClasspath() + { + addJarsDirectories(GRADLE_DEPS_PATH); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginFileFilter.java b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginFileFilter.java new file mode 100644 index 0000000000..c9cf9f9878 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginFileFilter.java @@ -0,0 +1,61 @@ +package net.runelite.client.plugins; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.List; + +/** + * Determines whether a {@link File} is an external plugin folder. To be considered a plugin a folder must: + *

+ * * Must not be a blacklisted name + * * Have a {@code .gradle.kts} file in the root named after the folder + * * Have a {@code MANIFEST.MF} located at {@code build/tmp/jar/MANIFEST.MF} + */ +public class ExternalPluginFileFilter implements FileFilter +{ + private static final List blacklist = Arrays.asList( + ".git", + "build", + "target", + "release" + ); + + private static final List buildFiles = Arrays.asList( + "%s.gradle.kts", + "%s.gradle" + ); + + @Override + public boolean accept(File pathName) + { + // Check if this path looks like a plugin development directory + if (!pathName.isDirectory()) + { + return false; + } + + String dirName = pathName.getName(); + if (blacklist.contains(dirName)) + { + return false; + } + + // Check if the plugin directory has a MANIFEST.MF which si required for loading + if (!new File(pathName, ExternalPluginManager.DEVELOPMENT_MANIFEST_PATH).exists()) + { + return false; + } + + // By convention plugins their directory is $name and they have a $name.gradle.kts or $name.gradle file in their root + for (String buildFile : buildFiles) + { + if (new File(pathName, String.format(buildFile, dirName)).exists()) + { + return true; + } + } + + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java index a4666b4e81..e03e419d3e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java @@ -30,19 +30,11 @@ import com.google.inject.CreationException; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; -import java.io.Closeable; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -82,26 +74,11 @@ import net.runelite.client.util.Groups; import net.runelite.client.util.MiscUtils; import net.runelite.client.util.SwingUtil; import org.jgroups.Message; -import org.pf4j.BasePluginLoader; -import org.pf4j.CompoundPluginLoader; -import org.pf4j.CompoundPluginRepository; import org.pf4j.DefaultPluginManager; import org.pf4j.DependencyResolver; -import org.pf4j.DevelopmentPluginClasspath; -import org.pf4j.DevelopmentPluginRepository; -import org.pf4j.JarPluginLoader; -import org.pf4j.JarPluginRepository; -import org.pf4j.ManifestPluginDescriptorFinder; -import org.pf4j.PluginAlreadyLoadedException; import org.pf4j.PluginDependency; -import org.pf4j.PluginDescriptorFinder; -import org.pf4j.PluginLoader; -import org.pf4j.PluginRepository; import org.pf4j.PluginRuntimeException; -import org.pf4j.PluginState; -import org.pf4j.PluginStateEvent; import org.pf4j.PluginWrapper; -import org.pf4j.RuntimeMode; import org.pf4j.update.DefaultUpdateRepository; import org.pf4j.update.PluginInfo; import org.pf4j.update.UpdateManager; @@ -112,6 +89,9 @@ import org.pf4j.update.VerifyException; @Singleton public class ExternalPluginManager { + public static final String DEFAULT_PLUGIN_REPO = "OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/"; + static final String DEVELOPMENT_MANIFEST_PATH = "build/tmp/jar/MANIFEST.MF"; + public static ArrayList pluginClassLoaders = new ArrayList<>(); private final PluginManager runelitePluginManager; private org.pf4j.PluginManager externalPluginManager; @@ -121,14 +101,14 @@ public class ExternalPluginManager private final EventBus eventBus; private final ConfigManager configManager; private final Map pluginsMap = new HashMap<>(); - @Getter - private final boolean developmentMode = RuneLiteProperties.getPluginDevelopmentPath().length > 0; + @Getter(AccessLevel.PUBLIC) + private static final boolean developmentMode = RuneLiteProperties.getPluginDevelopmentPath().length > 0; @Getter(AccessLevel.PUBLIC) private final Map> pluginsInfoMap = new HashMap<>(); private final Groups groups; @Getter(AccessLevel.PUBLIC) private UpdateManager updateManager; - private Set pluginTypes = Set.of(PluginType.values()); + private final Set pluginTypes = Set.of(PluginType.values()); @Inject public ExternalPluginManager( @@ -155,376 +135,7 @@ public class ExternalPluginManager private void initPluginManager() { - externalPluginManager = new DefaultPluginManager( - RuneLiteProperties.getPluginPath() != null ? Paths.get(RuneLiteProperties.getPluginPath()) - : EXTERNALPLUGIN_DIR.toPath()) - { - @Override - protected PluginDescriptorFinder createPluginDescriptorFinder() - { - return new ManifestPluginDescriptorFinder() - { - protected Path getManifestPath(Path pluginPath) - { - if (isDevelopment()) - { - // The superclass performs a find, which is slow in development mode since we're pointing - // at a sources directory, which can have a lot of files. The external plugin template - // will always output the manifest at the following location, so we can hardcode this path. - return pluginPath.resolve("build/tmp/jar/MANIFEST.MF"); - } - - return super.getManifestPath(pluginPath); - } - }; - } - - @Override - protected PluginRepository createPluginRepository() - { - CompoundPluginRepository compoundPluginRepository = new CompoundPluginRepository(); - - if (isNotDevelopment()) - { - JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot()) - { - @Override - public List getPluginPaths() - { - File[] files = pluginsRoot.toFile().listFiles(filter); - - if ((files == null) || files.length == 0) - { - return Collections.emptyList(); - } - - List paths = new ArrayList<>(files.length); - for (File file : files) - { - paths.add(file.toPath()); - } - - return paths; - } - }; - - compoundPluginRepository.add(jarPluginRepository); - } - - if (isDevelopment()) - { - FileFilter pluginsFilter = new FileFilter() - { - private final List blacklist = Arrays.asList( - ".git", - "build", - "target" - ); - - private final List buildFiles = Arrays.asList( - "%s.gradle.kts", - "%s.gradle" - ); - - @Override - public boolean accept(File pathName) - { - // Check if this path looks like a plugin development directory - if (!pathName.isDirectory()) - { - return false; - } - - String dirName = pathName.getName(); - if (blacklist.contains(dirName)) - { - return false; - } - - boolean isPlugin = false; - - // By convention plugins their directory is $name and they have a $name.gradle.kts or $name.gradle file in their root - for (String buildFile : buildFiles) - { - if (new File(pathName, String.format(buildFile, dirName)).exists()) - { - isPlugin = true; - break; - } - } - - // It is a plugin directory, but we should also check if it can actually be loaded - if (!new File(pathName, "build/tmp/jar/MANIFEST.MF").exists()) - { - return false; - } - - return isPlugin; - } - }; - - for (String developmentPluginPath : RuneLiteProperties.getPluginDevelopmentPath()) - { - DevelopmentPluginRepository developmentPluginRepository = new DevelopmentPluginRepository(Paths.get(developmentPluginPath)) - { - @Override - public boolean deletePluginPath(Path pluginPath) - { - // Do nothing, because we'd be deleting our sources! - return filter.accept(pluginPath.toFile()); - } - }; - - developmentPluginRepository.setFilter(pluginsFilter); - compoundPluginRepository.add(developmentPluginRepository); - } - } - - return compoundPluginRepository; - } - - @Override - protected PluginLoader createPluginLoader() - { - return new CompoundPluginLoader() - .add(new BasePluginLoader(this, new DevelopmentPluginClasspath().addJarsDirectories("build/deps")), this::isDevelopment) - .add(new JarPluginLoader(this), this::isNotDevelopment); - } - - @Override - public void loadPlugins() - { - if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) - { - log.warn("No '{}' root", pluginsRoot); - return; - } - - List pluginPaths = pluginRepository.getPluginPaths(); - - if (pluginPaths.isEmpty()) - { - log.warn("No plugins"); - return; - } - - log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths); - - for (Path pluginPath : pluginPaths) - { - try - { - loadPluginFromPath(pluginPath); - } - catch (PluginRuntimeException e) - { - if (!(e instanceof PluginAlreadyLoadedException)) - { - log.error("Could not load plugin {}", pluginPath, e); - } - } - } - - try - { - resolvePlugins(); - } - catch (PluginRuntimeException e) - { - if (e instanceof DependencyResolver.DependenciesNotFoundException) - { - throw e; - } - - log.error(e.getMessage(), e); - } - } - - @Override - protected void resolvePlugins() - { - // retrieves the plugins descriptors - List descriptors = new ArrayList<>(); - for (PluginWrapper plugin : plugins.values()) - { - descriptors.add(plugin.getDescriptor()); - } - - // retrieves the plugins descriptors from the resolvedPlugins list. This allows to load plugins that have already loaded dependencies. - for (PluginWrapper plugin : resolvedPlugins) - { - descriptors.add(plugin.getDescriptor()); - } - - DependencyResolver.Result result = dependencyResolver.resolve(descriptors); - - if (result.hasCyclicDependency()) - { - throw new DependencyResolver.CyclicDependencyException(); - } - - List notFoundDependencies = result.getNotFoundDependencies(); - if (!notFoundDependencies.isEmpty()) - { - throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies); - } - - List wrongVersionDependencies = result.getWrongVersionDependencies(); - if (!wrongVersionDependencies.isEmpty()) - { - throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies); - } - - List sortedPlugins = result.getSortedPlugins(); - - // move plugins from "unresolved" to "resolved" - for (String pluginId : sortedPlugins) - { - PluginWrapper pluginWrapper = plugins.get(pluginId); - - //The plugin is already resolved. Don't put a copy in the resolvedPlugins. - if (resolvedPlugins.contains(pluginWrapper)) - { - continue; - } - - if (unresolvedPlugins.remove(pluginWrapper)) - { - PluginState pluginState = pluginWrapper.getPluginState(); - if (pluginState != PluginState.DISABLED) - { - pluginWrapper.setPluginState(PluginState.RESOLVED); - } - - resolvedPlugins.add(pluginWrapper); - - firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); - } - } - } - - @Override - public RuntimeMode getRuntimeMode() - { - return developmentMode ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT; - } - - @Override - public PluginState stopPlugin(String pluginId) - { - if (!plugins.containsKey(pluginId)) - { - throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); - } - - PluginWrapper pluginWrapper = getPlugin(pluginId); - org.pf4j.PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); - PluginState pluginState = pluginWrapper.getPluginState(); - if (PluginState.STOPPED == pluginState) - { - log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor)); - return PluginState.STOPPED; - } - - // test for disabled plugin - if (PluginState.DISABLED == pluginState) - { - // do nothing - return pluginState; - } - - pluginWrapper.getPlugin().stop(); - pluginWrapper.setPluginState(PluginState.STOPPED); - startedPlugins.remove(pluginWrapper); - - firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); - - return pluginWrapper.getPluginState(); - } - - @Override - public boolean unloadPlugin(String pluginId) - { - try - { - PluginState pluginState = stopPlugin(pluginId); - if (PluginState.STARTED == pluginState) - { - return false; - } - - PluginWrapper pluginWrapper = getPlugin(pluginId); - - // remove the plugin - plugins.remove(pluginId); - getResolvedPlugins().remove(pluginWrapper); - - firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState)); - - // remove the classloader - Map pluginClassLoaders = getPluginClassLoaders(); - if (pluginClassLoaders.containsKey(pluginId)) - { - ClassLoader classLoader = pluginClassLoaders.remove(pluginId); - if (classLoader instanceof Closeable) - { - try - { - ((Closeable) classLoader).close(); - } - catch (IOException e) - { - throw new PluginRuntimeException(e, "Cannot close classloader"); - } - } - } - - return true; - } - catch (IllegalArgumentException e) - { - // ignore not found exceptions because this method is recursive - } - - return false; - } - - @Override - public boolean deletePlugin(String pluginId) - { - if (!plugins.containsKey(pluginId)) - { - throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); - } - - PluginWrapper pluginWrapper = getPlugin(pluginId); - // stop the plugin if it's started - PluginState pluginState = stopPlugin(pluginId); - if (PluginState.STARTED == pluginState) - { - log.error("Failed to stop plugin '{}' on delete", pluginId); - return false; - } - - // get an instance of plugin before the plugin is unloaded - // for reason see https://github.com/pf4j/pf4j/issues/309 - - org.pf4j.Plugin plugin = pluginWrapper.getPlugin(); - - if (!unloadPlugin(pluginId)) - { - log.error("Failed to unload plugin '{}' on delete", pluginId); - return false; - } - - // notify the plugin as it's deleted - plugin.delete(); - - Path pluginPath = pluginWrapper.getPluginPath(); - - return pluginRepository.deletePluginPath(pluginPath); - } - }; + externalPluginManager = new ExternalPf4jPluginManager(this); externalPluginManager.setSystemVersion(SYSTEM_VERSION); } @@ -595,8 +206,10 @@ public class ExternalPluginManager startExternalPluginManager(); } - - log.error("{}", ex.getMessage()); + else + { + log.error("Could not load plugins", ex); + } } } @@ -618,13 +231,14 @@ public class ExternalPluginManager { duplicateCheck(); log.debug("Trying to load new format: {}", openOSRSConfig.getExternalRepositories()); + repositories.clear(); + for (String keyval : openOSRSConfig.getExternalRepositories().split(";")) { String[] split = keyval.split("\\|"); if (split.length != 2) { log.debug("Split length invalid."); - repositories.clear(); return false; } String id = split[0]; @@ -677,12 +291,12 @@ public class ExternalPluginManager } catch (MalformedURLException e) { - e.printStackTrace(); + log.error("Old repository format contained malformed url", e); } catch (StringIndexOutOfBoundsException e) { log.error("Error loading external repositories. They have been reset."); - openOSRSConfig.setExternalRepositories("OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/"); + openOSRSConfig.setExternalRepositories(DEFAULT_PLUGIN_REPO); } updateManager = new UpdateManager(externalPluginManager, repositories); @@ -696,7 +310,7 @@ public class ExternalPluginManager } catch (MalformedURLException e) { - log.error("Repostitory could not be added"); + log.error("GitHub repostitory could not be added (owner={}, name={})", owner, name, e); } } @@ -761,24 +375,25 @@ public class ExternalPluginManager strings.add(s); } - if (duplicates) + if (!duplicates) { - StringBuilder sb = new StringBuilder(); - - for (String string : strings) - { - sb.append(string); - sb.append(";"); - } - - sb.deleteCharAt(sb.lastIndexOf(";")); - String duplicateFix = sb.toString(); - - log.info("Duplicate Repos detected, setting them to: {}", duplicateFix); - openOSRSConfig.setExternalRepositories(duplicateFix); + log.info("No duplicates found."); return; } - log.info("No duplicates found."); + + StringBuilder sb = new StringBuilder(); + + for (String string : strings) + { + sb.append(string); + sb.append(";"); + } + + sb.deleteCharAt(sb.lastIndexOf(";")); + String duplicateFix = sb.toString(); + + log.info("Duplicate Repos detected, setting them to: {}", duplicateFix); + openOSRSConfig.setExternalRepositories(duplicateFix); } private void scanAndInstantiate(List plugins, boolean init, boolean initConfig) @@ -855,7 +470,7 @@ public class ExternalPluginManager } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + log.warn("Could not instantiate external plugin", e); } }); } @@ -1388,4 +1003,5 @@ public class ExternalPluginManager break; } } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java index b3143c1f38..17891ce08b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListItem.java @@ -132,7 +132,8 @@ public class PluginListItem extends JPanel Map> pluginsInfoMap = externalPluginManager.getPluginsInfoMap(); - if (RuneLiteProperties.getLauncherVersion() == null && pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName())) + if (ExternalPluginManager.isDevelopmentMode() || + (RuneLiteProperties.getLauncherVersion() == null && pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName()))) { JButton hotSwapButton = new JButton(REFRESH_ICON); hotSwapButton.setRolloverIcon(REFRESH_ICON_HOVER); @@ -160,7 +161,7 @@ public class PluginListItem extends JPanel protected void done() { // In development mode our plugins will be loaded directly from sources, so we don't need to prompt - if (!externalPluginManager.isDevelopmentMode()) + if (!ExternalPluginManager.isDevelopmentMode()) { JOptionPane.showMessageDialog(ClientUI.getFrame(), pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`", From 0890be9c30000f123d18506fa9fdd790e856ee0d Mon Sep 17 00:00:00 2001 From: swazrgb <65694696+swazrgb@users.noreply.github.com> Date: Sun, 24 May 2020 23:38:39 +0200 Subject: [PATCH 2/2] Also clear repositories when split length is invalid --- .../net/runelite/client/plugins/ExternalPluginManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java index e03e419d3e..6fd06db131 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/ExternalPluginManager.java @@ -238,7 +238,8 @@ public class ExternalPluginManager String[] split = keyval.split("\\|"); if (split.length != 2) { - log.debug("Split length invalid."); + log.debug("Split length invalid: {}", keyval); + repositories.clear(); return false; } String id = split[0];