project: Rework external plugins, local instances syncing, hotswapping
This commit is contained in:
@@ -34,6 +34,7 @@ open class BootstrapTask @Inject constructor(@Input val type: String) : DefaultT
|
||||
|
||||
private fun getArtifacts(): Array<JsonBuilder> {
|
||||
val artifacts = ArrayList<JsonBuilder>()
|
||||
val artifactsSet = HashSet<String>()
|
||||
|
||||
project.configurations["runtimeClasspath"].resolvedConfiguration.resolvedArtifacts.forEach {
|
||||
val module = it.moduleVersion.id.toString()
|
||||
@@ -44,7 +45,11 @@ open class BootstrapTask @Inject constructor(@Input val type: String) : DefaultT
|
||||
val version = splat[2]
|
||||
lateinit var path: String
|
||||
|
||||
if (it.file.name.contains(ProjectVersions.openosrsVersion)) {
|
||||
if (it.file.name.contains("injected-client") ||
|
||||
it.file.name.contains("runelite-client") ||
|
||||
it.file.name.contains("http-api") ||
|
||||
it.file.name.contains("runescape-api") ||
|
||||
it.file.name.contains("runelite-api")) {
|
||||
path = "https://github.com/open-osrs/hosting/raw/master/${type}/${it.file.name}"
|
||||
} else if (it.file.name.contains("injection-annotations") || it.file.name.contains("rxrelay")) {
|
||||
path = "https://github.com/open-osrs/hosting/raw/master/" + group.replace(".", "/") + "/${name}/$version/${it.file.name}"
|
||||
@@ -62,14 +67,18 @@ open class BootstrapTask @Inject constructor(@Input val type: String) : DefaultT
|
||||
path += "${name}/$version/${name}-$version.jar"
|
||||
}
|
||||
|
||||
val artifactFile = File(it.file.absolutePath)
|
||||
val filePath = it.file.absolutePath
|
||||
val artifactFile = File(filePath)
|
||||
|
||||
artifacts.add(JsonBuilder(
|
||||
"name" to it.file.name,
|
||||
"path" to path,
|
||||
"size" to artifactFile.length(),
|
||||
"hash" to hash(artifactFile.readBytes())
|
||||
))
|
||||
if (!artifactsSet.contains(filePath)) {
|
||||
artifactsSet.add(filePath)
|
||||
artifacts.add(JsonBuilder(
|
||||
"name" to it.file.name,
|
||||
"path" to path,
|
||||
"size" to artifactFile.length(),
|
||||
"hash" to hash(artifactFile.readBytes())
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
artifacts.add(JsonBuilder(
|
||||
|
||||
@@ -62,6 +62,7 @@ object Libraries {
|
||||
const val guice = "4.2.2"
|
||||
const val h2 = "1.4.200"
|
||||
const val hamcrest = "2.2"
|
||||
const val javagroups = "4.0.0.Final"
|
||||
const val javax = "1.3.2"
|
||||
const val javaxInject = "1"
|
||||
const val jna = "5.5.0"
|
||||
@@ -108,6 +109,7 @@ object Libraries {
|
||||
const val guiceTestlib = "com.google.inject.extensions:guice-testlib:${Versions.guice}"
|
||||
const val h2 = "com.h2database:h2:${Versions.h2}"
|
||||
const val hamcrest = "org.hamcrest:hamcrest-library:${Versions.hamcrest}"
|
||||
const val javagroups = "org.jgroups:jgroups:${Versions.javagroups}"
|
||||
const val javax = "javax.annotation:javax.annotation-api:${Versions.javax}"
|
||||
const val javaxInject = "javax.inject:javax.inject:${Versions.javaxInject}"
|
||||
const val jna = "net.java.dev.jna:jna:${Versions.jna}"
|
||||
|
||||
@@ -56,6 +56,7 @@ dependencies {
|
||||
implementation(Libraries.okhttp3)
|
||||
implementation(Libraries.rxjava)
|
||||
implementation(Libraries.jna)
|
||||
implementation(Libraries.javagroups)
|
||||
implementation(Libraries.jnaPlatform)
|
||||
implementation(Libraries.discord)
|
||||
implementation(Libraries.substance)
|
||||
@@ -134,11 +135,15 @@ tasks {
|
||||
inputs.properties(tokens)
|
||||
|
||||
from("src/main/resources") {
|
||||
include("open.osrs.properties")
|
||||
include("sentry.properties")
|
||||
}
|
||||
into("${buildDir}/resources/main")
|
||||
|
||||
from("src/main/resources/net/runelite/client") {
|
||||
include("open.osrs.properties")
|
||||
}
|
||||
into("${buildDir}/resources/main/net/runelite/client")
|
||||
|
||||
filter(ReplaceTokens::class, "tokens" to tokens)
|
||||
filteringCharset = "UTF-8"
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.net.PasswordAuthentication;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
@@ -88,7 +89,7 @@ import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay;
|
||||
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
||||
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
|
||||
import net.runelite.client.util.AppLock;
|
||||
import net.runelite.client.util.Groups;
|
||||
import net.runelite.client.util.WorldUtil;
|
||||
import net.runelite.client.ws.PartyService;
|
||||
import net.runelite.http.api.worlds.World;
|
||||
@@ -112,6 +113,7 @@ public class RuneLite
|
||||
public static final File DEFAULT_CONFIG_FILE = new File(RUNELITE_DIR, "runeliteplus.properties");
|
||||
public static final Locale SYSTEM_LOCALE = Locale.getDefault();
|
||||
public static boolean allowPrivateServer = false;
|
||||
public static String uuid = UUID.randomUUID().toString();
|
||||
|
||||
@Getter
|
||||
private static Injector injector;
|
||||
@@ -191,6 +193,9 @@ public class RuneLite
|
||||
@Inject
|
||||
private Provider<PartyService> partyService;
|
||||
|
||||
@Inject
|
||||
private Groups groups;
|
||||
|
||||
@Inject
|
||||
private Hooks hooks;
|
||||
|
||||
@@ -210,9 +215,6 @@ public class RuneLite
|
||||
@Inject
|
||||
private Scheduler scheduler;
|
||||
|
||||
@Inject
|
||||
private AppLock appLock;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
@@ -383,15 +385,12 @@ public class RuneLite
|
||||
// Tell the plugin manager if client is outdated or not
|
||||
pluginManager.setOutdated(isOutdated);
|
||||
|
||||
// Load external plugins
|
||||
// Load external plugin manager
|
||||
externalPluginManager.startExternalUpdateManager();
|
||||
externalPluginManager.startExternalPluginManager();
|
||||
|
||||
if (appLock.lock(this.getClass().getName()))
|
||||
{
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
externalPluginManager.update();
|
||||
}
|
||||
// Update external plugins
|
||||
externalPluginManager.update();
|
||||
|
||||
// Load the plugins, but does not start them yet.
|
||||
// This will initialize configuration
|
||||
@@ -510,7 +509,7 @@ public class RuneLite
|
||||
{
|
||||
clientSessionManager.shutdown();
|
||||
discordService.close();
|
||||
appLock.release();
|
||||
groups.close();
|
||||
}
|
||||
|
||||
private static class ConfigFileConverter implements ValueConverter<File>
|
||||
|
||||
@@ -52,7 +52,7 @@ public class RuneLiteProperties
|
||||
|
||||
static
|
||||
{
|
||||
try (InputStream in = RuneLiteProperties.class.getResourceAsStream("/open.osrs.properties"))
|
||||
try (InputStream in = RuneLiteProperties.class.getResourceAsStream("open.osrs.properties"))
|
||||
{
|
||||
properties.load(in);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.Groups;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jgroups.Message;
|
||||
import org.jgroups.util.Util;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
@@ -82,15 +85,24 @@ public class ConfigManager
|
||||
private final Properties properties = new Properties();
|
||||
private final Map<String, String> pendingChanges = new HashMap<>();
|
||||
private final File settingsFileInput;
|
||||
private final Groups groups;
|
||||
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
public ConfigManager(@Named("config") File config, ScheduledExecutorService scheduledExecutorService)
|
||||
public ConfigManager(
|
||||
@Named("config") File config,
|
||||
ScheduledExecutorService scheduledExecutorService,
|
||||
Groups groups)
|
||||
{
|
||||
this.settingsFileInput = config;
|
||||
this.groups = groups;
|
||||
|
||||
scheduledExecutorService.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS);
|
||||
|
||||
groups.getMessageObjectSubject()
|
||||
.subscribe(this::receive);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -450,6 +462,7 @@ public class ConfigManager
|
||||
configChanged.setKey(key);
|
||||
configChanged.setOldValue(null);
|
||||
configChanged.setNewValue(value);
|
||||
|
||||
eventBus.post(ConfigChanged.class, configChanged);
|
||||
});
|
||||
}
|
||||
@@ -525,7 +538,17 @@ public class ConfigManager
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setConfiguration(String groupName, String key, Object value)
|
||||
{
|
||||
setConfiguration(groupName, key, objectToString(value));
|
||||
}
|
||||
|
||||
public void setConfiguration(String groupName, String key, String value)
|
||||
{
|
||||
setConfiguration(groupName, key, value, null);
|
||||
}
|
||||
|
||||
public void setConfiguration(String groupName, String key, String value, String origin)
|
||||
{
|
||||
String oldValue = (String) properties.setProperty(groupName + "." + key, value);
|
||||
|
||||
@@ -547,16 +570,23 @@ public class ConfigManager
|
||||
configChanged.setKey(key);
|
||||
configChanged.setOldValue(oldValue);
|
||||
configChanged.setNewValue(value);
|
||||
configChanged.setOrigin(origin == null ? RuneLite.uuid : origin);
|
||||
configChanged.setPath(settingsFileInput.getAbsolutePath());
|
||||
|
||||
eventBus.post(ConfigChanged.class, configChanged);
|
||||
}
|
||||
|
||||
public void setConfiguration(String groupName, String key, Object value)
|
||||
{
|
||||
setConfiguration(groupName, key, objectToString(value));
|
||||
if (origin == null)
|
||||
{
|
||||
broadcast(configChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetConfiguration(String groupName, String key)
|
||||
{
|
||||
unsetConfiguration(groupName, key, null);
|
||||
}
|
||||
|
||||
public void unsetConfiguration(String groupName, String key, String origin)
|
||||
{
|
||||
String oldValue = (String) properties.remove(groupName + "." + key);
|
||||
|
||||
@@ -577,8 +607,15 @@ public class ConfigManager
|
||||
configChanged.setGroup(groupName);
|
||||
configChanged.setKey(key);
|
||||
configChanged.setOldValue(oldValue);
|
||||
configChanged.setOrigin(origin == null ? RuneLite.uuid : origin);
|
||||
configChanged.setPath(settingsFileInput.getAbsolutePath());
|
||||
|
||||
eventBus.post(ConfigChanged.class, configChanged);
|
||||
|
||||
if (origin == null)
|
||||
{
|
||||
broadcast(configChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigDescriptor getConfigDescriptor(Object configurationProxy)
|
||||
@@ -842,4 +879,40 @@ public class ConfigManager
|
||||
|
||||
syncPropertiesFromFile(newestFile);
|
||||
}
|
||||
|
||||
private void broadcast(ConfigChanged configChanged)
|
||||
{
|
||||
groups.sendConfig(null, configChanged);
|
||||
}
|
||||
|
||||
public void receive(Message message)
|
||||
{
|
||||
if (message.getObject() instanceof String)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ConfigChanged configChanged = Util.objectFromByteBuffer(message.getBuffer());
|
||||
|
||||
if (!configChanged.getPath().equals(settingsFileInput.getAbsolutePath()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (configChanged.getNewValue() == null)
|
||||
{
|
||||
unsetConfiguration(configChanged.getGroup(), configChanged.getKey(), configChanged.getOrigin());
|
||||
}
|
||||
else
|
||||
{
|
||||
setConfiguration(configChanged.getGroup(), configChanged.getKey(), configChanged.getNewValue(), configChanged.getOrigin());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,11 +342,23 @@ public interface OpenOSRSConfig extends Config
|
||||
return new Title();
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "localSync",
|
||||
name = "Sync local instances",
|
||||
description = "Enables multiple local instances of OpenOSRS to communicate (this enables syncing plugin state and config options)",
|
||||
position = 21,
|
||||
titleSection = "miscTitle"
|
||||
)
|
||||
default boolean localSync()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "keyboardPin",
|
||||
name = "Keyboard bank pin",
|
||||
description = "Enables you to type your bank pin",
|
||||
position = 21,
|
||||
position = 22,
|
||||
titleSection = "miscTitle"
|
||||
)
|
||||
default boolean keyboardPin()
|
||||
@@ -358,7 +370,7 @@ public interface OpenOSRSConfig extends Config
|
||||
keyName = "detachHotkey",
|
||||
name = "Detach Cam",
|
||||
description = "Detach Camera hotkey, press this and it will activate detatched camera.",
|
||||
position = 22,
|
||||
position = 23,
|
||||
titleSection = "miscTitle"
|
||||
)
|
||||
default Keybind detachHotkey()
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package net.runelite.client.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.events.Event;
|
||||
|
||||
@@ -31,7 +32,7 @@ import net.runelite.api.events.Event;
|
||||
* An event where a configuration entry has been modified.
|
||||
*/
|
||||
@Data
|
||||
public class ConfigChanged implements Event
|
||||
public class ConfigChanged implements Event, Serializable
|
||||
{
|
||||
/**
|
||||
* The parent group for the key.
|
||||
@@ -52,4 +53,12 @@ public class ConfigChanged implements Event
|
||||
* The new value of the entry, null if the entry has been unset.
|
||||
*/
|
||||
private String newValue;
|
||||
/**
|
||||
* The client where the config value was changed from
|
||||
*/
|
||||
private String origin;
|
||||
/**
|
||||
* Path of the current config file
|
||||
*/
|
||||
private String path;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Owain van Brakel <https://github.com/Owain94>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import com.google.inject.Binder;
|
||||
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.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -30,13 +53,13 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -48,12 +71,16 @@ import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.OpenOSRSConfig;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.ExternalPluginChanged;
|
||||
import net.runelite.client.events.ExternalRepositoryChanged;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.RuneLiteSplashScreen;
|
||||
import net.runelite.client.util.Groups;
|
||||
import net.runelite.client.util.MiscUtils;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
import org.jgroups.Message;
|
||||
import org.jgroups.ReceiverAdapter;
|
||||
import org.pf4j.DefaultPluginManager;
|
||||
import org.pf4j.DependencyResolver;
|
||||
import org.pf4j.JarPluginLoader;
|
||||
@@ -65,6 +92,8 @@ 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;
|
||||
@@ -75,8 +104,7 @@ import org.pf4j.update.VerifyException;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public
|
||||
class ExternalPluginManager
|
||||
public class ExternalPluginManager extends ReceiverAdapter
|
||||
{
|
||||
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
|
||||
private final PluginManager runelitePluginManager;
|
||||
@@ -86,35 +114,41 @@ class ExternalPluginManager
|
||||
private final OpenOSRSConfig openOSRSConfig;
|
||||
private final EventBus eventBus;
|
||||
private final ConfigManager configManager;
|
||||
private final List<Plugin> plugins = new CopyOnWriteArrayList<>();
|
||||
private final Map<String, String> pluginsMap = new HashMap<>();
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private final Map<String, Map<String, String>> pluginsInfoMap = new HashMap<>();
|
||||
private final Groups groups;
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private UpdateManager updateManager;
|
||||
private Map<String, PluginInfo.PluginRelease> lastPluginRelease = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public ExternalPluginManager(
|
||||
PluginManager pluginManager,
|
||||
OpenOSRSConfig openOSRSConfig,
|
||||
EventBus eventBus,
|
||||
ConfigManager configManager)
|
||||
ConfigManager configManager,
|
||||
Groups groups)
|
||||
{
|
||||
this.runelitePluginManager = pluginManager;
|
||||
this.openOSRSConfig = openOSRSConfig;
|
||||
this.eventBus = eventBus;
|
||||
this.configManager = configManager;
|
||||
this.groups = groups;
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
EXTERNALPLUGIN_DIR.mkdirs();
|
||||
|
||||
initPluginManager();
|
||||
|
||||
groups.getMessageStringSubject()
|
||||
.subscribe(this::receive);
|
||||
}
|
||||
|
||||
private void initPluginManager()
|
||||
{
|
||||
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
|
||||
|
||||
this.externalPluginManager = new DefaultPluginManager(
|
||||
debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release")
|
||||
externalPluginManager = new DefaultPluginManager(
|
||||
RuneLiteProperties.getPluginPath() != null ? Paths.get(RuneLiteProperties.getPluginPath())
|
||||
: EXTERNALPLUGIN_DIR.toPath())
|
||||
{
|
||||
@Override
|
||||
@@ -158,7 +192,70 @@ class ExternalPluginManager
|
||||
@Override
|
||||
public RuntimeMode getRuntimeMode()
|
||||
{
|
||||
return debug ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
|
||||
return RuneLiteProperties.getLauncherVersion() == null ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resolvePlugins()
|
||||
{
|
||||
// retrieves the plugins descriptors
|
||||
List<org.pf4j.PluginDescriptor> 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<String> notFoundDependencies = result.getNotFoundDependencies();
|
||||
if (!notFoundDependencies.isEmpty())
|
||||
{
|
||||
throw new DependencyResolver.DependenciesNotFoundException(notFoundDependencies);
|
||||
}
|
||||
|
||||
List<DependencyResolver.WrongDependencyVersion> wrongVersionDependencies = result.getWrongVersionDependencies();
|
||||
if (!wrongVersionDependencies.isEmpty())
|
||||
{
|
||||
throw new DependencyResolver.DependenciesWrongVersionException(wrongVersionDependencies);
|
||||
}
|
||||
|
||||
List<String> 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
|
||||
@@ -209,8 +306,129 @@ class ExternalPluginManager
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@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<String, ClassLoader> 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);
|
||||
}
|
||||
};
|
||||
this.externalPluginManager.setSystemVersion(SYSTEM_VERSION);
|
||||
externalPluginManager.setSystemVersion(SYSTEM_VERSION);
|
||||
}
|
||||
|
||||
public boolean developmentMode()
|
||||
{
|
||||
return externalPluginManager.isDevelopment();
|
||||
}
|
||||
|
||||
public boolean doesGhRepoExist(String owner, String name)
|
||||
@@ -263,7 +481,7 @@ class ExternalPluginManager
|
||||
{
|
||||
try
|
||||
{
|
||||
this.externalPluginManager.loadPlugins();
|
||||
externalPluginManager.loadPlugins();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -275,7 +493,7 @@ class ExternalPluginManager
|
||||
|
||||
for (String dep : deps)
|
||||
{
|
||||
install(dep);
|
||||
updateManager.installPlugin(dep, null);
|
||||
}
|
||||
|
||||
startExternalPluginManager();
|
||||
@@ -292,7 +510,7 @@ class ExternalPluginManager
|
||||
loadOldFormat();
|
||||
}
|
||||
|
||||
this.updateManager = new UpdateManager(this.externalPluginManager, repositories);
|
||||
updateManager = new UpdateManager(externalPluginManager, repositories);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
@@ -361,7 +579,7 @@ class ExternalPluginManager
|
||||
openOSRSConfig.setExternalRepositories("OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/");
|
||||
}
|
||||
|
||||
this.updateManager = new UpdateManager(this.externalPluginManager, repositories);
|
||||
updateManager = new UpdateManager(externalPluginManager, repositories);
|
||||
}
|
||||
|
||||
public void addGHRepository(String owner, String name)
|
||||
@@ -407,12 +625,9 @@ class ExternalPluginManager
|
||||
openOSRSConfig.setExternalRepositories(config.toString());
|
||||
}
|
||||
|
||||
private List<Plugin> scanAndInstantiate(List<Plugin> plugins, boolean init, boolean initConfig) throws IOException
|
||||
private void scanAndInstantiate(List<Plugin> plugins, boolean init, boolean initConfig)
|
||||
{
|
||||
RuneLiteSplashScreen.stage(.66, "Loading external plugins");
|
||||
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
|
||||
.directed()
|
||||
.build();
|
||||
|
||||
AtomicInteger loaded = new AtomicInteger();
|
||||
List<Plugin> scannedPlugins = new CopyOnWriteArrayList<>();
|
||||
@@ -451,6 +666,7 @@ class ExternalPluginManager
|
||||
Plugin plugininst;
|
||||
try
|
||||
{
|
||||
//noinspection unchecked
|
||||
plugininst = instantiate(scannedPlugins, (Class<Plugin>) plugin.getClass(), init, initConfig);
|
||||
scannedPlugins.add(plugininst);
|
||||
}
|
||||
@@ -477,7 +693,6 @@ class ExternalPluginManager
|
||||
});
|
||||
}
|
||||
|
||||
return scannedPlugins;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -492,7 +707,7 @@ class ExternalPluginManager
|
||||
Optional<Plugin> dependency =
|
||||
Stream.concat(runelitePluginManager.getPlugins().stream(), scannedPlugins.stream())
|
||||
.filter(p -> p.getClass() == pluginDependency.value()).findFirst();
|
||||
if (!dependency.isPresent())
|
||||
if (dependency.isEmpty())
|
||||
{
|
||||
throw new PluginInstantiationException(
|
||||
"Unmet dependency for " + clazz.getSimpleName() + ": " + pluginDependency.value().getSimpleName());
|
||||
@@ -551,12 +766,12 @@ class ExternalPluginManager
|
||||
{
|
||||
try
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() ->
|
||||
SwingUtil.syncExec(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
runelitePluginManager.startPlugin(plugin);
|
||||
runelitePluginManager.add(plugin);
|
||||
runelitePluginManager.startPlugin(plugin);
|
||||
eventBus.post(ExternalPluginChanged.class,
|
||||
new ExternalPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()), plugin,
|
||||
true));
|
||||
@@ -586,43 +801,53 @@ class ExternalPluginManager
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private void checkDepsAndStart(List<PluginWrapper> startedPlugins, List<Plugin> scannedPlugins, PluginWrapper pluginWrapper)
|
||||
{
|
||||
boolean depsLoaded = true;
|
||||
for (PluginDependency dependency : pluginWrapper.getDescriptor().getDependencies())
|
||||
{
|
||||
if (startedPlugins.stream().noneMatch(pl -> pl.getPluginId().equals(dependency.getPluginId())))
|
||||
{
|
||||
depsLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!depsLoaded)
|
||||
{
|
||||
// This should never happen but can crash the client
|
||||
return;
|
||||
}
|
||||
|
||||
scannedPlugins.addAll(loadPlugin(pluginWrapper.getPluginId()));
|
||||
}
|
||||
|
||||
public void loadPlugins()
|
||||
{
|
||||
this.externalPluginManager.startPlugins();
|
||||
externalPluginManager.startPlugins();
|
||||
List<PluginWrapper> startedPlugins = getStartedPlugins();
|
||||
List<Plugin> scannedPlugins = new ArrayList<>();
|
||||
|
||||
for (PluginWrapper plugin : startedPlugins)
|
||||
{
|
||||
boolean depsLoaded = true;
|
||||
for (PluginDependency dependency : plugin.getDescriptor().getDependencies())
|
||||
{
|
||||
if (startedPlugins.stream().noneMatch(pl -> pl.getPluginId().equals(dependency.getPluginId())))
|
||||
{
|
||||
depsLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!depsLoaded)
|
||||
{
|
||||
// This should never happen but can crash the client
|
||||
continue;
|
||||
}
|
||||
|
||||
scannedPlugins.addAll(loadPlugin(plugin.getPluginId()));
|
||||
checkDepsAndStart(startedPlugins, scannedPlugins, plugin);
|
||||
}
|
||||
|
||||
startPlugins(scannedPlugins, false, false);
|
||||
}
|
||||
scanAndInstantiate(scannedPlugins, false, false);
|
||||
|
||||
private void startPlugins(List<Plugin> scannedPlugins, boolean init, boolean initConfig)
|
||||
{
|
||||
try
|
||||
if (groups.getInstanceCount() > 1)
|
||||
{
|
||||
plugins.addAll(scanAndInstantiate(scannedPlugins, init, initConfig));
|
||||
for (String pluginId : getDisabledPlugins())
|
||||
{
|
||||
groups.sendString("STOPEXTERNAL;" + pluginId);
|
||||
}
|
||||
}
|
||||
catch (IOException ignored)
|
||||
else
|
||||
{
|
||||
for (String pluginId : getDisabledPlugins())
|
||||
{
|
||||
externalPluginManager.enablePlugin(pluginId);
|
||||
externalPluginManager.deletePlugin(pluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,55 +858,57 @@ class ExternalPluginManager
|
||||
for (Plugin plugin : extensions)
|
||||
{
|
||||
pluginClassLoaders.add(plugin.getClass().getClassLoader());
|
||||
|
||||
pluginsMap.remove(plugin.getClass().getSimpleName());
|
||||
pluginsMap.put(plugin.getClass().getSimpleName(), pluginId);
|
||||
|
||||
pluginsInfoMap.remove(plugin.getClass().getSimpleName());
|
||||
|
||||
AtomicReference<String> support = new AtomicReference<>("");
|
||||
AtomicReference<String> version = new AtomicReference<>("");
|
||||
|
||||
updateManager.getRepositories().forEach(repository ->
|
||||
repository.getPlugins().forEach((key, value) -> {
|
||||
if (key.equals(pluginId))
|
||||
{
|
||||
support.set(value.projectUrl);
|
||||
|
||||
for (PluginInfo.PluginRelease release : value.releases)
|
||||
{
|
||||
if (externalPluginManager.getSystemVersion().equals("0.0.0") || externalPluginManager.getVersionManager().checkVersionConstraint(externalPluginManager.getSystemVersion(), release.requires))
|
||||
{
|
||||
if (lastPluginRelease.get(pluginId) == null)
|
||||
{
|
||||
lastPluginRelease.put(pluginId, release);
|
||||
}
|
||||
else if (externalPluginManager.getVersionManager().compareVersions(release.version, lastPluginRelease.get(pluginId).version) > 0)
|
||||
{
|
||||
lastPluginRelease.put(pluginId, release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version.set(lastPluginRelease.get(pluginId).version);
|
||||
}
|
||||
}));
|
||||
|
||||
pluginsInfoMap.put(
|
||||
plugin.getClass().getSimpleName(),
|
||||
new HashMap<>()
|
||||
{{
|
||||
put("version", version.get());
|
||||
put("id", externalPluginManager.getPlugin(pluginId).getDescriptor().getPluginId());
|
||||
put("provider", externalPluginManager.getPlugin(pluginId).getDescriptor().getProvider());
|
||||
put("support", support.get());
|
||||
}}
|
||||
);
|
||||
|
||||
scannedPlugins.add(plugin);
|
||||
}
|
||||
|
||||
return scannedPlugins;
|
||||
}
|
||||
|
||||
private void stopPlugins()
|
||||
{
|
||||
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
|
||||
|
||||
for (PluginWrapper pluginWrapper : startedPlugins)
|
||||
{
|
||||
String pluginId = pluginWrapper.getDescriptor().getPluginId();
|
||||
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
|
||||
|
||||
for (Plugin plugin : runelitePluginManager.getPlugins())
|
||||
{
|
||||
if (!extensions.get(0).getClass().getName().equals(plugin.getClass().getName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
runelitePluginManager.stopPlugin(plugin);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
throw new RuntimeException(e2);
|
||||
}
|
||||
});
|
||||
runelitePluginManager.remove(plugin);
|
||||
|
||||
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginId, plugin, false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("unable to stop plugin", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path stopPlugin(String pluginId)
|
||||
{
|
||||
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
|
||||
@@ -704,7 +931,7 @@ class ExternalPluginManager
|
||||
|
||||
try
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() ->
|
||||
SwingUtil.syncExec(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -716,6 +943,7 @@ class ExternalPluginManager
|
||||
}
|
||||
});
|
||||
runelitePluginManager.remove(plugin);
|
||||
pluginClassLoaders.remove(plugin.getClass().getClassLoader());
|
||||
|
||||
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginId, plugin, false));
|
||||
|
||||
@@ -734,13 +962,13 @@ class ExternalPluginManager
|
||||
|
||||
public boolean install(String pluginId) throws VerifyException
|
||||
{
|
||||
|
||||
if (getDisabledPlugins().contains(pluginId))
|
||||
{
|
||||
this.externalPluginManager.enablePlugin(pluginId);
|
||||
this.externalPluginManager.startPlugin(pluginId);
|
||||
externalPluginManager.enablePlugin(pluginId);
|
||||
externalPluginManager.startPlugin(pluginId);
|
||||
|
||||
startPlugins(loadPlugin(pluginId), true, false);
|
||||
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
|
||||
scanAndInstantiate(loadPlugin(pluginId), true, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -775,7 +1003,9 @@ class ExternalPluginManager
|
||||
|
||||
updateManager.installPlugin(pluginId, null);
|
||||
|
||||
startPlugins(loadPlugin(pluginId), true, true);
|
||||
scanAndInstantiate(loadPlugin(pluginId), true, true);
|
||||
|
||||
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
|
||||
}
|
||||
catch (DependencyResolver.DependenciesNotFoundException ex)
|
||||
{
|
||||
@@ -792,6 +1022,11 @@ class ExternalPluginManager
|
||||
}
|
||||
|
||||
public boolean uninstall(String pluginId)
|
||||
{
|
||||
return uninstall(pluginId, false);
|
||||
}
|
||||
|
||||
public boolean uninstall(String pluginId, boolean skip)
|
||||
{
|
||||
Path pluginPath = stopPlugin(pluginId);
|
||||
|
||||
@@ -801,13 +1036,35 @@ class ExternalPluginManager
|
||||
}
|
||||
|
||||
externalPluginManager.stopPlugin(pluginId);
|
||||
externalPluginManager.disablePlugin(pluginId);
|
||||
|
||||
if (skip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (groups.getInstanceCount() > 1)
|
||||
{
|
||||
groups.sendString("STOPEXTERNAL;" + pluginId);
|
||||
}
|
||||
else
|
||||
{
|
||||
externalPluginManager.deletePlugin(pluginId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
if (groups.getInstanceCount() > 1)
|
||||
{
|
||||
// Do not update when there is more than one client open -> api might contain changes
|
||||
log.info("Not updating external plugins since there is more than 1 client open");
|
||||
return;
|
||||
}
|
||||
|
||||
RuneLiteSplashScreen.stage(.59, "Updating external plugins");
|
||||
|
||||
boolean error = false;
|
||||
if (updateManager.hasUpdates())
|
||||
{
|
||||
@@ -828,6 +1085,7 @@ class ExternalPluginManager
|
||||
}
|
||||
catch (PluginRuntimeException ex)
|
||||
{
|
||||
// This should never happen but can crash the client
|
||||
log.warn("Cannot update plugin '{}', the user probably has another client open", plugin.id);
|
||||
error = true;
|
||||
break;
|
||||
@@ -861,15 +1119,99 @@ class ExternalPluginManager
|
||||
|
||||
public List<String> getDisabledPlugins()
|
||||
{
|
||||
return this.externalPluginManager.getResolvedPlugins()
|
||||
return externalPluginManager.getResolvedPlugins()
|
||||
.stream()
|
||||
.filter(not(this.externalPluginManager.getStartedPlugins()::contains))
|
||||
.filter(not(externalPluginManager.getStartedPlugins()::contains))
|
||||
.map(PluginWrapper::getPluginId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<PluginWrapper> getStartedPlugins()
|
||||
{
|
||||
return this.externalPluginManager.getStartedPlugins();
|
||||
return externalPluginManager.getStartedPlugins();
|
||||
}
|
||||
|
||||
public Boolean reloadStart(String pluginId)
|
||||
{
|
||||
externalPluginManager.loadPlugins();
|
||||
externalPluginManager.startPlugin(pluginId);
|
||||
|
||||
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
|
||||
List<Plugin> scannedPlugins = new ArrayList<>();
|
||||
|
||||
for (PluginWrapper pluginWrapper : startedPlugins)
|
||||
{
|
||||
if (!pluginId.equals(pluginWrapper.getDescriptor().getPluginId()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
checkDepsAndStart(startedPlugins, scannedPlugins, pluginWrapper);
|
||||
}
|
||||
|
||||
scanAndInstantiate(scannedPlugins, true, false);
|
||||
|
||||
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void receive(Message message)
|
||||
{
|
||||
if (message.getObject() instanceof ConfigChanged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String[] messageObject = ((String) message.getObject()).split(";");
|
||||
|
||||
if (messageObject.length < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String command = messageObject[0];
|
||||
String pluginId = messageObject[1];
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "STARTEXTERNAL":
|
||||
externalPluginManager.loadPlugins();
|
||||
externalPluginManager.startPlugin(pluginId);
|
||||
|
||||
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
|
||||
List<Plugin> scannedPlugins = new ArrayList<>();
|
||||
|
||||
for (PluginWrapper pluginWrapper : startedPlugins)
|
||||
{
|
||||
if (!pluginId.equals(pluginWrapper.getDescriptor().getPluginId()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
checkDepsAndStart(startedPlugins, scannedPlugins, pluginWrapper);
|
||||
}
|
||||
|
||||
scanAndInstantiate(scannedPlugins, true, false);
|
||||
|
||||
break;
|
||||
|
||||
case "STOPEXTERNAL":
|
||||
uninstall(pluginId, true);
|
||||
externalPluginManager.unloadPlugin(pluginId);
|
||||
groups.send(message.getSrc(), "STOPPEDEXTERNAL;" + pluginId);
|
||||
break;
|
||||
|
||||
case "STOPPEDEXTERNAL":
|
||||
groups.getMessageMap().get(pluginId).remove(message.getSrc());
|
||||
|
||||
if (groups.getMessageMap().get(pluginId).size() == 0)
|
||||
{
|
||||
groups.getMessageMap().remove(pluginId);
|
||||
externalPluginManager.deletePlugin(pluginId);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,9 @@ public class PluginClassLoader extends URLClassLoader
|
||||
// fall back to main class loader
|
||||
return parent.loadClass(name);
|
||||
}
|
||||
catch (NoClassDefFoundError ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ import com.google.inject.CreationException;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Module;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
@@ -56,6 +58,7 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -68,6 +71,7 @@ import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.RuneLiteConfig;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.PluginChanged;
|
||||
import net.runelite.client.events.SessionClose;
|
||||
import net.runelite.client.events.SessionOpen;
|
||||
@@ -76,6 +80,8 @@ import net.runelite.client.task.ScheduledMethod;
|
||||
import net.runelite.client.task.Scheduler;
|
||||
import net.runelite.client.ui.RuneLiteSplashScreen;
|
||||
import net.runelite.client.util.GameEventManager;
|
||||
import net.runelite.client.util.Groups;
|
||||
import org.jgroups.Message;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
@@ -96,6 +102,8 @@ public class PluginManager
|
||||
private final List<PluginConfigurationDescriptor> fakePlugins = new ArrayList<>();
|
||||
private final String runeliteGroupName = RuneLiteConfig.class
|
||||
.getAnnotation(ConfigGroup.class).value();
|
||||
private final Groups groups;
|
||||
private final File settingsFileInput;
|
||||
|
||||
@Inject
|
||||
ExternalPluginLoader externalPluginLoader;
|
||||
@@ -109,18 +117,23 @@ public class PluginManager
|
||||
final EventBus eventBus,
|
||||
final Scheduler scheduler,
|
||||
final ConfigManager configManager,
|
||||
final Provider<GameEventManager> sceneTileManager)
|
||||
final Provider<GameEventManager> sceneTileManager,
|
||||
final Groups groups,
|
||||
final @Named("config") File config)
|
||||
{
|
||||
this.eventBus = eventBus;
|
||||
this.scheduler = scheduler;
|
||||
this.configManager = configManager;
|
||||
this.sceneTileManager = sceneTileManager;
|
||||
this.groups = groups;
|
||||
this.settingsFileInput = config;
|
||||
|
||||
if (eventBus != null)
|
||||
{
|
||||
eventBus.subscribe(SessionOpen.class, this, this::onSessionOpen);
|
||||
eventBus.subscribe(SessionClose.class, this, this::onSessionClose);
|
||||
}
|
||||
eventBus.subscribe(SessionOpen.class, this, this::onSessionOpen);
|
||||
eventBus.subscribe(SessionClose.class, this, this::onSessionClose);
|
||||
|
||||
groups.getMessageStringSubject()
|
||||
.subscribeOn(Schedulers.from(SwingUtilities::invokeLater))
|
||||
.subscribe(this::receive);
|
||||
}
|
||||
|
||||
private void onSessionOpen(SessionOpen event)
|
||||
@@ -321,6 +334,12 @@ public class PluginManager
|
||||
for (ClassInfo classInfo : classes)
|
||||
{
|
||||
Class<?> clazz = classInfo.load();
|
||||
|
||||
if (clazz == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PluginDescriptor pluginDescriptor = clazz.getAnnotation(PluginDescriptor.class);
|
||||
|
||||
if (pluginDescriptor == null)
|
||||
@@ -462,6 +481,8 @@ public class PluginManager
|
||||
throw new PluginInstantiationException(ex);
|
||||
}
|
||||
|
||||
groups.broadcastSring("STARTPLUGIN;" + plugin.getClass().getSimpleName() + ";" + settingsFileInput.getAbsolutePath());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -491,6 +512,8 @@ public class PluginManager
|
||||
throw new PluginInstantiationException(ex);
|
||||
}
|
||||
|
||||
groups.broadcastSring("STOPPLUGIN;" + plugin.getClass().getSimpleName() + ";" + settingsFileInput.getAbsolutePath());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -661,4 +684,77 @@ public class PluginManager
|
||||
incrementChildren(graph, dependencyCount, m, val + 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void receive(Message message)
|
||||
{
|
||||
if (message.getObject() instanceof ConfigChanged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String[] messageObject = ((String) message.getObject()).split(";");
|
||||
|
||||
if (messageObject.length < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String command = messageObject[0];
|
||||
String pluginName = messageObject[1];
|
||||
String path = messageObject[2];
|
||||
Plugin plugin = null;
|
||||
|
||||
if (!path.equals(settingsFileInput.getAbsolutePath()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (Plugin pl : getPlugins())
|
||||
{
|
||||
if (pl.getClass().getSimpleName().equals(pluginName))
|
||||
{
|
||||
plugin = pl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Plugin finalPlugin = plugin;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "STARTPLUGIN":
|
||||
|
||||
try
|
||||
{
|
||||
startPlugin(finalPlugin);
|
||||
}
|
||||
catch (PluginInstantiationException e)
|
||||
{
|
||||
log.warn("unable to start plugin", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "STOPPLUGIN":
|
||||
|
||||
try
|
||||
{
|
||||
stopPlugin(finalPlugin);
|
||||
}
|
||||
catch (PluginInstantiationException e)
|
||||
{
|
||||
log.warn("unable to stop plugin", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionListener;
|
||||
@@ -78,6 +79,7 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.util.Text;
|
||||
import net.runelite.client.RuneLite;
|
||||
import net.runelite.client.config.Button;
|
||||
import net.runelite.client.config.ConfigDescriptor;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
@@ -90,7 +92,9 @@ import net.runelite.client.config.ModifierlessKeybind;
|
||||
import net.runelite.client.config.Range;
|
||||
import net.runelite.client.config.Units;
|
||||
import net.runelite.client.eventbus.EventBus;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.PluginChanged;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import net.runelite.client.plugins.PluginManager;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.DynamicGridLayout;
|
||||
@@ -102,6 +106,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.MiscUtils;
|
||||
import net.runelite.client.util.SwingUtil;
|
||||
|
||||
@@ -132,6 +137,9 @@ class ConfigPanel extends PluginPanel
|
||||
@Inject
|
||||
private PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
private ExternalPluginManager externalPluginManager;
|
||||
|
||||
@Inject
|
||||
private ColorPickerManager colorPickerManager;
|
||||
|
||||
@@ -233,6 +241,7 @@ class ConfigPanel extends PluginPanel
|
||||
|
||||
rebuild(false);
|
||||
eventBus.subscribe(PluginChanged.class, this, this::onPluginChanged);
|
||||
eventBus.subscribe(ConfigChanged.class, this, this::onConfigChanged);
|
||||
}
|
||||
|
||||
private void getSections(ConfigDescriptor cd)
|
||||
@@ -425,11 +434,44 @@ class ConfigPanel extends PluginPanel
|
||||
ConfigDescriptor cd = pluginConfig.getConfigDescriptor();
|
||||
assert cd != null;
|
||||
|
||||
List<JButton> buttons = new ArrayList<>();
|
||||
|
||||
Map<String, Map<String, String>> pluginsInfoMap = externalPluginManager.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<String, String> 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")));
|
||||
buttons.add(button);
|
||||
|
||||
mainPanel.add(infoPanel);
|
||||
}
|
||||
|
||||
getSections(cd);
|
||||
getTitleSections(cd);
|
||||
|
||||
List<JButton> buttons = new ArrayList<>();
|
||||
|
||||
for (ConfigItemDescriptor cid : cd.getItems())
|
||||
{
|
||||
if (cid == null)
|
||||
@@ -1168,4 +1210,25 @@ class ConfigPanel extends PluginPanel
|
||||
pluginToggle.setSelected(event.isLoaded()));
|
||||
}
|
||||
}
|
||||
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
if (event.getOrigin().equals(RuneLite.uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SwingUtilities.invokeAndWait(() -> rebuild(true));
|
||||
}
|
||||
catch (InterruptedException | InvocationTargetException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static String htmlLabel(String key, String value)
|
||||
{
|
||||
return "<html><body style = 'color:#a5a5a5'>" + key + ": <span style = 'color:white'>" + value + "</span></body></html>";
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,19 @@ 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.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SwingWorker;
|
||||
import lombok.Getter;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import net.runelite.client.plugins.PluginType;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
@@ -50,6 +56,8 @@ public class PluginListItem extends JPanel
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -70,10 +78,13 @@ public class PluginListItem extends JPanel
|
||||
static
|
||||
{
|
||||
BufferedImage configIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "config_edit_icon.png");
|
||||
BufferedImage refreshIcon = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "refresh.png");
|
||||
BufferedImage onStar = ImageUtil.getResourceStreamFromClass(ConfigPanel.class, "star_on.png");
|
||||
CONFIG_ICON = new ImageIcon(configIcon);
|
||||
REFRESH_ICON = new ImageIcon(refreshIcon);
|
||||
ON_STAR = new ImageIcon(ImageUtil.recolorImage(onStar, ColorScheme.BRAND_BLUE));
|
||||
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),
|
||||
@@ -82,7 +93,7 @@ public class PluginListItem extends JPanel
|
||||
OFF_STAR = new ImageIcon(offStar);
|
||||
}
|
||||
|
||||
PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig)
|
||||
PluginListItem(PluginListPanel pluginListPanel, PluginConfigurationDescriptor pluginConfig, ExternalPluginManager externalPluginManager)
|
||||
{
|
||||
this.pluginListPanel = pluginListPanel;
|
||||
this.pluginConfig = pluginConfig;
|
||||
@@ -119,6 +130,55 @@ public class PluginListItem extends JPanel
|
||||
buttonPanel.setLayout(new GridLayout(1, 2));
|
||||
add(buttonPanel, BorderLayout.LINE_END);
|
||||
|
||||
Map<String, Map<String, String>> pluginsInfoMap = externalPluginManager.getPluginsInfoMap();
|
||||
|
||||
if (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<String, String> pluginInfo = pluginsInfoMap.get(pluginConfig.getPlugin().getClass().getSimpleName());
|
||||
String pluginId = pluginInfo.get("id");
|
||||
|
||||
hotSwapButton.setIcon(REFRESH_ICON);
|
||||
externalPluginManager.uninstall(pluginId);
|
||||
|
||||
SwingWorker<Boolean, Void> worker = new SwingWorker<>()
|
||||
{
|
||||
@Override
|
||||
protected Boolean doInBackground()
|
||||
{
|
||||
return externalPluginManager.uninstall(pluginId);
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
|
||||
JOptionPane.showMessageDialog(ClientUI.getFrame(),
|
||||
pluginId + " is unloaded, put the new jar file in the externalmanager folder and click `ok`",
|
||||
"Hotswap " + pluginId,
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
|
||||
worker = new SwingWorker<>()
|
||||
{
|
||||
@Override
|
||||
protected Boolean doInBackground()
|
||||
{
|
||||
return externalPluginManager.reloadStart(pluginId);
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
});
|
||||
|
||||
hotSwapButton.setVisible(true);
|
||||
hotSwapButton.setToolTipText("Hotswap plugin");
|
||||
}
|
||||
|
||||
if (pluginConfig.hasConfigurables())
|
||||
{
|
||||
JButton configButton = new JButton(CONFIG_ICON);
|
||||
@@ -211,8 +271,6 @@ public class PluginListItem extends JPanel
|
||||
*/
|
||||
static void addLabelMouseOver(final JLabel label)
|
||||
{
|
||||
final Color labelForeground = label.getForeground();
|
||||
|
||||
label.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
private Color lastForeground;
|
||||
|
||||
@@ -62,6 +62,7 @@ import javax.swing.event.DocumentListener;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.util.Text;
|
||||
import net.runelite.client.RuneLite;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigDescriptor;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
@@ -73,6 +74,7 @@ import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.events.ExternalPluginChanged;
|
||||
import net.runelite.client.events.ExternalPluginsLoaded;
|
||||
import net.runelite.client.events.PluginChanged;
|
||||
import net.runelite.client.plugins.ExternalPluginManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.plugins.PluginInstantiationException;
|
||||
@@ -110,6 +112,7 @@ public class PluginListPanel extends PluginPanel
|
||||
private final PluginManager pluginManager;
|
||||
private final Provider<ConfigPanel> configPanelProvider;
|
||||
private final OpenOSRSConfig openOSRSConfig;
|
||||
private final ExternalPluginManager externalPluginManager;
|
||||
|
||||
@Getter
|
||||
private final MultiplexingPluginPanel muxer;
|
||||
@@ -140,6 +143,7 @@ public class PluginListPanel extends PluginPanel
|
||||
PluginManager pluginManager,
|
||||
Provider<ConfigPanel> configPanelProvider,
|
||||
OpenOSRSConfig openOSRSConfig,
|
||||
ExternalPluginManager externalPluginManager,
|
||||
EventBus eventBus)
|
||||
{
|
||||
super(false);
|
||||
@@ -148,8 +152,14 @@ public class PluginListPanel extends PluginPanel
|
||||
this.pluginManager = pluginManager;
|
||||
this.configPanelProvider = configPanelProvider;
|
||||
this.openOSRSConfig = openOSRSConfig;
|
||||
this.externalPluginManager = externalPluginManager;
|
||||
|
||||
eventBus.subscribe(ConfigChanged.class, this, ev -> {
|
||||
if (ev.getGroup().equals("runelite") && ev.getKey().equals("pinnedPlugins") && !ev.getOrigin().equals(RuneLite.uuid))
|
||||
{
|
||||
SwingUtilities.invokeLater(this::rebuildPluginList);
|
||||
}
|
||||
|
||||
if (!ev.getGroup().equals("openosrs"))
|
||||
{
|
||||
return;
|
||||
@@ -157,7 +167,7 @@ public class PluginListPanel extends PluginPanel
|
||||
|
||||
if (ev.getKey().equals("enableCategories") || ev.getKey().equals("pluginSortMode"))
|
||||
{
|
||||
rebuildPluginList();
|
||||
SwingUtilities.invokeLater(this::rebuildPluginList);
|
||||
}
|
||||
|
||||
if (ev.getKey().equals("pluginSortMode"))
|
||||
@@ -264,7 +274,7 @@ public class PluginListPanel extends PluginPanel
|
||||
})
|
||||
).map(desc ->
|
||||
{
|
||||
PluginListItem listItem = new PluginListItem(this, desc);
|
||||
PluginListItem listItem = new PluginListItem(this, desc, externalPluginManager);
|
||||
listItem.setPinned(pinnedPlugins.contains(desc.getName()));
|
||||
listItem.setColor(getColorByCategory(listItem.getPluginType()));
|
||||
|
||||
|
||||
@@ -67,6 +67,11 @@ public class RuneLiteSplashScreen extends JFrame
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
public static boolean showing()
|
||||
{
|
||||
return INSTANCE != null;
|
||||
}
|
||||
|
||||
public static void setError(String title, String content)
|
||||
{
|
||||
if (INSTANCE != null)
|
||||
|
||||
@@ -34,9 +34,6 @@ import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
@@ -54,9 +51,6 @@ import net.runelite.client.util.LinkBrowser;
|
||||
@Slf4j
|
||||
public class InfoPanel extends JPanel
|
||||
{
|
||||
private static final String RUNELITE_VERSION = "runelite.version";
|
||||
private static final String RUNELITE_PLUS_VERSION = "open.osrs.version";
|
||||
private static final String RUNELITE_PLUS_DATE = "open.osrs.builddate";
|
||||
private static final Color DARK_GREY = new Color(10, 10, 10, 255);
|
||||
|
||||
private static final BufferedImage TRANSPARENT_LOGO = ImageUtil.getResourceStreamFromClass(InfoPanel.class, "/openosrs.png");
|
||||
@@ -67,16 +61,6 @@ public class InfoPanel extends JPanel
|
||||
|
||||
public InfoPanel()
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
try (InputStream in = getClass().getResourceAsStream("/open.osrs.properties"))
|
||||
{
|
||||
properties.load(in);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("unable to load propertries", ex);
|
||||
}
|
||||
|
||||
this.setLayout(new GridBagLayout());
|
||||
this.setPreferredSize(PANEL_SIZE);
|
||||
this.setBackground(new Color(38, 38, 38));
|
||||
@@ -104,11 +88,11 @@ public class InfoPanel extends JPanel
|
||||
c.weighty = 0;
|
||||
|
||||
// OpenOSRS version
|
||||
this.add(createPanelTextButton("OpenOSRS Version: " + properties.getProperty(RUNELITE_PLUS_VERSION)), c);
|
||||
this.add(createPanelTextButton("OpenOSRS Version: " + RuneLiteProperties.getPlusVersion()), c);
|
||||
c.gridy++;
|
||||
|
||||
// Build date
|
||||
this.add(createPanelTextButton("Build date: " + properties.getProperty(RUNELITE_PLUS_DATE)), c);
|
||||
this.add(createPanelTextButton("Build date: " + RuneLiteProperties.getPlusDate()), c);
|
||||
c.gridy++;
|
||||
|
||||
final JLabel logsFolder = createPanelButton("Open logs folder", null, () -> LinkBrowser.openLocalFile(LOGS_DIR));
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by rumatoest at github.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
/**
|
||||
* The Class AppLock.
|
||||
*
|
||||
* @author Vladislav Zablotsky
|
||||
*/
|
||||
public class AppLock
|
||||
{
|
||||
|
||||
private static CrossLock lockInstance;
|
||||
|
||||
/**
|
||||
* Set lock for application instance.
|
||||
* Method must be run only one time at application start.
|
||||
*
|
||||
* @param lockId Unique lock identifiers
|
||||
* @return true if succeeded
|
||||
*/
|
||||
public synchronized boolean lock(String lockId)
|
||||
{
|
||||
if (lockInstance == null)
|
||||
{
|
||||
lockInstance = new CrossLock("application_" + lockId);
|
||||
}
|
||||
return lockInstance.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to release application lock.
|
||||
* Thus another application instances will be able to use lock with current ID.
|
||||
*/
|
||||
public synchronized void release()
|
||||
{
|
||||
if (lockInstance != null)
|
||||
{
|
||||
lockInstance.clear();
|
||||
}
|
||||
lockInstance = null;
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by rumatoest at github.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package net.runelite.client.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import static net.runelite.client.RuneLite.RUNELITE_DIR;
|
||||
|
||||
/**
|
||||
* Universal cross application instances locker.
|
||||
* Allow you to create simple lock like object which can be used for
|
||||
* different application instances. Basic idea is simple - just a simple file lock.
|
||||
* <br />
|
||||
* All you need is to define unique key for each lock type.
|
||||
*
|
||||
* @author Vladislav Zablotsky
|
||||
*/
|
||||
public class CrossLock
|
||||
{
|
||||
|
||||
private static final HashMap<String, CrossLock> locks = new HashMap<>();
|
||||
|
||||
private final String id;
|
||||
|
||||
private final File fileToLock;
|
||||
|
||||
private FileOutputStream fileStream;
|
||||
|
||||
private FileChannel fileStreamChannel;
|
||||
|
||||
private FileLock lockOnFile;
|
||||
|
||||
/**
|
||||
* Will create or retrieve lock instance.
|
||||
* Each lock id is unique among all you instances,
|
||||
* thus only one instance can acquire lock for this id.
|
||||
*
|
||||
* @param lockId Unique lock identifier
|
||||
* @return Not null
|
||||
*/
|
||||
public static CrossLock get(String lockId)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
return locks.get(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronized (CrossLock.class)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
return locks.get(lockId);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrossLock cl = new CrossLock(lockId);
|
||||
locks.put(lockId, cl);
|
||||
return cl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove lock object for specific id and release lock if any.
|
||||
*
|
||||
* @param lockId Unique lock identifier
|
||||
*/
|
||||
public static void remove(String lockId)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
CrossLock lock = null;
|
||||
synchronized (CrossLock.class)
|
||||
{
|
||||
if (locks.containsKey(lockId))
|
||||
{
|
||||
lock = locks.remove(lockId);
|
||||
}
|
||||
}
|
||||
if (lock != null)
|
||||
{
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CrossLock(String lockId)
|
||||
{
|
||||
this.id = lockId;
|
||||
fileToLock = new File(RUNELITE_DIR, lockId + ".app_lock");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return lock instance identifier.
|
||||
*/
|
||||
public String id()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate lock.
|
||||
* Note! This is only cross application (cross instances) lock. It will not work
|
||||
* as lock inside single application instance.
|
||||
*
|
||||
* @return true if lock was acquire or false
|
||||
*/
|
||||
public synchronized boolean lock()
|
||||
{
|
||||
if (lockOnFile != null && lockOnFile.isValid())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
String lockContent = "#Java AppLock Object\n#Locked by key: " + id() + "\r\n";
|
||||
try
|
||||
{
|
||||
if (fileToLock.exists())
|
||||
{
|
||||
fileToLock.createNewFile();
|
||||
}
|
||||
fileStream = new FileOutputStream(fileToLock);
|
||||
fileStreamChannel = fileStream.getChannel();
|
||||
lockOnFile = fileStreamChannel.tryLock();
|
||||
if (lockOnFile != null)
|
||||
{
|
||||
fileStream.write(lockContent.getBytes());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!(ex instanceof OverlappingFileLockException))
|
||||
{
|
||||
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return lockOnFile != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release lock associated with this object.
|
||||
*/
|
||||
public synchronized void release()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lockOnFile != null && lockOnFile.isValid())
|
||||
{
|
||||
lockOnFile.release();
|
||||
}
|
||||
lockOnFile = null;
|
||||
|
||||
if (fileStream != null)
|
||||
{
|
||||
fileStream.close();
|
||||
fileStream = null;
|
||||
}
|
||||
|
||||
if (fileStreamChannel != null && fileStreamChannel.isOpen())
|
||||
{
|
||||
fileStreamChannel.close();
|
||||
}
|
||||
fileStreamChannel = null;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.getLogger(AppLock.class.getName()).log(Level.WARNING,
|
||||
"Can not get application lock for id=" + id() + "\n" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release lock and remove lock file.
|
||||
*/
|
||||
public synchronized void clear()
|
||||
{
|
||||
release();
|
||||
if (fileToLock.exists())
|
||||
{
|
||||
fileToLock.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable
|
||||
{
|
||||
this.clear();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package net.runelite.client.util;
|
||||
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.client.RuneLite;
|
||||
import net.runelite.client.config.OpenOSRSConfig;
|
||||
import net.runelite.client.events.ConfigChanged;
|
||||
import net.runelite.client.ui.RuneLiteSplashScreen;
|
||||
import org.jgroups.Address;
|
||||
import org.jgroups.JChannel;
|
||||
import org.jgroups.Message;
|
||||
import org.jgroups.ReceiverAdapter;
|
||||
import org.jgroups.View;
|
||||
import org.jgroups.util.Util;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class Groups extends ReceiverAdapter
|
||||
{
|
||||
private final OpenOSRSConfig openOSRSConfig;
|
||||
private final JChannel channel;
|
||||
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private int instanceCount;
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private Map<String, List<Address>> messageMap = new HashMap<>();
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private final PublishSubject<Message> messageStringSubject = PublishSubject.create();
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private final PublishSubject<Message> messageObjectSubject = PublishSubject.create();
|
||||
|
||||
@Inject
|
||||
public Groups(OpenOSRSConfig openOSRSConfig) throws Exception
|
||||
{
|
||||
this.openOSRSConfig = openOSRSConfig;
|
||||
|
||||
this.channel = new JChannel(RuneLite.class.getResourceAsStream("/udp.xml"))
|
||||
.setName(RuneLite.uuid)
|
||||
.setReceiver(this)
|
||||
.setDiscardOwnMessages(true)
|
||||
.connect("openosrs");
|
||||
}
|
||||
|
||||
public void broadcastSring(String command)
|
||||
{
|
||||
send(null, command);
|
||||
}
|
||||
|
||||
public void sendConfig(Address destination, ConfigChanged configChanged)
|
||||
{
|
||||
if (!openOSRSConfig.localSync() || RuneLiteSplashScreen.showing() || instanceCount < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] buffer = Util.objectToByteBuffer(configChanged);
|
||||
Message message = new Message()
|
||||
.setDest(destination)
|
||||
.setBuffer(buffer);
|
||||
|
||||
channel.send(message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendString(String command)
|
||||
{
|
||||
String[] messageObject = command.split(";");
|
||||
String pluginId = messageObject[1];
|
||||
|
||||
messageMap.put(pluginId, new ArrayList<>());
|
||||
|
||||
for (Address member : channel.getView().getMembers())
|
||||
{
|
||||
if (member.toString().equals(RuneLite.uuid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
messageMap.get(pluginId).add(member);
|
||||
send(member, command);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(Address destination, String command)
|
||||
{
|
||||
if (!openOSRSConfig.localSync() || RuneLiteSplashScreen.showing() || instanceCount < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
channel.send(new Message(destination, command));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewAccepted(View view)
|
||||
{
|
||||
instanceCount = view.getMembers().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Message message)
|
||||
{
|
||||
if (RuneLiteSplashScreen.showing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.getObject() instanceof String)
|
||||
{
|
||||
messageStringSubject.onNext(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
messageObjectSubject.onNext(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,5 @@
|
||||
</root>
|
||||
|
||||
<logger name="org.pf4j.AbstractPluginManager" level="OFF"/>
|
||||
<logger name="org.jgroups" level="ERROR"/>
|
||||
</configuration>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 211 B |
54
runelite-client/src/main/resources/udp.xml
Normal file
54
runelite-client/src/main/resources/udp.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<!--
|
||||
Default stack using IP multicasting. It is similar to the "udp"
|
||||
stack in stacks.xml, but doesn't use streaming state transfer and flushing
|
||||
author: Bela Ban
|
||||
-->
|
||||
|
||||
<config xmlns="urn:org:jgroups"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd">
|
||||
<UDP
|
||||
mcast_port="${jgroups.udp.mcast_port:45588}"
|
||||
ip_ttl="4"
|
||||
tos="8"
|
||||
ucast_recv_buf_size="5M"
|
||||
ucast_send_buf_size="5M"
|
||||
mcast_recv_buf_size="5M"
|
||||
mcast_send_buf_size="5M"
|
||||
max_bundle_size="64K"
|
||||
enable_diagnostics="true"
|
||||
thread_naming_pattern="cl"
|
||||
|
||||
thread_pool.min_threads="0"
|
||||
thread_pool.max_threads="20"
|
||||
thread_pool.keep_alive_time="30000"/>
|
||||
|
||||
<PING/>
|
||||
<MERGE3 max_interval="30000"
|
||||
min_interval="10000"/>
|
||||
<FD_SOCK/>
|
||||
<FD_ALL/>
|
||||
<VERIFY_SUSPECT timeout="1500"/>
|
||||
<BARRIER/>
|
||||
<pbcast.NAKACK2 xmit_interval="500"
|
||||
xmit_table_num_rows="100"
|
||||
xmit_table_msgs_per_row="2000"
|
||||
xmit_table_max_compaction_time="30000"
|
||||
use_mcast_xmit="false"
|
||||
discard_delivered_msgs="true"/>
|
||||
<UNICAST3 xmit_interval="500"
|
||||
xmit_table_num_rows="100"
|
||||
xmit_table_msgs_per_row="2000"
|
||||
xmit_table_max_compaction_time="60000"
|
||||
conn_expiry_timeout="0"/>
|
||||
<pbcast.STABLE desired_avg_gossip="50000"
|
||||
max_bytes="4M"/>
|
||||
<pbcast.GMS print_local_addr="false" join_timeout="2000"/>
|
||||
<UFC max_credits="2M"
|
||||
min_threshold="0.4"/>
|
||||
<MFC max_credits="2M"
|
||||
min_threshold="0.4"/>
|
||||
<FRAG2 frag_size="60K"/>
|
||||
<RSVP resend_interval="2000" timeout="10000"/>
|
||||
<pbcast.STATE_TRANSFER/>
|
||||
</config>
|
||||
@@ -116,7 +116,7 @@ public class PluginManagerTest
|
||||
@Test
|
||||
public void testLoadPlugins() throws Exception
|
||||
{
|
||||
PluginManager pluginManager = new PluginManager(null, null, null, null);
|
||||
PluginManager pluginManager = new PluginManager(null, null, null, null, null, null);
|
||||
pluginManager.setOutdated(true);
|
||||
pluginManager.loadCorePlugins();
|
||||
Collection<Plugin> plugins = pluginManager.getPlugins();
|
||||
@@ -127,7 +127,7 @@ public class PluginManagerTest
|
||||
.count();
|
||||
assertEquals(expected, plugins.size());
|
||||
|
||||
pluginManager = new PluginManager(null, null, null, null);
|
||||
pluginManager = new PluginManager(null, null, null, null, null, null);
|
||||
pluginManager.loadCorePlugins();
|
||||
plugins = pluginManager.getPlugins();
|
||||
|
||||
@@ -145,7 +145,7 @@ public class PluginManagerTest
|
||||
modules.add(new GraphvizModule());
|
||||
modules.add(new RuneLiteModule(() -> null, RuneLite.DEFAULT_CONFIG_FILE));
|
||||
|
||||
PluginManager pluginManager = new PluginManager(null, null, null, null);
|
||||
PluginManager pluginManager = new PluginManager(null, null, null, null, null, null);
|
||||
pluginManager.loadCorePlugins();
|
||||
modules.addAll(pluginManager.getPlugins());
|
||||
|
||||
@@ -194,10 +194,10 @@ public class PluginManagerTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventbusAnnotations() throws PluginInstantiationException
|
||||
public void testEventbusAnnotations() throws Exception
|
||||
{
|
||||
EventBus eventbus = new EventBus();
|
||||
PluginManager pluginManager = new PluginManager(eventbus, null, null, null)
|
||||
PluginManager pluginManager = new PluginManager(eventbus, null, null, null, null, null)
|
||||
{
|
||||
@Override
|
||||
public boolean isPluginEnabled(Plugin plugin)
|
||||
|
||||
Reference in New Issue
Block a user