Files
runelite/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginManager.java

1074 lines
28 KiB
Java

/*
* 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 com.openosrs.client.plugins;
import com.google.common.collect.Lists;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.Graphs;
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.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
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.Named;
import javax.inject.Singleton;
import javax.swing.JOptionPane;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLite;
import static com.openosrs.client.OpenOSRS.EXTERNALPLUGIN_DIR;
import static com.openosrs.client.OpenOSRS.SYSTEM_VERSION;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigManager;
import com.openosrs.client.config.OpenOSRSConfig;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.ConfigChanged;
import com.openosrs.client.events.ExternalPluginChanged;
import com.openosrs.client.events.ExternalRepositoryChanged;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginInstantiationException;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.ui.ClientUI;
import com.openosrs.client.ui.OpenOSRSSplashScreen;
import com.openosrs.client.util.Groups;
import com.openosrs.client.util.MiscUtils;
import com.openosrs.client.util.SwingUtil;
import org.jgroups.Message;
import org.pf4j.DefaultPluginManager;
import org.pf4j.DependencyResolver;
import org.pf4j.PluginDependency;
import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginWrapper;
import org.pf4j.update.DefaultUpdateRepository;
import org.pf4j.update.PluginInfo;
import org.pf4j.update.UpdateManager;
import org.pf4j.update.UpdateRepository;
import org.pf4j.update.VerifyException;
@Slf4j
@Singleton
public class ExternalPluginManager
{
public static final String DEFAULT_PLUGIN_REPOS = "OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/";
static final String DEVELOPMENT_MANIFEST_PATH = "build/tmp/jar/MANIFEST.MF";
public static ArrayList<ClassLoader> pluginClassLoaders = new ArrayList<>();
private final net.runelite.client.plugins.PluginManager runelitePluginManager;
private org.pf4j.PluginManager externalPluginManager;
@Getter(AccessLevel.PUBLIC)
private final List<UpdateRepository> repositories = new ArrayList<>();
private final OpenOSRSConfig openOSRSConfig;
private final EventBus eventBus;
private final ExecutorService executorService;
private final ConfigManager configManager;
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 final boolean safeMode;
@Inject
public ExternalPluginManager(
@Named("safeMode") final boolean safeMode,
net.runelite.client.plugins.PluginManager pluginManager,
OpenOSRSConfig openOSRSConfig,
EventBus eventBus,
ExecutorService executorService,
ConfigManager configManager,
Groups groups)
{
this.safeMode = safeMode;
this.runelitePluginManager = pluginManager;
this.openOSRSConfig = openOSRSConfig;
this.eventBus = eventBus;
this.executorService = executorService;
this.configManager = configManager;
this.groups = groups;
//noinspection ResultOfMethodCallIgnored
EXTERNALPLUGIN_DIR.mkdirs();
initPluginManager();
groups.getMessageStringSubject()
.subscribe(this::receive);
}
private void initPluginManager()
{
externalPluginManager = new ExternalPf4jPluginManager(this);
externalPluginManager.setSystemVersion(SYSTEM_VERSION);
}
public boolean doesGhRepoExist(String owner, String name)
{
return doesRepoExist("gh:" + owner + "/" + name);
}
/**
* Note that {@link UpdateManager#addRepository} checks if the repo exists, however it throws an exception which is bad
*/
public boolean doesRepoExist(String id)
{
return repositories.stream().anyMatch((repo) -> repo.getId().equals(id));
}
private static URL toRepositoryUrl(String owner, String name) throws MalformedURLException
{
return new URL("https://raw.githubusercontent.com/" + owner + "/" + name + "/master/");
}
public static boolean testGHRepository(String owner, String name)
{
try
{
return testRepository(toRepositoryUrl(owner, name));
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
return false;
}
public static boolean testRepository(URL url)
{
final List<UpdateRepository> repositories = new ArrayList<>();
repositories.add(new DefaultUpdateRepository("repository-testing", url));
DefaultPluginManager testPluginManager = new DefaultPluginManager(EXTERNALPLUGIN_DIR.toPath());
UpdateManager updateManager = new UpdateManager(testPluginManager, repositories);
return updateManager.getPlugins().size() <= 0;
}
public static <T> Predicate<T> not(Predicate<T> t)
{
return t.negate();
}
public void startExternalPluginManager()
{
try
{
externalPluginManager.loadPlugins();
}
catch (Exception ex)
{
if (ex instanceof DependencyResolver.DependenciesNotFoundException)
{
List<String> deps = ((DependencyResolver.DependenciesNotFoundException) ex).getDependencies();
log.error("The following dependencies are missing: {}", deps);
for (String dep : deps)
{
updateManager.installPlugin(dep, null);
}
startExternalPluginManager();
}
else
{
log.error("Could not load plugins", ex);
}
}
}
public void startExternalUpdateManager()
{
if (!tryLoadNewFormat())
{
log.debug("Load new format failed.");
loadOldFormat();
}
updateManager = new UpdateManager(externalPluginManager, repositories);
saveConfig();
}
public boolean tryLoadNewFormat()
{
try
{
duplicateCheck();
log.debug("Trying to load new format: {}", openOSRSConfig.getExternalRepositories());
repositories.clear();
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
{
String[] split = keyval.split("\\|");
if (split.length != 2)
{
log.debug("Split length invalid: {}", keyval);
repositories.clear();
return false;
}
String id = split[0];
String url = split[1];
if (!url.endsWith("/"))
{
url = url.concat("/");
}
if (id.contains("https://raw.githubusercontent.com/"))
{
id = "gh:" + id.substring(id.indexOf("https://raw.githubusercontent.com/")).replace("/master", "")
.replace("https://raw.githubusercontent.com/", "");
if (id.endsWith("/"))
{
id = id.substring(0, id.lastIndexOf("/"));
}
}
repositories.add(new DefaultUpdateRepository(id, new URL(url)));
}
}
catch (ArrayIndexOutOfBoundsException | MalformedURLException e)
{
log.error("Error in new format", e);
repositories.clear();
return false;
}
return true;
}
public void loadOldFormat()
{
try
{
log.debug("Loading old format.");
repositories.clear();
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
{
log.debug("KeyVal: {}", keyval);
String id = keyval.substring(0, keyval.lastIndexOf(":https"));
String url = keyval.substring(keyval.lastIndexOf("https"));
DefaultUpdateRepository defaultRepo = new DefaultUpdateRepository(id, new URL(url));
repositories.add(defaultRepo);
log.debug("Added Repo: {}", defaultRepo.getUrl());
}
}
catch (MalformedURLException e)
{
log.error("Old repository format contained malformed url", e);
}
catch (StringIndexOutOfBoundsException e)
{
log.error("Error loading external repositories. They have been reset.");
openOSRSConfig.setExternalRepositories(DEFAULT_PLUGIN_REPOS);
}
updateManager = new UpdateManager(externalPluginManager, repositories);
}
public void addGHRepository(String owner, String name)
{
try
{
addRepository("gh:" + owner + "/" + name, toRepositoryUrl(owner, name));
}
catch (MalformedURLException e)
{
log.error("GitHub repostitory could not be added (owner={}, name={})", owner, name, e);
}
}
public void addRepository(String key, URL url)
{
DefaultUpdateRepository respository = new DefaultUpdateRepository(key, url);
updateManager.addRepository(respository);
eventBus.post(new ExternalRepositoryChanged(key, true));
saveConfig();
}
public void removeRepository(String owner)
{
updateManager.removeRepository(owner);
eventBus.post(new ExternalRepositoryChanged(owner, false));
saveConfig();
}
private void saveConfig()
{
StringBuilder config = new StringBuilder();
for (UpdateRepository repository : updateManager.getRepositories())
{
config.append(repository.getId());
config.append("|");
config.append(MiscUtils.urlToStringEncoded(repository.getUrl()));
config.append(";");
}
config.deleteCharAt(config.lastIndexOf(";"));
openOSRSConfig.setExternalRepositories(config.toString());
}
public void setWarning(boolean val)
{
configManager.setConfiguration("openosrs", "warning", val);
}
public boolean getWarning()
{
return openOSRSConfig.warning();
}
/**
* This method is a fail safe to ensure that no duplicate
* repositories end up getting saved to the config.
* <p>
* Configs that had duplicate repos prior to this should
* be updated and set correctly.
*/
private void duplicateCheck()
{
String[] split = openOSRSConfig.getExternalRepositories().split(";");
if (split.length <= 0)
{
return;
}
Set<String> strings = new HashSet<>();
boolean duplicates = false;
for (String s : split)
{
if (strings.contains(s))
{
log.error("Duplicate Repo: {}", s);
duplicates = true;
continue;
}
strings.add(s);
}
if (!duplicates)
{
log.info("No duplicates found.");
return;
}
StringBuilder sb = new StringBuilder();
for (String string : strings)
{
sb.append(string);
sb.append(";");
}
sb.deleteCharAt(sb.lastIndexOf(";"));
String duplicateFix = sb.toString();
log.info("Duplicate Repos detected, setting them to: {}", duplicateFix);
openOSRSConfig.setExternalRepositories(duplicateFix);
}
private void scanAndInstantiate(List<Plugin> plugins, boolean init, boolean initConfig)
{
OpenOSRSSplashScreen.stage(.66, "Loading external plugins");
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder
.directed()
.build();
for (Plugin plugin : plugins)
{
Class<? extends Plugin> clazz = plugin.getClass();
PluginDescriptor pluginDescriptor = clazz.getAnnotation(PluginDescriptor.class);
try
{
if (pluginDescriptor == null)
{
if (Plugin.class.isAssignableFrom(clazz))
{
log.warn("Class {} is a plugin, but has no plugin descriptor", clazz);
}
continue;
}
else if (!Plugin.class.isAssignableFrom(clazz))
{
log.warn("Class {} has plugin descriptor, but is not a plugin", clazz);
continue;
}
}
catch (EnumConstantNotPresentException e)
{
log.warn("{} has an invalid plugin type of {}", clazz, e.getMessage());
continue;
}
if (safeMode && !pluginDescriptor.loadInSafeMode())
{
log.debug("Disabling {} due to safe mode", clazz);
// also disable the plugin from autostarting later
configManager.unsetConfiguration(RuneLiteConfig.GROUP_NAME, clazz.getSimpleName().toLowerCase());
continue;
}
@SuppressWarnings("unchecked") Class<Plugin> pluginClass = (Class<Plugin>) clazz;
graph.addNode(pluginClass);
}
// Build plugin graph
for (Class<? extends Plugin> pluginClazz : graph.nodes())
{
net.runelite.client.plugins.PluginDependency[] pluginDependencies = pluginClazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class);
for (net.runelite.client.plugins.PluginDependency pluginDependency : pluginDependencies)
{
if (graph.nodes().contains(pluginDependency.value()))
{
graph.putEdge(pluginClazz, (Class<? extends Plugin>) pluginDependency.value());
}
}
}
if (Graphs.hasCycle(graph))
{
throw new RuntimeException("Plugin dependency graph contains a cycle!");
}
List<List<Class<? extends Plugin>>> sortedPlugins = PluginManager.topologicalGroupSort(graph);
sortedPlugins = Lists.reverse(sortedPlugins);
AtomicInteger loaded = new AtomicInteger();
final long start = System.currentTimeMillis();
List<Plugin> scannedPlugins = new CopyOnWriteArrayList<>();
sortedPlugins.forEach(group ->
{
List<Future<?>> curGroup = new ArrayList<>();
group.forEach(pluginClazz ->
curGroup.add(executorService.submit(() ->
{
Plugin plugininst;
try
{
//noinspection unchecked
plugininst = instantiate(scannedPlugins, (Class<Plugin>) pluginClazz, init, initConfig);
if (plugininst == null)
{
return;
}
scannedPlugins.add(plugininst);
}
catch (PluginInstantiationException e)
{
log.warn("Error instantiating plugin!", e);
return;
}
loaded.getAndIncrement();
OpenOSRSSplashScreen.stage(.67, .75, "Loading external plugins", loaded.get(), scannedPlugins.size());
})));
curGroup.forEach(future ->
{
try
{
future.get();
}
catch (InterruptedException | ExecutionException e)
{
log.warn("Could not instantiate external plugin", e);
}
});
});
log.info("External plugin instantiation took {}ms", System.currentTimeMillis() - start);
}
@SuppressWarnings("unchecked")
private Plugin instantiate(List<Plugin> scannedPlugins, Class<Plugin> clazz, boolean init, boolean initConfig)
throws PluginInstantiationException
{
net.runelite.client.plugins.PluginDependency[] pluginDependencies =
clazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class);
List<Plugin> deps = new ArrayList<>();
for (net.runelite.client.plugins.PluginDependency pluginDependency : pluginDependencies)
{
Optional<Plugin> dependency =
Stream.concat(runelitePluginManager.getOprsPlugins().stream(), scannedPlugins.stream())
.filter(p -> p.getClass() == pluginDependency.value()).findFirst();
if (dependency.isEmpty())
{
throw new PluginInstantiationException(
"Unmet dependency for " + clazz.getSimpleName() + ": " + pluginDependency.value().getSimpleName());
}
deps.add(dependency.get());
}
log.info("Loading plugin {}", clazz.getSimpleName());
Plugin plugin;
try
{
plugin = clazz.getDeclaredConstructor().newInstance();
}
catch (ThreadDeath e)
{
throw e;
}
catch (Throwable ex)
{
throw new PluginInstantiationException(ex);
}
try
{
Injector parent = RuneLite.getInjector();
if (deps.size() > 1)
{
List<Module> modules = new ArrayList<>(deps.size());
for (Plugin p : deps)
{
// Create a module for each dependency
Module module = (Binder binder) ->
{
binder.bind((Class<Plugin>) p.getClass()).toInstance(p);
binder.install(p);
};
modules.add(module);
}
// Create a parent injector containing all of the dependencies
parent = parent.createChildInjector(modules);
}
else if (!deps.isEmpty())
{
// With only one dependency we can simply use its injector
parent = deps.get(0).injector;
}
// Create injector for the module
Module pluginModule = (Binder binder) ->
{
// Since the plugin itself is a module, it won't bind itself, so we'll bind it here
binder.bind(clazz).toInstance(plugin);
binder.install(plugin);
};
Injector pluginInjector = parent.createChildInjector(pluginModule);
pluginInjector.injectMembers(plugin);
plugin.injector = pluginInjector;
if (initConfig)
{
for (Key<?> key : pluginInjector.getBindings().keySet())
{
Class<?> type = key.getTypeLiteral().getRawType();
if (Config.class.isAssignableFrom(type))
{
if (type.getPackageName().startsWith(plugin.getClass().getPackageName()))
{
Config config = (Config) pluginInjector.getInstance(key);
configManager.setDefaultConfiguration(config, false);
}
}
}
}
if (init)
{
try
{
SwingUtil.syncExec(() ->
{
try
{
runelitePluginManager.add(plugin);
runelitePluginManager.startPlugin(plugin);
eventBus.post(new ExternalPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()),
plugin, true));
}
catch (PluginInstantiationException e)
{
throw new RuntimeException(e);
}
});
}
catch (Exception ex)
{
log.warn("unable to start plugin", ex);
}
}
else
{
runelitePluginManager.add(plugin);
}
}
catch (CreationException ex)
{
throw new PluginInstantiationException(ex);
}
catch (NoClassDefFoundError ex)
{
log.error("Plugin {} is outdated", clazz.getSimpleName());
return null;
}
log.debug("Loaded plugin {}", clazz.getSimpleName());
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()
{
externalPluginManager.startPlugins();
List<PluginWrapper> startedPlugins = getStartedPlugins();
List<Plugin> scannedPlugins = new ArrayList<>();
for (PluginWrapper plugin : startedPlugins)
{
checkDepsAndStart(startedPlugins, scannedPlugins, plugin);
}
scanAndInstantiate(scannedPlugins, false, false);
if (groups.getInstanceCount() > 1)
{
for (String pluginId : getDisabledPlugins())
{
groups.sendString("STOPEXTERNAL;" + pluginId);
}
}
else
{
for (String pluginId : getDisabledPlugins())
{
externalPluginManager.enablePlugin(pluginId);
externalPluginManager.deletePlugin(pluginId);
}
}
}
private List<Plugin> loadPlugin(String pluginId)
{
List<Plugin> scannedPlugins = new ArrayList<>();
try
{
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
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<>("");
updateManager.getRepositories().forEach(repository ->
repository.getPlugins().forEach((key, value) ->
{
if (key.equals(pluginId))
{
support.set(value.projectUrl);
}
}));
pluginsInfoMap.put(
plugin.getClass().getSimpleName(),
new HashMap<>()
{{
put("version", externalPluginManager.getPlugin(pluginId).getDescriptor().getVersion());
put("id", externalPluginManager.getPlugin(pluginId).getDescriptor().getPluginId());
put("provider", externalPluginManager.getPlugin(pluginId).getDescriptor().getProvider());
put("support", support.get());
}}
);
scannedPlugins.add(plugin);
}
}
catch (Throwable ex)
{
log.error("Plugin {} could not be loaded.", pluginId, ex);
}
return scannedPlugins;
}
private Path stopPlugin(String pluginId)
{
List<PluginWrapper> startedPlugins = List.copyOf(getStartedPlugins());
for (PluginWrapper pluginWrapper : startedPlugins)
{
if (!pluginId.equals(pluginWrapper.getDescriptor().getPluginId()))
{
continue;
}
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
for (Plugin plugin : runelitePluginManager.getOprsPlugins())
{
if (!extensions.get(0).getClass().getName().equals(plugin.getClass().getName()))
{
continue;
}
try
{
SwingUtil.syncExec(() ->
{
try
{
runelitePluginManager.stopPlugin(plugin);
}
catch (Exception e2)
{
throw new RuntimeException(e2);
}
});
runelitePluginManager.remove(plugin);
pluginClassLoaders.remove(plugin.getClass().getClassLoader());
eventBus.post(new ExternalPluginChanged(pluginId, plugin, false));
return pluginWrapper.getPluginPath();
}
catch (Exception ex)
{
log.warn("unable to stop plugin", ex);
return null;
}
}
}
return null;
}
public boolean install(String pluginId) throws VerifyException
{
if (getDisabledPlugins().contains(pluginId))
{
externalPluginManager.enablePlugin(pluginId);
externalPluginManager.startPlugin(pluginId);
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
scanAndInstantiate(loadPlugin(pluginId), true, false);
return true;
}
if (getStartedPlugins().stream().anyMatch(ev -> ev.getPluginId().equals(pluginId)))
{
return true;
}
try
{
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
// Null version returns the last release version of this plugin for given system version
if (latest == null)
{
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;
}
updateManager.installPlugin(pluginId, null);
scanAndInstantiate(loadPlugin(pluginId), true, true);
groups.broadcastSring("STARTEXTERNAL;" + pluginId);
}
catch (DependencyResolver.DependenciesNotFoundException ex)
{
uninstall(pluginId);
for (String dep : ex.getDependencies())
{
install(dep);
}
install(pluginId);
}
return false;
}
public boolean uninstall(String pluginId)
{
return uninstall(pluginId, false);
}
public boolean uninstall(String pluginId, boolean skip)
{
Path pluginPath = stopPlugin(pluginId);
if (pluginPath == null)
{
return false;
}
externalPluginManager.stopPlugin(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;
}
OpenOSRSSplashScreen.stage(.59, "Updating external plugins");
boolean error = false;
if (updateManager.hasUpdates())
{
List<PluginInfo> updates = updateManager.getUpdates();
for (PluginInfo plugin : updates)
{
PluginInfo.PluginRelease lastRelease = updateManager.getLastPluginRelease(plugin.id);
String lastVersion = lastRelease.version;
try
{
OpenOSRSSplashScreen.stage(.59, "Updating " + plugin.id + " to version " + lastVersion);
boolean updated = updateManager.updatePlugin(plugin.id, lastVersion);
if (!updated)
{
log.warn("Cannot update plugin '{}'", plugin.id);
error = true;
}
}
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;
}
}
}
if (error)
{
initPluginManager();
startExternalUpdateManager();
startExternalPluginManager();
}
}
public Set<String> getDependencies()
{
Set<String> deps = new HashSet<>();
List<PluginWrapper> startedPlugins = getStartedPlugins();
for (PluginWrapper pluginWrapper : startedPlugins)
{
for (PluginDependency pluginDependency : pluginWrapper.getDescriptor().getDependencies())
{
deps.add(pluginDependency.getPluginId());
}
}
return deps;
}
public List<String> getDisabledPlugins()
{
return externalPluginManager.getResolvedPlugins()
.stream()
.filter(not(externalPluginManager.getStartedPlugins()::contains))
.map(PluginWrapper::getPluginId)
.collect(Collectors.toList());
}
public List<PluginWrapper> getStartedPlugins()
{
return externalPluginManager.getStartedPlugins();
}
public Boolean reloadStart(String pluginId)
{
externalPluginManager.loadPlugins();
externalPluginManager.startPlugin(pluginId);
List<PluginWrapper> startedPlugins = List.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 = List.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;
}
}
}