From a3becd39e2f22abd5f6f3faebe74309eaac0a049 Mon Sep 17 00:00:00 2001 From: Owain van Brakel Date: Mon, 22 Feb 2021 18:38:22 +0100 Subject: [PATCH 1/3] client: Allow loading externals from source --- .../java/com/openosrs/client/OpenOSRS.java | 23 +++++++- .../OPRSExternalPf4jPluginManager.java | 41 +++++++++++--- .../plugins/OPRSExternalPluginManager.java | 54 ++++++++++++------- 3 files changed, 92 insertions(+), 26 deletions(-) diff --git a/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java b/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java index 4d64898bce..53e83b4869 100644 --- a/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java +++ b/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java @@ -1,22 +1,28 @@ package com.openosrs.client; +import com.google.common.base.Strings; import java.io.File; import java.io.IOException; import java.util.Properties; import java.util.UUID; +import lombok.AccessLevel; +import lombok.Getter; public class OpenOSRS { public static final File OPENOSRS_DIR = new File(System.getProperty("user.home"), ".openosrs"); public static final File EXTERNALPLUGIN_DIR = new File(OPENOSRS_DIR, "plugins"); + public static final String PLUGIN_DEVELOPMENT_PATH = "plugin.development.path"; public static final String SYSTEM_VERSION; public static final String SYSTEM_API_VERSION; + @Getter(AccessLevel.PACKAGE) + private static final Properties properties = new Properties(); + public static String uuid = UUID.randomUUID().toString(); static { - Properties properties = new Properties(); try { properties.load(OpenOSRS.class.getResourceAsStream("/openosrs.properties")); @@ -25,10 +31,25 @@ public class OpenOSRS { e.printStackTrace(); } + SYSTEM_VERSION = properties.getProperty("oprs.version", "0.0.0"); SYSTEM_API_VERSION = properties.getProperty("oprs.api.version"); } + public static String[] getPluginDevelopmentPath() + { + // First check if property supplied as environment variable PLUGIN_DEVELOPMENT_PATHS + String developmentPluginPaths = System.getenv(PLUGIN_DEVELOPMENT_PATH.replace('.', '_').toUpperCase()); + + if (Strings.isNullOrEmpty(developmentPluginPaths)) + { + // Otherwise check the property file + developmentPluginPaths = properties.getProperty(PLUGIN_DEVELOPMENT_PATH); + } + + return Strings.isNullOrEmpty(developmentPluginPaths) ? new String[0] : developmentPluginPaths.split(";"); + } + public static void preload() { } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java index 30abbfa8eb..cbbe334f48 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java @@ -5,6 +5,7 @@ import java.io.Closeable; 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.HashSet; @@ -19,6 +20,7 @@ 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; @@ -35,12 +37,9 @@ import org.pf4j.RuntimeMode; @Slf4j class OPRSExternalPf4jPluginManager extends DefaultPluginManager { - private final OPRSExternalPluginManager externalPluginManager; - - public OPRSExternalPf4jPluginManager(OPRSExternalPluginManager externalPluginManager) + public OPRSExternalPf4jPluginManager() { super(OpenOSRS.EXTERNALPLUGIN_DIR.toPath()); - this.externalPluginManager = externalPluginManager; } @Override @@ -68,8 +67,30 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager { CompoundPluginRepository compoundPluginRepository = new CompoundPluginRepository(); - JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot()); - compoundPluginRepository.add(jarPluginRepository); + if (isNotDevelopment()) + { + JarPluginRepository jarPluginRepository = new JarPluginRepository(getPluginsRoot()); + compoundPluginRepository.add(jarPluginRepository); + } + + if (isDevelopment()) + { + for (String developmentPluginPath : OpenOSRS.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 OPRSExternalPluginFileFilter()); + compoundPluginRepository.add(developmentPluginRepository); + } + } return compoundPluginRepository; } @@ -121,6 +142,12 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager { if (!(e instanceof PluginAlreadyLoadedException)) { + if (!OPRSExternalPluginManager.isDevelopmentMode()) + { + String plugin = pluginPath.toString().substring(pluginsRoots.get(0).toString().length() + 1); + duplicatePlugins.add(plugin); + } + log.error("Could not load plugin {}", pluginPath, e); } } @@ -220,7 +247,7 @@ class OPRSExternalPf4jPluginManager extends DefaultPluginManager @Override public RuntimeMode getRuntimeMode() { - return RuntimeMode.DEPLOYMENT; + return OPRSExternalPluginManager.isDevelopmentMode() ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT; } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java index dd6c86d1ee..81c63f272d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java @@ -33,6 +33,7 @@ import com.google.inject.CreationException; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; +import com.openosrs.client.OpenOSRS; import static com.openosrs.client.OpenOSRS.EXTERNALPLUGIN_DIR; import static com.openosrs.client.OpenOSRS.SYSTEM_API_VERSION; import com.openosrs.client.config.OpenOSRSConfig; @@ -108,6 +109,8 @@ public class OPRSExternalPluginManager private final ConfigManager configManager; private final Map pluginsMap = new HashMap<>(); @Getter(AccessLevel.PUBLIC) + private static final boolean developmentMode = OpenOSRS.getPluginDevelopmentPath().length > 0; + @Getter(AccessLevel.PUBLIC) private final Map> pluginsInfoMap = new HashMap<>(); private final Groups groups; @Getter(AccessLevel.PUBLIC) @@ -143,7 +146,7 @@ public class OPRSExternalPluginManager private void initPluginManager() { - externalPluginManager = new OPRSExternalPf4jPluginManager(this); + externalPluginManager = new OPRSExternalPf4jPluginManager(); externalPluginManager.setSystemVersion(SYSTEM_API_VERSION); } @@ -826,29 +829,39 @@ public class OPRSExternalPluginManager try { - PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId); - - // Null version returns the last release version of this plugin for given system version - if (latest == null) + if (!developmentMode) { - try + PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId); + + // Null version returns the last release version of this plugin for given system version + if (latest == null) { - SwingUtil.syncExec(() -> - JOptionPane.showMessageDialog(ClientUI.getFrame(), - pluginId + " is outdated and cannot be installed", - "Installation error", - JOptionPane.ERROR_MESSAGE)); - } - catch (InvocationTargetException | InterruptedException ignored) - { - return false; + try + { + SwingUtil.syncExec(() -> + JOptionPane.showMessageDialog(ClientUI.getFrame(), + pluginId + " is outdated and cannot be installed", + "Installation error", + JOptionPane.ERROR_MESSAGE)); + } + catch (InvocationTargetException | InterruptedException ignored) + { + return false; + } + + return true; } - return true; + updateManager.installPlugin(pluginId, null); + scanAndInstantiate(loadPlugin(pluginId), true, true); + } + else + { + // In development mode our plugin will already be present in a repository, so we can just load it + externalPluginManager.loadPlugins(); + externalPluginManager.startPlugin(pluginId); } - updateManager.installPlugin(pluginId, null); - scanAndInstantiate(loadPlugin(pluginId), true, true); ExternalPluginsChanged event = new ExternalPluginsChanged(null); eventBus.post(event); groups.broadcastSring("STARTEXTERNAL;" + pluginId); @@ -908,6 +921,11 @@ public class OPRSExternalPluginManager log.info("Not updating external plugins since there is more than 1 client open"); return; } + else if (developmentMode) + { + log.info("Not updating because we're running in developer mode"); + return; + } OpenOSRSSplashScreen.stage(.59, "Updating external plugins"); From 185826b835e7ccef8c896bb3e701affd36251284 Mon Sep 17 00:00:00 2001 From: Owain van Brakel Date: Mon, 22 Feb 2021 19:00:32 +0100 Subject: [PATCH 2/3] config: Reload button for external plugins in development mode --- .../client/plugins/config/PluginListItem.java | 67 +++++++++++++++++- .../plugins/config/PluginListPanel.java | 6 +- .../client/plugins/config/refresh.png | Bin 0 -> 211 bytes 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/config/refresh.png 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 767fddbc1a..6c3099fa36 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 @@ -37,17 +37,23 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import lombok.Getter; +import net.runelite.client.RuneLiteProperties; import net.runelite.client.externalplugins.ExternalPluginManifest; +import net.runelite.client.plugins.OPRSExternalPluginManager; +import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; import net.runelite.client.util.ImageUtil; @@ -57,6 +63,8 @@ class PluginListItem extends JPanel implements SearchablePlugin { private static final ImageIcon CONFIG_ICON; private static final ImageIcon CONFIG_ICON_HOVER; + private static final ImageIcon REFRESH_ICON; + private static final ImageIcon REFRESH_ICON_HOVER; private static final ImageIcon ON_STAR; private static final ImageIcon OFF_STAR; @@ -74,10 +82,13 @@ class PluginListItem extends JPanel implements SearchablePlugin static { BufferedImage configIcon = ImageUtil.loadImageResource(ConfigPanel.class, "config_edit_icon.png"); + BufferedImage refreshIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "refresh.png"); BufferedImage onStar = ImageUtil.loadImageResource(ConfigPanel.class, "star_on.png"); CONFIG_ICON = new ImageIcon(configIcon); + REFRESH_ICON = new ImageIcon(refreshIcon); ON_STAR = new ImageIcon(onStar); CONFIG_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(configIcon, -100)); + REFRESH_ICON_HOVER = new ImageIcon(ImageUtil.luminanceOffset(refreshIcon, -100)); BufferedImage offStar = ImageUtil.luminanceScale( ImageUtil.grayscaleImage(onStar), @@ -86,7 +97,7 @@ class PluginListItem extends JPanel implements SearchablePlugin OFF_STAR = new ImageIcon(offStar); } - PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig) + PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig, OPRSExternalPluginManager oprsExternalPluginManager) { this.pluginListPanel = pluginListPanel; this.pluginConfig = pluginConfig; @@ -133,6 +144,60 @@ class PluginListItem extends JPanel implements SearchablePlugin buttonPanel.setLayout(new GridLayout(1, 2)); add(buttonPanel, BorderLayout.LINE_END); + Map> pluginsInfoMap = oprsExternalPluginManager.getPluginsInfoMap(); + + if ((OPRSExternalPluginManager.isDevelopmentMode() || RuneLiteProperties.getLauncherVersion() == null) && pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName())) + { + JButton hotSwapButton = new JButton(REFRESH_ICON); + hotSwapButton.setRolloverIcon(REFRESH_ICON_HOVER); + SwingUtil.removeButtonDecorations(hotSwapButton); + hotSwapButton.setPreferredSize(new Dimension(25, 0)); + hotSwapButton.setVisible(false); + buttonPanel.add(hotSwapButton); + + hotSwapButton.addActionListener(e -> + { + Map pluginInfo = pluginsInfoMap.get(pluginConfig.getPlugin().getClass().getSimpleName()); + String pluginId = pluginInfo.get("id"); + + hotSwapButton.setIcon(REFRESH_ICON); + + new SwingWorker<>() + { + @Override + protected Boolean doInBackground() + { + return oprsExternalPluginManager.uninstall(pluginId); + } + + @Override + protected void done() + { + // In development mode our plugins will be loaded directly from sources, so we don't need to prompt + if (!OPRSExternalPluginManager.isDevelopmentMode()) + { + JOptionPane.showMessageDialog(ClientUI.getFrame(), + pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`", + "Hotswap " + pluginId, + JOptionPane.INFORMATION_MESSAGE); + } + + new SwingWorker<>() + { + @Override + protected Boolean doInBackground() + { + return oprsExternalPluginManager.reloadStart(pluginId); + } + }.execute(); + } + }.execute(); + }); + + hotSwapButton.setVisible(true); + hotSwapButton.setToolTipText("Hotswap plugin"); + } + JMenuItem configMenuItem = null; if (pluginConfig.hasConfigurables()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java index 286b534877..b4ce131eb7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java @@ -57,6 +57,7 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.events.PluginChanged; import net.runelite.client.externalplugins.ExternalPluginManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginInstantiationException; @@ -93,6 +94,7 @@ class PluginListPanel extends PluginPanel @Getter private final ExternalPluginManager externalPluginManager; + private final OPRSExternalPluginManager oprsExternalPluginManager; @Getter private final MultiplexingPluginPanel muxer; @@ -107,6 +109,7 @@ class PluginListPanel extends PluginPanel ConfigManager configManager, PluginManager pluginManager, ExternalPluginManager externalPluginManager, + OPRSExternalPluginManager oprsExternalPluginManager, EventBus eventBus, Provider configPanelProvider, Provider pluginHubPanelProvider) @@ -116,6 +119,7 @@ class PluginListPanel extends PluginPanel this.configManager = configManager; this.pluginManager = pluginManager; this.externalPluginManager = externalPluginManager; + this.oprsExternalPluginManager = oprsExternalPluginManager; this.configPanelProvider = configPanelProvider; muxer = new MultiplexingPluginPanel(this) @@ -215,7 +219,7 @@ class PluginListPanel extends PluginPanel ) .map(desc -> { - PluginListItem listItem = new PluginListItem(this, desc); + PluginListItem listItem = new PluginListItem(this, desc, oprsExternalPluginManager); listItem.setPinned(pinnedPlugins.contains(desc.getName())); return listItem; }) diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/config/refresh.png b/runelite-client/src/main/resources/net/runelite/client/plugins/config/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..cd26305f7a8c54a4c05a0b587352fde61e12f474 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mW_h|ehEy;fxv1*o!YJZ!vESWj z4cqeMxi*{GO&+e>y}<9tUB$n$58kz#8b5g~oAe)TSmDAoHa$9#W_(ESs zRlv@*TZBuhwyxIlaNp~F Date: Mon, 22 Feb 2021 19:07:21 +0100 Subject: [PATCH 3/3] config: Add metadata to external plugins configs --- .../client/plugins/config/ConfigPanel.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java index b176bf154b..c818ac9085 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java @@ -27,10 +27,15 @@ package net.runelite.client.plugins.config; import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; import com.google.common.primitives.Ints; +import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridLayout; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.ItemEvent; @@ -54,6 +59,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JScrollPane; +import javax.swing.JSeparator; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.ScrollPaneConstants; @@ -86,6 +92,7 @@ import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.events.PluginChanged; import net.runelite.client.externalplugins.ExternalPluginManager; import net.runelite.client.externalplugins.ExternalPluginManifest; +import net.runelite.client.plugins.OPRSExternalPluginManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginManager; import net.runelite.client.ui.ColorScheme; @@ -98,6 +105,7 @@ import net.runelite.client.ui.components.colorpicker.ColorPickerManager; import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker; import net.runelite.client.util.ColorUtil; import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.SwingUtil; import net.runelite.client.util.Text; @@ -130,6 +138,9 @@ class ConfigPanel extends PluginPanel @Inject private ExternalPluginManager externalPluginManager; + @Inject + private OPRSExternalPluginManager oprsExternalPluginManager; + @Inject private ColorPickerManager colorPickerManager; @@ -255,6 +266,53 @@ class ConfigPanel extends PluginPanel ConfigDescriptor cd = pluginConfig.getConfigDescriptor(); + Map> pluginsInfoMap = oprsExternalPluginManager.getPluginsInfoMap(); + + if (pluginConfig.getPlugin() != null && pluginsInfoMap.containsKey(pluginConfig.getPlugin().getClass().getSimpleName())) + { + + JPanel infoPanel = new JPanel(); + infoPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + infoPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + infoPanel.setLayout(new GridLayout(0, 1)); + + final Font smallFont = FontManager.getRunescapeSmallFont(); + + Map pluginInfo = pluginsInfoMap.get(pluginConfig.getPlugin().getClass().getSimpleName()); + + JLabel idLabel = new JLabel(htmlLabel("id", pluginInfo.get("id"))); + idLabel.setFont(smallFont); + infoPanel.add(idLabel); + + JLabel versionLabel = new JLabel(htmlLabel("version", pluginInfo.get("version"))); + versionLabel.setFont(smallFont); + infoPanel.add(versionLabel); + + JLabel providerLabel = new JLabel(htmlLabel("provider", pluginInfo.get("provider"))); + providerLabel.setFont(smallFont); + infoPanel.add(providerLabel); + + JButton button = new JButton("Support"); + button.addActionListener(e -> LinkBrowser.browse(pluginInfo.get("support"))); + + JSeparator separator = new JSeparator() + { + @Override + protected void paintComponent(Graphics g) + { + int width = this.getSize().width; + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(new BasicStroke(2)); + g2.setColor(ColorScheme.BRAND_BLUE); + g2.drawLine(0, 0, width, 0); + } + }; + + mainPanel.add(infoPanel); + mainPanel.add(button); + mainPanel.add(separator); + } + final Map sectionWidgets = new HashMap<>(); final Map topLevelPanels = new TreeMap<>((a, b) -> ComparisonChain.start() @@ -704,4 +762,9 @@ class ConfigPanel extends PluginPanel }); return menuItem; } + + private static String htmlLabel(String key, String value) + { + return "" + key + ": " + value + ""; + } }