Commits vergleichen
17 Commits
BetterBauS
...
master
Autor | SHA1 | Datum | |
---|---|---|---|
fe67e5078f | |||
5d30a9144a | |||
9493b251a6 | |||
ac8c277846 | |||
cd8706b67f | |||
355218c761 | |||
ab5c965f20 | |||
0867fbf422 | |||
4ec3c641be | |||
a0f2ce2375 | |||
af89693438 | |||
9ae3858a74 | |||
2a8c3f91e4 | |||
|
ac1ffcfbf1 | ||
|
35e3486740 | ||
|
924e3587f5 | ||
|
3fc6f53330 |
183
build.gradle
183
build.gradle
@ -17,14 +17,12 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
|
|
||||||
id 'com.github.johnrengelman.shadow' version '5.0.0'
|
id 'com.github.johnrengelman.shadow' version '5.0.0'
|
||||||
|
id 'de.steamwar.gradle' version 'RELEASE'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'de.steamwar'
|
group 'de.steamwar'
|
||||||
@ -32,35 +30,16 @@ version ''
|
|||||||
|
|
||||||
mainClassName = ''
|
mainClassName = ''
|
||||||
|
|
||||||
Properties steamwarProperties = new Properties()
|
|
||||||
if (file("steamwar.properties").exists()) {
|
|
||||||
steamwarProperties.load(file("steamwar.properties").newDataInputStream())
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
buildName = 'PersistentBungeeCore'
|
|
||||||
artifactName = 'persistentbungeecore'
|
|
||||||
|
|
||||||
uberJarName = "${buildName}-all.jar"
|
|
||||||
jarName = "${artifactName}.jar"
|
|
||||||
libs = "${buildDir}/libs"
|
|
||||||
|
|
||||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
|
||||||
operatingSystem = "windows"
|
|
||||||
} else {
|
|
||||||
operatingSystem = "unix"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.11
|
||||||
targetCompatibility = 1.8
|
targetCompatibility = 1.11
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
srcDirs = ['src/']
|
srcDirs = ['src/']
|
||||||
|
include '**/*.java', '**/*.kt'
|
||||||
}
|
}
|
||||||
resources {
|
resources {
|
||||||
srcDirs = ['src/']
|
srcDirs = ['src/']
|
||||||
@ -71,10 +50,6 @@ sourceSets {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
||||||
maven {
|
|
||||||
url = uri('https://steamwar.de/maven')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -83,153 +58,9 @@ dependencies {
|
|||||||
annotationProcessor 'org.projectlombok:lombok:1.18.22'
|
annotationProcessor 'org.projectlombok:lombok:1.18.22'
|
||||||
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
|
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
|
||||||
|
|
||||||
compileOnly 'de.steamwar:waterfall:RELEASE'
|
compileOnly swdep("waterfall")
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildProject {
|
steamwar {
|
||||||
description 'Build this project'
|
publishing = true
|
||||||
group "Steamwar"
|
|
||||||
|
|
||||||
dependsOn build
|
|
||||||
}
|
|
||||||
|
|
||||||
task finalizeProject {
|
|
||||||
description 'Finalize this project'
|
|
||||||
group "Steamwar"
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
if ("${buildDir}" == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete fileTree("${libs}").matching {
|
|
||||||
exclude("${uberJarName}")
|
|
||||||
}
|
|
||||||
file(libs + "/" + uberJarName).renameTo(file(libs + "/" + jarName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build.finalizedBy(finalizeProject)
|
|
||||||
|
|
||||||
if (steamwarProperties.containsKey("hostname")) {
|
|
||||||
String hostname = steamwarProperties.get("hostname")
|
|
||||||
String uploadPath = steamwarProperties.getOrDefault("uploadPath", "~")
|
|
||||||
|
|
||||||
String server = steamwarProperties.getOrDefault("server", "Dev1.15")
|
|
||||||
String serverStartFlags = steamwarProperties.getOrDefault("serverStartFlags", "")
|
|
||||||
|
|
||||||
task uploadProject {
|
|
||||||
description 'Upload this project'
|
|
||||||
group "Steamwar"
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
await(shell("scp ${libs}/${jarName} ${hostname}:${uploadPath}/${server}/plugins"))
|
|
||||||
if (steamwarProperties.getOrDefault("directStart", "false") == "false" && !answer("Start ${server} server?")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverStart(server, serverStartFlags, hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uploadProject.dependsOn(buildProject)
|
|
||||||
|
|
||||||
task startDevServer {
|
|
||||||
description 'Start the DevServer'
|
|
||||||
group "Steamwar"
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
serverStart(server, serverStartFlags, hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def await(Process proc) {
|
|
||||||
def out = new StringBuilder()
|
|
||||||
def err = new StringBuilder()
|
|
||||||
proc.waitForProcessOutput(out, err)
|
|
||||||
return [out, err, proc.exitValue()]
|
|
||||||
}
|
|
||||||
|
|
||||||
private def shell(String command) {
|
|
||||||
if (operatingSystem == "unix") {
|
|
||||||
return ['bash', '-c', command].execute()
|
|
||||||
} else {
|
|
||||||
return ["cmd", "/c", command].execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def serverStart(String serverName, String serverFlags, String hostname) {
|
|
||||||
def proc = shell("ssh -t ${hostname} \"./mc ${serverFlags} ${serverName}\"")
|
|
||||||
|
|
||||||
Set<String> strings = new HashSet<>()
|
|
||||||
File file = new File("${projectDir}/ignoredlog");
|
|
||||||
if (file.exists()) {
|
|
||||||
new BufferedReader(new InputStreamReader(new FileInputStream(file))).readLines().forEach({ s ->
|
|
||||||
strings.add(s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread outputThread = new Thread({
|
|
||||||
Reader reader = proc.getInputStream().newReader();
|
|
||||||
Writer writer = System.out.newWriter();
|
|
||||||
try {
|
|
||||||
while (proc.alive) {
|
|
||||||
String s = reader.readLine()
|
|
||||||
if (s == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (strings.stream().anyMatch({check -> s.contains(check)})) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
writer.write(s + "\n")
|
|
||||||
writer.flush()
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
})
|
|
||||||
outputThread.setName("${serverName} - OutputThread")
|
|
||||||
outputThread.start()
|
|
||||||
|
|
||||||
Writer writer
|
|
||||||
Thread inputThread = new Thread({
|
|
||||||
Reader reader = System.in.newReader()
|
|
||||||
writer = proc.getOutputStream().newWriter()
|
|
||||||
try {
|
|
||||||
while (proc.alive) {
|
|
||||||
String s = reader.readLine()
|
|
||||||
writer.write(s + "\n")
|
|
||||||
writer.flush()
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
})
|
|
||||||
inputThread.setName("${serverName} - InputThread")
|
|
||||||
inputThread.start()
|
|
||||||
|
|
||||||
gradle.buildFinished { buildResult ->
|
|
||||||
if (!proc.alive) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writer = proc.getOutputStream().newWriter()
|
|
||||||
writer.write("stop\n")
|
|
||||||
writer.flush()
|
|
||||||
awaitClose(proc, outputThread, inputThread)
|
|
||||||
}
|
|
||||||
awaitClose(proc, outputThread, inputThread)
|
|
||||||
};
|
|
||||||
|
|
||||||
private static def awaitClose(Process proc, Thread outputThread, Thread inputThread) {
|
|
||||||
while (proc.alive) {
|
|
||||||
Thread.sleep(10)
|
|
||||||
}
|
|
||||||
proc.closeStreams()
|
|
||||||
outputThread.interrupt()
|
|
||||||
inputThread.interrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private def answer(String question) {
|
|
||||||
while (System.in.available() > 0) System.in.read()
|
|
||||||
println(question)
|
|
||||||
boolean valid = "Yy".contains(((char) System.in.read()).toString())
|
|
||||||
while (System.in.available() > 0) System.in.read()
|
|
||||||
return valid
|
|
||||||
}
|
}
|
@ -17,4 +17,13 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven {
|
||||||
|
url = uri("https://steamwar.de/maven/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rootProject.name = 'PersistentBungeeCore'
|
rootProject.name = 'PersistentBungeeCore'
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package de.steamwar.bungeecore;
|
package de.steamwar.bungeecore;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class Arenaserver extends Subserver {
|
public class Arenaserver extends Subserver {
|
||||||
|
|
||||||
private final String mode;
|
private final String mode;
|
||||||
@ -7,21 +10,10 @@ public class Arenaserver extends Subserver {
|
|||||||
private final boolean allowMerge;
|
private final boolean allowMerge;
|
||||||
|
|
||||||
public Arenaserver(String serverName, String mode, String map, boolean allowMerge, int port, ProcessBuilder processBuilder, Runnable shutdownCallback) {
|
public Arenaserver(String serverName, String mode, String map, boolean allowMerge, int port, ProcessBuilder processBuilder, Runnable shutdownCallback) {
|
||||||
super(Servertype.ARENA, serverName, port, processBuilder, shutdownCallback);
|
super(Servertype.ARENA, serverName, port, processBuilder, shutdownCallback, null);
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.map = map;
|
this.map = map;
|
||||||
this.allowMerge = allowMerge;
|
this.allowMerge = allowMerge;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMode() {
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMap() {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAllowMerge() {
|
|
||||||
return allowMerge;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,26 +19,47 @@
|
|||||||
|
|
||||||
package de.steamwar.bungeecore;
|
package de.steamwar.bungeecore;
|
||||||
|
|
||||||
import java.util.UUID;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class Bauserver extends Subserver {
|
public class Bauserver extends Subserver {
|
||||||
|
private static final Map<UUID, Bauserver> servers = new HashMap<>();
|
||||||
|
public static Bauserver get(UUID owner) {
|
||||||
|
synchronized (servers) {
|
||||||
|
return servers.get(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final UUID owner;
|
private final UUID owner;
|
||||||
|
|
||||||
public Bauserver(String serverName, UUID owner, int port, String... command) {
|
|
||||||
this(serverName, owner, port, () -> {}, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bauserver(String serverName, UUID owner, int port, Runnable shutdownCallback, String... command) {
|
|
||||||
this(serverName, owner, port, new ProcessBuilder(command), shutdownCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
|
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
|
||||||
super(Servertype.BAUSERVER, serverName, port, processBuilder, shutdownCallback);
|
this(serverName, owner, port, processBuilder, shutdownCallback, null);
|
||||||
this.owner = owner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getOwner(){
|
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback){
|
||||||
return owner;
|
super(Servertype.BAUSERVER, serverName, port, processBuilder, shutdownCallback, failureCallback);
|
||||||
|
this.owner = owner;
|
||||||
|
|
||||||
|
synchronized (servers) {
|
||||||
|
servers.put(owner, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void register() {
|
||||||
|
super.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void unregister() {
|
||||||
|
synchronized (servers) {
|
||||||
|
servers.remove(owner);
|
||||||
|
}
|
||||||
|
super.unregister();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,46 @@
|
|||||||
|
|
||||||
package de.steamwar.bungeecore;
|
package de.steamwar.bungeecore;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class Builderserver extends Subserver {
|
public class Builderserver extends Subserver {
|
||||||
|
|
||||||
|
private static final Map<String, Builderserver> servers = new HashMap<>();
|
||||||
|
public static Builderserver get(String map) {
|
||||||
|
synchronized (servers) {
|
||||||
|
return servers.get(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final String map;
|
private final String map;
|
||||||
|
|
||||||
public Builderserver(String serverName, String map, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
|
public Builderserver(String serverName, String map, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
|
||||||
super(Servertype.BUILDER, serverName, port, processBuilder, shutdownCallback);
|
this(serverName, map, port, processBuilder, shutdownCallback, null);
|
||||||
this.map = map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMap() {
|
public Builderserver(String serverName, String map, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback){
|
||||||
return map;
|
super(Servertype.BUILDER, serverName, port, processBuilder, shutdownCallback, failureCallback);
|
||||||
|
this.map = map;
|
||||||
|
|
||||||
|
synchronized (servers) {
|
||||||
|
servers.put(map, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void register() {
|
||||||
|
super.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void unregister() {
|
||||||
|
synchronized (servers) {
|
||||||
|
servers.remove(map);
|
||||||
|
}
|
||||||
|
super.unregister();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,39 +19,38 @@
|
|||||||
|
|
||||||
package de.steamwar.bungeecore;
|
package de.steamwar.bungeecore;
|
||||||
|
|
||||||
|
import de.steamwar.persistent.Reflection;
|
||||||
import net.md_5.bungee.api.CommandSender;
|
import net.md_5.bungee.api.CommandSender;
|
||||||
import net.md_5.bungee.api.bungeepluginmanager.ModifiedPluginEventBus;
|
import de.steamwar.persistent.ModifiedPluginEventBus;
|
||||||
import net.md_5.bungee.api.bungeepluginmanager.PluginUtils;
|
import de.steamwar.persistent.PluginUtils;
|
||||||
import net.md_5.bungee.api.bungeepluginmanager.ReflectionUtils;
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
import net.md_5.bungee.api.plugin.Command;
|
import net.md_5.bungee.api.plugin.Command;
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.api.plugin.PluginDescription;
|
||||||
import net.md_5.bungee.api.plugin.PluginManager;
|
import net.md_5.bungee.api.plugin.PluginManager;
|
||||||
|
import net.md_5.bungee.event.EventBus;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class Persistent extends Plugin {
|
public class Persistent extends Plugin {
|
||||||
|
|
||||||
private static Persistent instance;
|
public static final String PREFIX = "§eSteam§8War» ";
|
||||||
private static String chatPrefix = "";
|
|
||||||
private static String lobbyServer = "";
|
private static final Reflection.FieldAccessor<EventBus> eventBus = Reflection.getField(PluginManager.class, "eventBus", EventBus.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
ReflectionUtils.setFieldValue(getProxy().getPluginManager(), "eventBus", new ModifiedPluginEventBus());
|
eventBus.set(getProxy().getPluginManager(), new ModifiedPluginEventBus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable(){
|
public void onEnable(){
|
||||||
instance = this;
|
|
||||||
|
|
||||||
getProxy().getPluginManager().registerCommand(this, new Command("softreload", "bungeecore.softreload") {
|
getProxy().getPluginManager().registerCommand(this, new Command("softreload", "bungeecore.softreload") {
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandSender sender, String[] args) {
|
public void execute(CommandSender sender, String[] args) {
|
||||||
// Copied from https://www.spigotmc.org/resources/bungeepluginmanager-manage-your-bungee-plugin-at-runtime.63861/
|
softreload();
|
||||||
PluginManager pluginManager = getProxy().getPluginManager();
|
|
||||||
Plugin bungeecore = pluginManager.getPlugin("BungeeCore");
|
|
||||||
PluginUtils.unloadPlugin(bungeecore);
|
|
||||||
PluginUtils.loadPlugin(new File(getProxy().getPluginsFolder(), "BungeeCore.jar"));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -61,23 +60,30 @@ public class Persistent extends Plugin {
|
|||||||
Subserver.shutdown();
|
Subserver.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLobbyServer(String lobbyServer) {
|
public void softreload() {
|
||||||
Persistent.lobbyServer = lobbyServer;
|
Map<String, Command> commandMap = PluginUtils.CommandMap.get(getProxy().getPluginManager());
|
||||||
|
Map<String, Plugin> plugins = PluginUtils.Plugins.get(getProxy().getPluginManager());
|
||||||
|
Object libraryLoader = PluginUtils.LibraryLoader.get(getProxy().getPluginManager());
|
||||||
|
PluginDescription desc = PluginUtils.loadDescription(getProxy().getPluginManager(), new File(getProxy().getPluginsFolder(), "BungeeCore.jar"));
|
||||||
|
|
||||||
|
getProxy().getScheduler().runAsync(this, () -> {
|
||||||
|
getProxy().broadcast(TextComponent.fromLegacyText(PREFIX + "§eNetwork update is starting§8."));
|
||||||
|
try {
|
||||||
|
PluginUtils.unloadPlugin(getProxy(), commandMap, plugins, getProxy().getPluginManager().getPlugin("BungeeCore"));
|
||||||
|
PluginUtils.loadPlugin(plugins, libraryLoader, desc);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
getLogger().log(Level.SEVERE, "Errors during softreload", t);
|
||||||
|
getProxy().broadcast(TextComponent.fromLegacyText(PREFIX + "§cNetwork update failed§8, §cexpect network restart soon§8."));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setChatPrefix(String prefix){
|
getProxy().broadcast(TextComponent.fromLegacyText(PREFIX + "§eNetwork update complete§8."));
|
||||||
chatPrefix = prefix;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getLobbyServer() {
|
@Deprecated
|
||||||
return lobbyServer;
|
public static void setLobbyServer(String lobbyServer) {}
|
||||||
}
|
|
||||||
|
|
||||||
static String getPrefix(){
|
@Deprecated
|
||||||
return chatPrefix;
|
public static void setChatPrefix(String prefix) {}
|
||||||
}
|
|
||||||
|
|
||||||
static Persistent getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package de.steamwar.bungeecore;
|
package de.steamwar.bungeecore;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import net.md_5.bungee.api.ChatMessageType;
|
import net.md_5.bungee.api.ChatMessageType;
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
@ -26,25 +27,25 @@ import net.md_5.bungee.api.chat.TextComponent;
|
|||||||
import net.md_5.bungee.api.config.ServerInfo;
|
import net.md_5.bungee.api.config.ServerInfo;
|
||||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
public class Subserver {
|
||||||
public class Subserver implements Runnable {
|
|
||||||
|
|
||||||
private static final List<Subserver> serverList = new LinkedList<>();
|
|
||||||
private static final Logger logger = ProxyServer.getInstance().getLogger();
|
private static final Logger logger = ProxyServer.getInstance().getLogger();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final List<Subserver> serverList = new LinkedList<>();
|
||||||
|
private static final Map<ServerInfo, Subserver> infoToServer = new HashMap<>();
|
||||||
|
|
||||||
public static Subserver getSubserver(ProxiedPlayer p) {
|
public static Subserver getSubserver(ProxiedPlayer p) {
|
||||||
synchronized (serverList) {
|
synchronized (serverList) {
|
||||||
for(int i = serverList.size()-1; i >= 0; i--){
|
for(int i = serverList.size()-1; i >= 0; i--){
|
||||||
@ -54,43 +55,45 @@ public class Subserver implements Runnable {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Subserver getSubserver(ServerInfo server) {
|
public static Subserver getSubserver(ServerInfo server) {
|
||||||
synchronized (serverList) {
|
synchronized (serverList) {
|
||||||
for(Subserver s : serverList)
|
return infoToServer.get(server);
|
||||||
if(s.server == server)
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
public static List<Subserver> getServerList(){
|
|
||||||
return serverList;
|
static void shutdown(){
|
||||||
|
while (!serverList.isEmpty()) {
|
||||||
|
Subserver server = serverList.get(0);
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String serverName;
|
private final String serverName;
|
||||||
|
private final boolean checkpoint;
|
||||||
private final Runnable shutdownCallback;
|
private final Runnable shutdownCallback;
|
||||||
|
private final Consumer<Exception> failureCallback;
|
||||||
private final Process process;
|
private final Process process;
|
||||||
private final PrintWriter writer;
|
private final PrintWriter writer;
|
||||||
|
@Getter
|
||||||
private final ServerInfo server;
|
private final ServerInfo server;
|
||||||
|
@Getter
|
||||||
private final Servertype type;
|
private final Servertype type;
|
||||||
private final Thread thread;
|
private final Thread thread;
|
||||||
|
@Getter
|
||||||
private boolean started;
|
private boolean started;
|
||||||
|
|
||||||
private final List<ProxiedPlayer> cachedPlayers = new LinkedList<>();
|
private final List<ProxiedPlayer> cachedPlayers = new LinkedList<>();
|
||||||
|
@Getter
|
||||||
private final Map<ProxiedPlayer, String> tablistNames = new HashMap<>();
|
private final Map<ProxiedPlayer, String> tablistNames = new HashMap<>();
|
||||||
|
|
||||||
public Subserver(Servertype type, String serverName, int port, String... command){
|
protected Subserver(Servertype type, String serverName, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
|
||||||
this(type, serverName, port, () -> {}, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subserver(Servertype type, String serverName, int port, Runnable shutdownCallback, String... command){
|
|
||||||
this(type, serverName, port, new ProcessBuilder(command), shutdownCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subserver(Servertype type, String serverName, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
|
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.shutdownCallback = shutdownCallback;
|
this.shutdownCallback = shutdownCallback;
|
||||||
|
this.failureCallback = failureCallback == null ? this::fatalError : failureCallback;
|
||||||
|
this.checkpoint = processBuilder.command().contains("criu");
|
||||||
|
|
||||||
try{
|
try{
|
||||||
this.process = processBuilder.start();
|
this.process = processBuilder.start();
|
||||||
@ -103,27 +106,18 @@ public class Subserver implements Runnable {
|
|||||||
serverName, address, "SteamWar.de - Subserver", false);
|
serverName, address, "SteamWar.de - Subserver", false);
|
||||||
this.writer = new PrintWriter(process.getOutputStream(), true);
|
this.writer = new PrintWriter(process.getOutputStream(), true);
|
||||||
|
|
||||||
this.thread = new Thread(this, "Subserver " + serverName);
|
this.thread = new Thread(this::run, "Subserver " + serverName);
|
||||||
this.thread.start();
|
this.thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerInfo getServer(){
|
@Deprecated
|
||||||
return server;
|
|
||||||
}
|
|
||||||
public Servertype getType(){
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
public Map<ProxiedPlayer, String> getTablistNames(){
|
|
||||||
return tablistNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasStarted(){
|
public boolean hasStarted(){
|
||||||
return started;
|
return started;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPlayer(ProxiedPlayer p) {
|
public void sendPlayer(ProxiedPlayer p) {
|
||||||
if(!started){
|
if(!started){
|
||||||
p.sendMessage(Persistent.getPrefix() + "§7Der Server wird gestartet, einen Moment bitte...");
|
p.sendMessage(ChatMessageType.ACTION_BAR, generateBar(0));
|
||||||
cachedPlayers.add(p);
|
cachedPlayers.add(p);
|
||||||
}else{
|
}else{
|
||||||
p.connect(server);
|
p.connect(server);
|
||||||
@ -135,10 +129,22 @@ public class Subserver implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
execute("stop");
|
|
||||||
try {
|
try {
|
||||||
if(!process.waitFor(1, TimeUnit.MINUTES))
|
long pid = process.pid();
|
||||||
|
if(checkpoint)
|
||||||
|
pid = process.children().findAny().map(ProcessHandle::pid).orElse(pid);
|
||||||
|
|
||||||
|
Runtime.getRuntime().exec(new String[]{"kill", "-SIGUSR1", Long.toString(pid)});
|
||||||
|
} catch (IOException e) {
|
||||||
|
ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Failed to send SIGUSR1 to subserver.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(!process.waitFor(1, TimeUnit.MINUTES)) {
|
||||||
|
logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!");
|
||||||
process.destroyForcibly();
|
process.destroyForcibly();
|
||||||
|
}
|
||||||
|
|
||||||
thread.join();
|
thread.join();
|
||||||
}catch(InterruptedException e){
|
}catch(InterruptedException e){
|
||||||
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
|
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
|
||||||
@ -146,74 +152,77 @@ public class Subserver implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void waitForTermination(){
|
|
||||||
try {
|
|
||||||
if(!process.waitFor(5, TimeUnit.MINUTES) && server.getPlayers().isEmpty()){
|
|
||||||
process.destroy();
|
|
||||||
}
|
|
||||||
thread.join();
|
|
||||||
}catch(InterruptedException e){
|
|
||||||
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void shutdown(){
|
|
||||||
while (!serverList.isEmpty()) {
|
|
||||||
Subserver server = serverList.get(0);
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onServer(ProxiedPlayer p){
|
private boolean onServer(ProxiedPlayer p){
|
||||||
return cachedPlayers.contains(p) || server.getPlayers().contains(p);
|
return cachedPlayers.contains(p) || server.getPlayers().contains(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fatalError(){
|
private void fatalError(Exception e) {
|
||||||
|
logger.log(Level.SEVERE, e, () -> serverName + " did not run correctly!");
|
||||||
|
|
||||||
for(ProxiedPlayer cached : cachedPlayers)
|
for(ProxiedPlayer cached : cachedPlayers)
|
||||||
cached.sendMessage(Persistent.getPrefix() + "§cUnerwarteter Fehler beim Serverstart.");
|
cached.sendMessage(TextComponent.fromLegacy(Persistent.PREFIX + "§cUnexpected error during server startup."));
|
||||||
for(ProxiedPlayer player : server.getPlayers())
|
for(ProxiedPlayer player : server.getPlayers())
|
||||||
player.sendMessage(Persistent.getPrefix() + "§cUnerwarteter Fehler im Server.");
|
player.sendMessage(TextComponent.fromLegacy(Persistent.PREFIX + "§cLost connection to server."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendProgress(int progress){
|
private void start(InputStream stream, Predicate<String> test) throws IOException {
|
||||||
StringBuilder sb = new StringBuilder("§e");
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
|
||||||
for(int i = 0; i < progress; i++)
|
|
||||||
sb.append('⬛');
|
|
||||||
sb.append("§8");
|
|
||||||
for(int i = progress; i < 10; i++)
|
|
||||||
sb.append('⬛');
|
|
||||||
|
|
||||||
BaseComponent[] tc = TextComponent.fromLegacyText(sb.toString());
|
|
||||||
for(ProxiedPlayer cached : cachedPlayers)
|
|
||||||
cached.sendMessage(ChatMessageType.ACTION_BAR, tc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(){
|
|
||||||
ProxyServer.getInstance().getServers().put(serverName, server);
|
|
||||||
synchronized (serverList) {
|
|
||||||
serverList.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
try(BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))){
|
|
||||||
String line = "";
|
String line = "";
|
||||||
while (!started && (line = reader.readLine()) != null) {
|
while (!started && (line = reader.readLine()) != null) {
|
||||||
started = line.contains("ViaVersion detected server version");
|
started = test.test(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line == null)
|
||||||
|
throw new IOException(serverName + " did not start correctly!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void register() {
|
||||||
|
if(ProxyServer.getInstance().getServers().containsKey(serverName)) {
|
||||||
|
SecurityException e = new SecurityException("Server already registered: " + serverName);
|
||||||
|
stop();
|
||||||
|
failureCallback.accept(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (serverList) {
|
||||||
|
ProxyServer.getInstance().getServers().put(serverName, server);
|
||||||
|
serverList.add(this);
|
||||||
|
infoToServer.put(server, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unregister() {
|
||||||
|
synchronized (serverList){
|
||||||
|
infoToServer.remove(server);
|
||||||
|
serverList.remove(this);
|
||||||
|
ProxyServer.getInstance().getServers().remove(serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
register();
|
||||||
|
|
||||||
|
Exception ex = null;
|
||||||
|
try {
|
||||||
|
if(checkpoint) {
|
||||||
|
start(process.getErrorStream(), line -> line.contains("Restore finished successfully."));
|
||||||
|
} else {
|
||||||
|
start(process.getInputStream(), line -> {
|
||||||
if(line.contains("Loading libraries, please wait"))
|
if(line.contains("Loading libraries, please wait"))
|
||||||
sendProgress(0);
|
sendProgress(2);
|
||||||
else if(line.contains("Starting Minecraft server on"))
|
else if(line.contains("Starting Minecraft server on"))
|
||||||
sendProgress(3);
|
sendProgress(4);
|
||||||
else if(line.contains("Preparing start region"))
|
else if(line.contains("Preparing start region"))
|
||||||
sendProgress(6);
|
sendProgress(6);
|
||||||
|
return line.contains("Finished mapping loading");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(line == null){
|
if(!started)
|
||||||
logger.log(Level.SEVERE, "Subserver {0} stopped to early!", serverName);
|
|
||||||
fatalError();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
sendProgress(9);
|
sendProgress(8);
|
||||||
|
|
||||||
Thread.sleep(300);
|
Thread.sleep(300);
|
||||||
|
|
||||||
@ -223,21 +232,27 @@ public class Subserver implements Runnable {
|
|||||||
}
|
}
|
||||||
cachedPlayers.clear();
|
cachedPlayers.clear();
|
||||||
|
|
||||||
reader.close(); // Prevent stdout clogging up
|
|
||||||
process.waitFor();
|
process.waitFor();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
logger.log(Level.SEVERE, "Server " + serverName + " was interrupted!", e);
|
ex = e;
|
||||||
fatalError();
|
|
||||||
} catch(InterruptedException e) {
|
} catch(InterruptedException e) {
|
||||||
logger.log(Level.SEVERE, "Server " + serverName + " was interrupted!", e);
|
ex = e;
|
||||||
fatalError();
|
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
} finally {
|
} finally {
|
||||||
synchronized (serverList){
|
unregister();
|
||||||
serverList.remove(this);
|
|
||||||
}
|
|
||||||
ProxyServer.getInstance().getServers().remove(serverName);
|
|
||||||
shutdownCallback.run();
|
shutdownCallback.run();
|
||||||
|
if(ex != null)
|
||||||
|
failureCallback.accept(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseComponent generateBar(int progress) {
|
||||||
|
return TextComponent.fromLegacy("§e" + "⬛".repeat(Math.max(0, progress)) + "§8" + "⬛".repeat(Math.max(0, 10 - progress)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendProgress(int progress) {
|
||||||
|
BaseComponent tc = generateBar(progress);
|
||||||
|
for(ProxiedPlayer cached : cachedPlayers)
|
||||||
|
cached.sendMessage(ChatMessageType.ACTION_BAR, tc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
This file is a part of the SteamWar software.
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
Copyright (C) 2020 SteamWar.de-Serverteam
|
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package net.md_5.bungee.api.bungeepluginmanager;
|
package de.steamwar.persistent;
|
||||||
|
|
||||||
import net.md_5.bungee.api.event.AsyncEvent;
|
import net.md_5.bungee.api.event.AsyncEvent;
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
@ -33,7 +33,7 @@ public final class ModifiedPluginEventBus extends EventBus {
|
|||||||
private static final Set<AsyncEvent<?>> UNCOMPLETED_EVENTS = Collections.newSetFromMap(new WeakHashMap<>());
|
private static final Set<AsyncEvent<?>> UNCOMPLETED_EVENTS = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private static final Object LOCK = new Object();
|
private static final Object LOCK = new Object();
|
||||||
|
|
||||||
static void completeIntents(Plugin plugin) {
|
public static void completeIntents(Plugin plugin) {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
UNCOMPLETED_EVENTS.forEach(event -> {
|
UNCOMPLETED_EVENTS.forEach(event -> {
|
||||||
try {
|
try {
|
183
src/de/steamwar/persistent/PluginUtils.java
Normale Datei
183
src/de/steamwar/persistent/PluginUtils.java
Normale Datei
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.persistent;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
|
import net.md_5.bungee.api.plugin.Command;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.api.plugin.PluginDescription;
|
||||||
|
import net.md_5.bungee.api.plugin.PluginManager;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public final class PluginUtils {
|
||||||
|
//Adapted from https://github.com/Shevchik/BungeePluginManager/blob/master/src/bungeepluginmanager/PluginUtils.java
|
||||||
|
|
||||||
|
private static final Class<?> LibraryLoaderClass = Reflection.getClass("net.md_5.bungee.api.plugin.LibraryLoader");
|
||||||
|
private static final Reflection.MethodInvoker createLoader = Reflection.getTypedMethod(LibraryLoaderClass, "createLoader", ClassLoader.class, PluginDescription.class);
|
||||||
|
|
||||||
|
public static final Reflection.FieldAccessor<Map> CommandMap = Reflection.getField(PluginManager.class, "commandMap", Map.class);
|
||||||
|
public static final Reflection.FieldAccessor<Map> Plugins = Reflection.getField(PluginManager.class, "plugins", Map.class);
|
||||||
|
public static final Reflection.FieldAccessor<Yaml> Yaml = Reflection.getField(PluginManager.class, "yaml", Yaml.class);
|
||||||
|
public static final Reflection.FieldAccessor<?> LibraryLoader = Reflection.getField(PluginManager.class, "libraryLoader", LibraryLoaderClass);
|
||||||
|
public static final Reflection.FieldAccessor<ExecutorService> Service = Reflection.getField(Plugin.class, "service", ExecutorService.class);
|
||||||
|
|
||||||
|
private static final Class<?> PluginClassLoaderClass = Reflection.getClass("net.md_5.bungee.api.plugin.PluginClassloader");
|
||||||
|
private static final Reflection.ConstructorInvoker NewPluginClassLoader = Reflection.getConstructor(PluginClassLoaderClass, ProxyServer.class, PluginDescription.class, File.class, ClassLoader.class);
|
||||||
|
|
||||||
|
private static final Reflection.MethodInvoker init = Reflection.getMethod(Plugin.class, "init", ProxyServer.class, PluginDescription.class);
|
||||||
|
|
||||||
|
private static final Set<?> allLoaders = Reflection.getField(PluginClassLoaderClass, "allLoaders", Set.class).get(null);
|
||||||
|
|
||||||
|
private PluginUtils() {}
|
||||||
|
|
||||||
|
public static void unloadPlugin(ProxyServer proxy, Map<String, Command> commandMap, Map<String, Plugin> plugins, Plugin plugin) {
|
||||||
|
IllegalStateException error = new IllegalStateException();
|
||||||
|
|
||||||
|
PluginManager pluginManager = proxy.getPluginManager();
|
||||||
|
ClassLoader pluginClassLoader = plugin.getClass().getClassLoader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
plugin.onDisable();
|
||||||
|
} catch (Exception e) {
|
||||||
|
proxy.getLogger().log(Level.SEVERE, "Exception on disable", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//unregister event handlers
|
||||||
|
pluginManager.unregisterListeners(plugin);
|
||||||
|
|
||||||
|
//unregister commands
|
||||||
|
pluginManager.unregisterCommands(plugin);
|
||||||
|
//remove incorrectly registered plugins
|
||||||
|
commandMap.entrySet().removeIf(entry -> entry.getValue().getClass().getClassLoader() == pluginClassLoader);
|
||||||
|
|
||||||
|
//cancel scheduled tasks
|
||||||
|
proxy.getScheduler().cancel(plugin);
|
||||||
|
|
||||||
|
//finish uncompleted intents
|
||||||
|
ModifiedPluginEventBus.completeIntents(plugin);
|
||||||
|
|
||||||
|
//shutdown internal executor
|
||||||
|
ExecutorService service = Service.get(plugin);
|
||||||
|
if(service != null)
|
||||||
|
service.shutdownNow();
|
||||||
|
|
||||||
|
//stop all still active threads that belong to a plugin
|
||||||
|
for(Thread thread : Thread.getAllStackTraces().keySet()) {
|
||||||
|
if(thread.getClass().getClassLoader() != pluginClassLoader)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
thread.interrupt();
|
||||||
|
try {
|
||||||
|
thread.join(100);
|
||||||
|
} catch (InterruptedException t) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread.isAlive())
|
||||||
|
proxy.getLogger().log(Level.WARNING, () -> "Could not stop thread " + thread.getName() + " of plugin " + plugin.getDescription().getName() + ". Still running");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clear resource bundle cache
|
||||||
|
ResourceBundle.clearCache(pluginClassLoader);
|
||||||
|
|
||||||
|
//close all log handlers
|
||||||
|
for (Handler handler : plugin.getLogger().getHandlers()) {
|
||||||
|
handler.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//cleanup internal listener and command maps from plugin refs
|
||||||
|
plugins.values().remove(plugin);
|
||||||
|
|
||||||
|
// Remove classloader
|
||||||
|
allLoaders.remove(pluginClassLoader);
|
||||||
|
|
||||||
|
//close classloader
|
||||||
|
if (pluginClassLoader instanceof URLClassLoader) {
|
||||||
|
try {
|
||||||
|
((URLClassLoader) pluginClassLoader).close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
proxy.getLogger().log(Level.SEVERE, "Exception while closing the classloader", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error.getSuppressed().length > 0) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginDescription loadDescription(PluginManager pluginManager, File pluginFile) {
|
||||||
|
PluginDescription desc;
|
||||||
|
try (JarFile jar = new JarFile(pluginFile)) {
|
||||||
|
JarEntry pdf = jar.getJarEntry("bungee.yml");
|
||||||
|
|
||||||
|
if (pdf == null) {
|
||||||
|
pdf = jar.getJarEntry("plugin.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = jar.getInputStream(pdf)) {
|
||||||
|
//load description
|
||||||
|
desc = PluginUtils.Yaml.get(pluginManager).loadAs(in, PluginDescription.class);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
desc.setFile(pluginFile);
|
||||||
|
|
||||||
|
//check depends
|
||||||
|
Map<String, Plugin> pluginsMap = PluginUtils.Plugins.get(pluginManager);
|
||||||
|
for (String dependency : desc.getDepends()) {
|
||||||
|
if (!pluginsMap.containsKey(dependency)) {
|
||||||
|
throw new IllegalStateException(dependency +" (required by " + desc.getName() + ") is unavailable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadPlugin(Map<String, Plugin> plugins, Object libraryLoader, PluginDescription desc) {
|
||||||
|
URLClassLoader loader = (URLClassLoader)NewPluginClassLoader.invoke(ProxyServer.getInstance(), desc, desc.getFile(), libraryLoader != null ? createLoader.invoke(libraryLoader, desc) : null);
|
||||||
|
|
||||||
|
Plugin plugin;
|
||||||
|
try {
|
||||||
|
Class<?> mainclazz = loader.loadClass(desc.getMain());
|
||||||
|
plugin = (Plugin) Reflection.newInstance(mainclazz);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
init.invoke(plugin, ProxyServer.getInstance(), desc);
|
||||||
|
|
||||||
|
plugins.put(desc.getName(), plugin);
|
||||||
|
plugin.onLoad();
|
||||||
|
plugin.onEnable();
|
||||||
|
}
|
||||||
|
}
|
342
src/de/steamwar/persistent/Reflection.java
Normale Datei
342
src/de/steamwar/persistent/Reflection.java
Normale Datei
@ -0,0 +1,342 @@
|
|||||||
|
package de.steamwar.persistent;
|
||||||
|
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility class that simplifies reflection in Bukkit plugins.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public final class Reflection {
|
||||||
|
/**
|
||||||
|
* An interface for invoking a specific constructor.
|
||||||
|
*/
|
||||||
|
public interface ConstructorInvoker {
|
||||||
|
/**
|
||||||
|
* Invoke a constructor for a specific class.
|
||||||
|
*
|
||||||
|
* @param arguments - the arguments to pass to the constructor.
|
||||||
|
* @return The constructed object.
|
||||||
|
*/
|
||||||
|
Object invoke(Object... arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for invoking a specific method.
|
||||||
|
*/
|
||||||
|
public interface MethodInvoker {
|
||||||
|
/**
|
||||||
|
* Invoke a method on a specific target object.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static method.
|
||||||
|
* @param arguments - the arguments to pass to the method.
|
||||||
|
* @return The return value, or NULL if is void.
|
||||||
|
*/
|
||||||
|
Object invoke(Object target, Object... arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for retrieving the field content.
|
||||||
|
*
|
||||||
|
* @param <T> - field type.
|
||||||
|
*/
|
||||||
|
public interface FieldAccessor<T> {
|
||||||
|
/**
|
||||||
|
* Retrieve the content of a field.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static field.
|
||||||
|
* @return The value of the field.
|
||||||
|
*/
|
||||||
|
T get(Object target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content of a field.
|
||||||
|
*
|
||||||
|
* @param target - the target object, or NULL for a static field.
|
||||||
|
* @param value - the new value of the field.
|
||||||
|
*/
|
||||||
|
void set(Object target, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given object has this field.
|
||||||
|
*
|
||||||
|
* @param target - the object to test.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
boolean hasField(Object target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Reflection() {
|
||||||
|
// Seal class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param target - the target type.
|
||||||
|
* @param name - the name of the field, or NULL to ignore.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
|
||||||
|
return getField(target, name, fieldType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param name - the name of the field, or NULL to ignore.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
|
||||||
|
return getField(getClass(className), name, fieldType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param target - the target type.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @param index - the number of compatible fields to skip.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
|
||||||
|
return getField(target, null, fieldType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a field accessor for a specific field type and name.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param fieldType - a compatible field type.
|
||||||
|
* @param index - the number of compatible fields to skip.
|
||||||
|
* @return The field accessor.
|
||||||
|
*/
|
||||||
|
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
|
||||||
|
return getField(getClass(className), fieldType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index, Class<?>... parameters) {
|
||||||
|
return getField(target, null, fieldType, index, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index, Class<?>... parameters) {
|
||||||
|
for (final Field field : target.getDeclaredFields()) {
|
||||||
|
if(matching(field, name, fieldType, parameters) && index-- <= 0) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
return new FieldAccessor<T>() {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T get(Object target) {
|
||||||
|
try {
|
||||||
|
return (T) field.get(target);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot access reflection.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(Object target, Object value) {
|
||||||
|
try {
|
||||||
|
field.set(target, value);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot access reflection.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasField(Object target) {
|
||||||
|
// target instanceof DeclaringClass
|
||||||
|
return field.getDeclaringClass().isAssignableFrom(target.getClass());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in parent classes
|
||||||
|
if (target.getSuperclass() != null)
|
||||||
|
return getField(target.getSuperclass(), name, fieldType, index);
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> boolean matching(Field field, String name, Class<T> fieldType, Class<?>... parameters) {
|
||||||
|
if(name != null && !field.getName().equals(name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!fieldType.isAssignableFrom(field.getType()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(parameters.length > 0) {
|
||||||
|
Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments();
|
||||||
|
|
||||||
|
for(int i = 0; i < parameters.length; i++) {
|
||||||
|
if(arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
|
||||||
|
return getTypedMethod(getClass(className), methodName, null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
|
||||||
|
return getTypedMethod(clazz, methodName, null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param methodName - the method name, or NULL to skip.
|
||||||
|
* @param returnType - the expected return type, or NULL to ignore.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this specific method.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
|
||||||
|
for (final Method method : clazz.getDeclaredMethods()) {
|
||||||
|
if ((methodName == null || method.getName().equals(methodName))
|
||||||
|
&& (returnType == null || method.getReturnType().equals(returnType))
|
||||||
|
&& Arrays.equals(method.getParameterTypes(), params)) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
return (target, arguments) -> {
|
||||||
|
try {
|
||||||
|
return method.invoke(target, arguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("Cannot invoke method " + method, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in every superclass
|
||||||
|
if (clazz.getSuperclass() != null)
|
||||||
|
return getTypedMethod(clazz.getSuperclass(), methodName, returnType, params);
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this constructor.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
|
||||||
|
return getConstructor(getClass(className), params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||||
|
*
|
||||||
|
* @param clazz - a class to start with.
|
||||||
|
* @param params - the expected parameters.
|
||||||
|
* @return An object that invokes this constructor.
|
||||||
|
* @throws IllegalStateException If we cannot find this method.
|
||||||
|
*/
|
||||||
|
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
|
||||||
|
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||||
|
if (Arrays.equals(constructor.getParameterTypes(), params)) {
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
|
||||||
|
return arguments -> {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance(arguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class from its full name, without knowing its type on compile time.
|
||||||
|
* <p>
|
||||||
|
* This is useful when looking up fields by a NMS or OBC type.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param lookupName - the class name with variables.
|
||||||
|
* @return The class.
|
||||||
|
*/
|
||||||
|
public static Class<Object> getUntypedClass(String lookupName) {
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
Class<Object> clazz = (Class) getClass(lookupName);
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a class from its full name.
|
||||||
|
* <p>
|
||||||
|
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
|
||||||
|
* <p>
|
||||||
|
* <table border="1">
|
||||||
|
* <tr>
|
||||||
|
* <th>Variable</th>
|
||||||
|
* <th>Content</th>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{nms}</td>
|
||||||
|
* <td>Actual package name of net.minecraft.server.VERSION</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{obc}</td>
|
||||||
|
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
|
* <td>{version}</td>
|
||||||
|
* <td>The current Minecraft package VERSION, if any.</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @param lookupName - the class name with variables.
|
||||||
|
* @return The looked up class.
|
||||||
|
* @throws IllegalArgumentException If a variable or class could not be found.
|
||||||
|
*/
|
||||||
|
public static Class<?> getClass(String lookupName) {
|
||||||
|
try {
|
||||||
|
return Class.forName(lookupName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot find " + lookupName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static Object newInstance(Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
return clazz.newInstance();
|
||||||
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
|
throw new SecurityException("Could not create object", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is a part of the SteamWar software.
|
|
||||||
|
|
||||||
Copyright (C) 2020 SteamWar.de-Serverteam
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.md_5.bungee.api.bungeepluginmanager;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
|
||||||
import net.md_5.bungee.api.plugin.*;
|
|
||||||
import org.yaml.snakeyaml.Yaml;
|
|
||||||
import org.yaml.snakeyaml.constructor.Constructor;
|
|
||||||
import org.yaml.snakeyaml.representer.Representer;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
import java.util.logging.Handler;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
public final class PluginUtils {
|
|
||||||
private PluginUtils() {
|
|
||||||
throw new IllegalStateException("Utility class");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static void unloadPlugin(Plugin plugin) {
|
|
||||||
|
|
||||||
PluginManager pluginManager = ProxyServer.getInstance().getPluginManager();
|
|
||||||
ClassLoader pluginClassLoader = plugin.getClass().getClassLoader();
|
|
||||||
|
|
||||||
try {
|
|
||||||
//call onDisable
|
|
||||||
plugin.onDisable();
|
|
||||||
//close all log handlers
|
|
||||||
for (Handler handler : plugin.getLogger().getHandlers()) {
|
|
||||||
handler.close();
|
|
||||||
}
|
|
||||||
} catch (Exception t) {
|
|
||||||
severe("Exception disabling plugin", t, plugin.getDescription().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
//unregister event handlers
|
|
||||||
pluginManager.unregisterListeners(plugin);
|
|
||||||
//unregister commands
|
|
||||||
pluginManager.unregisterCommands(plugin);
|
|
||||||
//cancel tasks
|
|
||||||
ProxyServer.getInstance().getScheduler().cancel(plugin);
|
|
||||||
//shutdown internal executor
|
|
||||||
plugin.getExecutorService().shutdownNow();
|
|
||||||
//stop all still active threads that belong to a plugin
|
|
||||||
Thread.getAllStackTraces().keySet().stream()
|
|
||||||
.filter(thread -> (thread.getClass().getClassLoader() == pluginClassLoader))
|
|
||||||
.forEach(thread -> {
|
|
||||||
try {
|
|
||||||
thread.interrupt();
|
|
||||||
thread.join(100);
|
|
||||||
if (thread.isAlive()) {
|
|
||||||
ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Could not stop thread " + thread.getName() + " of plugin " + plugin.getDescription().getName() + ". Still running");
|
|
||||||
}
|
|
||||||
} catch (InterruptedException t) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//finish uncompleted intents
|
|
||||||
ModifiedPluginEventBus.completeIntents(plugin);
|
|
||||||
//remove commands that were registered by plugin not through normal means
|
|
||||||
try {
|
|
||||||
Map<String, Command> commandMap = ReflectionUtils.getFieldValue(pluginManager, "commandMap");
|
|
||||||
if (checkReflectionNotNull(commandMap, "commandMap")) {
|
|
||||||
commandMap.entrySet().removeIf(entry -> entry.getValue().getClass().getClassLoader() == pluginClassLoader);
|
|
||||||
}
|
|
||||||
} catch (Exception t) {
|
|
||||||
severe("Failed to cleanup commandMap", t, plugin.getDescription().getName());
|
|
||||||
}
|
|
||||||
//cleanup internal listener and command maps from plugin refs
|
|
||||||
try {
|
|
||||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(pluginManager, "plugins");
|
|
||||||
Multimap<Plugin, Command> commands = ReflectionUtils.getFieldValue(pluginManager, "commandsByPlugin");
|
|
||||||
Multimap<Plugin, Listener> listeners = ReflectionUtils.getFieldValue(pluginManager, "listenersByPlugin");
|
|
||||||
|
|
||||||
if (checkReflectionNotNull(pluginsMap, "plugin")
|
|
||||||
&& checkReflectionNotNull(commands, "commandByPlugin")
|
|
||||||
&& checkReflectionNotNull(listeners, "listenersByPlugin")) {
|
|
||||||
pluginsMap.values().remove(plugin);
|
|
||||||
commands.removeAll(plugin);
|
|
||||||
listeners.removeAll(plugin);
|
|
||||||
}
|
|
||||||
} catch (Exception t) {
|
|
||||||
severe("Failed to cleanup bungee internal maps from plugin refs", t, plugin.getDescription().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceBundle.clearCache(pluginClassLoader);
|
|
||||||
|
|
||||||
//close classloader
|
|
||||||
if (pluginClassLoader instanceof URLClassLoader) {
|
|
||||||
try {
|
|
||||||
((URLClassLoader) pluginClassLoader).close();
|
|
||||||
} catch (Exception t) {
|
|
||||||
severe("Failed to close the classloader for plugin", t, plugin.getDescription().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove classloader
|
|
||||||
try {
|
|
||||||
Class<?> PluginClassLoader = Class.forName("net.md_5.bungee.api.plugin.PluginClassloader");
|
|
||||||
Set<?> allLoaders = ReflectionUtils.getStaticFieldValue(PluginClassLoader, "allLoaders");
|
|
||||||
Preconditions.checkNotNull(allLoaders);
|
|
||||||
allLoaders.remove(pluginClassLoader);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean checkReflectionNotNull(Object obj, String field) {
|
|
||||||
if (obj == null) {
|
|
||||||
ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Could not get field {} by reflections: are you using the latest version of BungeePluginManager?", field);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static boolean loadPlugin(File pluginFile) {
|
|
||||||
|
|
||||||
try (JarFile jar = new JarFile(pluginFile)) {
|
|
||||||
|
|
||||||
JarEntry pdf = jar.getJarEntry("bungee.yml");
|
|
||||||
|
|
||||||
if (pdf == null) {
|
|
||||||
pdf = jar.getJarEntry("plugin.yml");
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream in = jar.getInputStream(pdf)) {
|
|
||||||
//load description
|
|
||||||
|
|
||||||
Representer representer = new Representer();
|
|
||||||
representer.getPropertyUtils().setSkipMissingProperties(true);
|
|
||||||
PluginDescription desc = new Yaml(new Constructor(PluginDescription.class), representer).loadAs(in, PluginDescription.class);
|
|
||||||
desc.setFile(pluginFile);
|
|
||||||
|
|
||||||
|
|
||||||
//check depends
|
|
||||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(ProxyServer.getInstance().getPluginManager(), "plugins");
|
|
||||||
Preconditions.checkNotNull(pluginsMap);
|
|
||||||
|
|
||||||
for (String dependency : desc.getDepends()) {
|
|
||||||
if (!pluginsMap.containsKey(dependency)) {
|
|
||||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]{dependency, desc.getName()});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object libraryLoader = ReflectionUtils.getFieldValue(ProxyServer.getInstance().getPluginManager(), "libraryLoader");
|
|
||||||
|
|
||||||
//load plugin
|
|
||||||
Class<?> pluginClassLoader = Class.forName("net.md_5.bungee.api.plugin.PluginClassloader");
|
|
||||||
java.lang.reflect.Constructor<?> constructor = pluginClassLoader.getConstructor(ProxyServer.class, PluginDescription.class, File.class, ClassLoader.class);
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
URLClassLoader loader = (URLClassLoader) constructor.newInstance(ProxyServer.getInstance(), desc, pluginFile, libraryLoader != null ? ReflectionUtils.invokeMethod(libraryLoader, "createLoader", desc) : null);
|
|
||||||
Class<?> mainclazz = loader.loadClass(desc.getMain());
|
|
||||||
Plugin plugin = (Plugin) mainclazz.getDeclaredConstructor().newInstance();
|
|
||||||
ReflectionUtils.invokeMethod(plugin, "init", ProxyServer.getInstance(), desc);
|
|
||||||
|
|
||||||
pluginsMap.put(desc.getName(), plugin);
|
|
||||||
plugin.onLoad();
|
|
||||||
plugin.onEnable();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception t) {
|
|
||||||
severe("Failed to load plugin", t, pluginFile.getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void severe(String message, Exception t, String pluginName) {
|
|
||||||
ProxyServer.getInstance().getLogger().log(Level.SEVERE, message + " " + pluginName, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is a part of the SteamWar software.
|
|
||||||
|
|
||||||
Copyright (C) 2020 SteamWar.de-Serverteam
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.md_5.bungee.api.bungeepluginmanager;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public final class ReflectionUtils {
|
|
||||||
private ReflectionUtils() {
|
|
||||||
throw new IllegalStateException("Utility class");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static <T> T getFieldValue(Object obj, String fieldName) {
|
|
||||||
Class<?> clazz = obj.getClass();
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
Field field = clazz.getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return (T) field.get(obj);
|
|
||||||
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
} while ((clazz = clazz.getSuperclass()) != null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFieldValue(Object obj, String fieldName, Object value) {
|
|
||||||
Class<?> clazz = obj.getClass();
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
Field field = clazz.getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
field.set(obj, value);
|
|
||||||
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
} while ((clazz = clazz.getSuperclass()) != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static <T> T getStaticFieldValue(Class<?> clazz, String fieldName) {
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
Field field = clazz.getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return (T) field.get(null);
|
|
||||||
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
} while ((clazz = clazz.getSuperclass()) != null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Object invokeMethod(Object obj, String methodName, Object... args) {
|
|
||||||
Class<?> clazz = obj.getClass();
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
|
||||||
if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {
|
|
||||||
method.setAccessible(true);
|
|
||||||
return method.invoke(obj, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException error) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
} while ((clazz = clazz.getSuperclass()) != null);
|
|
||||||
throw new SecurityException("Method could not be found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,4 +6,5 @@ artifacts:
|
|||||||
"/binarys/persistentbungeecore.jar": "build/libs/persistentbungeecore.jar"
|
"/binarys/persistentbungeecore.jar": "build/libs/persistentbungeecore.jar"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
- "mvn deploy:deploy-file -DgroupId=de.steamwar -DartifactId=persistentbungeecore -Dversion=RELEASE -Dpackaging=jar -Dfile=build/libs/persistentbungeecore.jar -Durl=file:///var/www/html/maven/"
|
- "./gradlew release"
|
||||||
|
- "./gradlew --stop"
|
In neuem Issue referenzieren
Einen Benutzer sperren