use core as a service, keep active as long as other registered plugins are active

This commit is contained in:
jascotty2 2019-08-21 22:47:15 -05:00
parent 6a60930a01
commit 782233cbde
11 changed files with 205 additions and 350 deletions

15
pom.xml
View File

@ -23,6 +23,15 @@
<id>private</id>
<url>http://repo.songoda.com/artifactory/private/</url>
</repository>
<!-- sk89q-repo and spigot-repo are required for worldguard -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sk89q-repo</id>
<url>https://maven.sk89q.com/repo/</url>
</repository>
</repositories>
<dependencies>
<dependency>
@ -71,5 +80,11 @@
<artifactId>sqlite-jdbc</artifactId>
<version>3.23.1</version>
</dependency>
<dependency>
<groupId>com.sk89q.worldguard</groupId>
<artifactId>worldguard-bukkit</artifactId>
<version>7.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -18,7 +18,7 @@ public class PluginInfo {
private String marketplaceLink;
private JSONObject json;
public PluginInfo(JavaPlugin javaPlugin, int songodaId) {
protected PluginInfo(JavaPlugin javaPlugin, int songodaId) {
this.javaPlugin = javaPlugin;
this.songodaId = songodaId;
}

View File

@ -1,15 +1,7 @@
package com.songoda.core;
import com.songoda.core.command.CommandManager;
import com.songoda.core.listeners.LoginListener;
import com.songoda.core.library.commands.CommandManager;
import com.songoda.core.modules.Module;
import org.bukkit.Bukkit;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -17,32 +9,114 @@ import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class SongodaCore {
private static String prefix = "[SongodaCore]";
private final static String prefix = "[SongodaCore]";
private static int updaterVersion = 1;
private final static int updaterVersion = 1;
private static Set<PluginInfo> registeredPlugins = new HashSet<>();
private final static Set<PluginInfo> registeredPlugins = new HashSet<>();
private static SongodaCore INSTANCE;
private static JavaPlugin hijackedPlugin;
private static SongodaCore INSTANCE = null;
private JavaPlugin piggybackedPlugin;
private final CommandManager commandManager;
private final EventListener loginListener = new EventListener();
private final HashMap<UUID, Long> lastCheck = new HashMap();
public static void registerPlugin(JavaPlugin plugin, int pluginID) {
}
public SongodaCore(JavaPlugin javaPlugin) {
hijackedPlugin = javaPlugin;
Bukkit.getPluginManager().registerEvents(new LoginListener(this), hijackedPlugin);
if(INSTANCE == null) {
// First: are there any other instances of SongodaCore active?
for (Class<?> clazz : Bukkit.getServicesManager().getKnownServices()) {
if(clazz.getSimpleName().equals("SongodaCore")) {
try {
// use the active service
clazz.getMethod("registerPlugin", JavaPlugin.class, int.class).invoke(null, plugin, pluginID);
return;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {
new CommandManager(this);
}
}
}
// register ourselves as the SongodaCore service!
INSTANCE = new SongodaCore(plugin);
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, plugin, ServicePriority.Normal);
}
INSTANCE.hook(new PluginInfo(plugin, pluginID));
}
public SongodaCore(JavaPlugin javaPlugin) {
piggybackedPlugin = javaPlugin;
commandManager = new CommandManager(javaPlugin);
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);
commandManager.addCommand(new SongodaCoreCommand(this))
.addSubCommand(new SongodaCoreDiagCommand(this));
Bukkit.getPluginManager().registerEvents(loginListener, javaPlugin);
}
private class EventListener implements Listener {
@EventHandler
void onLogin(PlayerLoginEvent event) {
// don't spam players with update checks
final Player player = event.getPlayer();
long now = System.currentTimeMillis();
Long last = lastCheck.get(player.getUniqueId());
if(last != null && now - 10000 < last) return;
lastCheck.put(player.getUniqueId(), now);
// is this player good to revieve update notices?
if (!event.getPlayer().isOp() && !player.hasPermission("songoda.updatecheck")) return;
// check for updates! ;)
for (PluginInfo plugin : getPlugins()) {
if (plugin.getNotification() != null && plugin.getJavaPlugin().isEnabled())
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin.getJavaPlugin(), () ->
player.sendMessage("[" + plugin.getJavaPlugin().getName() + "] " + plugin.getNotification()), 10L);
}
}
@EventHandler
void onDisable(PluginDisableEvent event) {
// don't track disabled plugins
PluginInfo pi = registeredPlugins.stream().filter(p -> event.getPlugin() == p.getJavaPlugin()).findFirst().orElse(null);
if(pi != null) {
registeredPlugins.remove(pi);
}
if(event.getPlugin() == piggybackedPlugin) {
System.out.println(prefix + " Is being disabled!!!");
// uh-oh! Abandon ship!!
Bukkit.getServicesManager().unregisterAll(piggybackedPlugin);
// can we move somewhere else?
if((pi = registeredPlugins.stream().findFirst().orElse(null)) != null) {
System.out.println(prefix + " Moving to " + pi.getJavaPlugin().getName());
// move ourselves to this plugin
piggybackedPlugin = pi.getJavaPlugin();
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, piggybackedPlugin, ServicePriority.Normal);
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);
}
}
}
}
private void hook(PluginInfo plugin) {
System.out.println(getPrefix() + "Hooked " + plugin.getJavaPlugin().getName() + ".");
registeredPlugins.add(plugin);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin.getJavaPlugin(), () -> update(plugin), 20L);
}
private void update(PluginInfo plugin) {
@ -81,30 +155,6 @@ public class SongodaCore {
}
}
public static PluginInfo load(PluginInfo plugin) {
boolean found = false;
for (Class<?> clazz : Bukkit.getServicesManager().getKnownServices()) {
try {
clazz.getMethod("hook", PluginInfo.class).invoke(null, plugin);
found = true;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {
}
}
if (!found) {
if (INSTANCE == null) INSTANCE = new SongodaCore(plugin.getJavaPlugin());
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, hijackedPlugin, ServicePriority.Normal);
hook(plugin);
}
return plugin;
}
public static void hook(PluginInfo plugin) {
System.out.println(getPrefix() + "Hooked " + plugin.getJavaPlugin().getName() + ".");
getInstance().update(plugin);
registeredPlugins.add(plugin);
}
public List<PluginInfo> getPlugins() {
return new ArrayList<>(registeredPlugins);
}
@ -118,7 +168,7 @@ public class SongodaCore {
}
public static JavaPlugin getHijackedPlugin() {
return hijackedPlugin;
return INSTANCE == null ? null : INSTANCE.piggybackedPlugin;
}
public static SongodaCore getInstance() {

View File

@ -0,0 +1,46 @@
package com.songoda.core;
import com.songoda.core.library.commands.AbstractCommand;
import java.util.List;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
class SongodaCoreCommand extends AbstractCommand {
final SongodaCore instance;
protected SongodaCoreCommand(SongodaCore instance) {
super(false, "songoda");
this.instance = instance;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if(sender instanceof Player) {
new SongodaCoreOverviewGUI(instance, (Player) sender);
} else {
sender.sendMessage("/songoda diag");
}
return ReturnType.SUCCESS;
}
@Override
public String getPermissionNode() {
return "songoda.admin";
}
@Override
public String getSyntax() {
return "/songoda";
}
@Override
public String getDescription() {
return "Displays this interface.";
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
}

View File

@ -1,8 +1,6 @@
package com.songoda.core.command.commands;
package com.songoda.core;
import com.songoda.core.PluginInfo;
import com.songoda.core.SongodaCore;
import com.songoda.core.command.AbstractCommand;
import com.songoda.core.library.commands.AbstractCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -11,8 +9,9 @@ import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.List;
public class CommandDiag extends AbstractCommand {
public class SongodaCoreDiagCommand extends AbstractCommand {
final SongodaCore instance;
private final String name = Bukkit.getServer().getClass().getPackage().getName();
private final String version = name.substring(name.lastIndexOf('.') + 1);
@ -22,8 +21,9 @@ public class CommandDiag extends AbstractCommand {
private Field tpsField;
public CommandDiag(AbstractCommand parent) {
super(parent, false, "diag");
protected SongodaCoreDiagCommand(SongodaCore instance) {
super(false, "diag");
this.instance = instance;
try {
serverInstance = getNMSClass("MinecraftServer").getMethod("getServer").invoke(null);
@ -36,7 +36,7 @@ public class CommandDiag extends AbstractCommand {
}
@Override
protected ReturnType runCommand(SongodaCore instance, CommandSender sender, String... args) {
protected ReturnType runCommand(CommandSender sender, String... args) {
sender.sendMessage("");
sender.sendMessage("Songoda Diagnostics Information");
@ -64,7 +64,7 @@ public class CommandDiag extends AbstractCommand {
}
@Override
protected List<String> onTab(SongodaCore instance, CommandSender sender, String... args) {
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}

View File

@ -1,18 +1,16 @@
package com.songoda.core.gui;
package com.songoda.core;
import com.songoda.core.PluginInfo;
import com.songoda.core.SongodaCore;
import com.songoda.core.utils.gui.AbstractGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.List;
public class GUIOverview extends AbstractGUI {
class SongodaCoreOverviewGUI extends AbstractGUI {
private final SongodaCore update;
public GUIOverview(SongodaCore update, Player player) {
protected SongodaCoreOverviewGUI(SongodaCore update, Player player) {
super(player);
this.update = update;

View File

@ -1,71 +0,0 @@
package com.songoda.core.command;
import com.songoda.core.SongodaCore;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class AbstractCommand {
private final boolean noConsole;
private AbstractCommand parent = null;
private boolean hasArgs = false;
private String command;
private List<String> subCommand = new ArrayList<>();
protected AbstractCommand(AbstractCommand parent, boolean noConsole, String... command) {
if (parent != null) {
this.subCommand = Arrays.asList(command);
} else {
this.command = Arrays.asList(command).get(0);
}
this.parent = parent;
this.noConsole = noConsole;
}
protected AbstractCommand(boolean noConsole, boolean hasArgs, String... command) {
this.command = Arrays.asList(command).get(0);
this.hasArgs = hasArgs;
this.noConsole = noConsole;
}
public AbstractCommand getParent() {
return parent;
}
public String getCommand() {
return command;
}
public List<String> getSubCommand() {
return subCommand;
}
public void addSubCommand(String command) {
subCommand.add(command);
}
protected abstract ReturnType runCommand(SongodaCore instance, CommandSender sender, String... args);
protected abstract List<String> onTab(SongodaCore instance, CommandSender sender, String... args);
public abstract String getPermissionNode();
public abstract String getSyntax();
public abstract String getDescription();
public boolean hasArgs() {
return hasArgs;
}
public boolean isNoConsole() {
return noConsole;
}
public enum ReturnType {SUCCESS, FAILURE, SYNTAX_ERROR}
}

View File

@ -1,111 +0,0 @@
package com.songoda.core.command;
import com.songoda.core.SongodaCore;
import com.songoda.core.command.commands.CommandDiag;
import com.songoda.core.command.commands.CommandSongoda;
import com.songoda.core.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class CommandManager implements CommandExecutor {
private SongodaCore instance;
private TabManager tabManager;
private List<AbstractCommand> commands = new ArrayList<>();
public CommandManager(SongodaCore instance) {
this.instance = instance;
this.tabManager = new TabManager(this);
registerCommandDynamically("songoda", this, tabManager);
AbstractCommand commandSongoda = addCommand(new CommandSongoda());
addCommand(new CommandDiag(commandSongoda));
}
private AbstractCommand addCommand(AbstractCommand abstractCommand) {
commands.add(abstractCommand);
return abstractCommand;
}
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
for (AbstractCommand abstractCommand : commands) {
if (abstractCommand.getCommand() != null && abstractCommand.getCommand().equalsIgnoreCase(command.getName().toLowerCase())) {
if (strings.length == 0 || abstractCommand.hasArgs()) {
processRequirements(abstractCommand, commandSender, strings);
return true;
}
} else if (strings.length != 0 && abstractCommand.getParent() != null && abstractCommand.getParent().getCommand().equalsIgnoreCase(command.getName())) {
String cmd = strings[0];
String cmd2 = strings.length >= 2 ? String.join(" ", strings[0], strings[1]) : null;
for (String cmds : abstractCommand.getSubCommand()) {
if (cmd.equalsIgnoreCase(cmds) || (cmd2 != null && cmd2.equalsIgnoreCase(cmds))) {
processRequirements(abstractCommand, commandSender, strings);
return true;
}
}
}
}
commandSender.sendMessage(instance.getPrefix() + Methods.formatText("&7The command you entered does not exist or is spelt incorrectly."));
return true;
}
private void processRequirements(AbstractCommand command, CommandSender sender, String[] strings) {
if (!(sender instanceof Player) && command.isNoConsole()) {
sender.sendMessage("You must be a player to use this command.");
return;
}
if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) {
AbstractCommand.ReturnType returnType = command.runCommand(instance, sender, strings);
if (returnType == AbstractCommand.ReturnType.SYNTAX_ERROR) {
sender.sendMessage(instance.getPrefix() + Methods.formatText("&cInvalid Syntax!"));
sender.sendMessage(instance.getPrefix() + Methods.formatText("&7The valid syntax is: &6" + command.getSyntax() + "&7."));
}
return;
}
sender.sendMessage(instance.getPrefix() + "You do not have permission to run this command.");
}
public List<AbstractCommand> getCommands() {
return Collections.unmodifiableList(commands);
}
public static void registerCommandDynamically(String command, CommandExecutor executor, TabManager tabManager) {
try {
// Retrieve the SimpleCommandMap from the server
Class<?> classCraftServer = Bukkit.getServer().getClass();
Field fieldCommandMap = classCraftServer.getDeclaredField("commandMap");
fieldCommandMap.setAccessible(true);
SimpleCommandMap commandMap = (SimpleCommandMap) fieldCommandMap.get(Bukkit.getServer());
// Construct a new Command object
Constructor<PluginCommand> constructorPluginCommand = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructorPluginCommand.setAccessible(true);
PluginCommand commandObject = constructorPluginCommand.newInstance(command, SongodaCore.getHijackedPlugin());
commandObject.setExecutor(executor);
// Set tab complete
commandObject.setTabCompleter(tabManager);
// Register the command
Field fieldKnownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands");
fieldKnownCommands.setAccessible(true);
Map<String, Command> knownCommands = (Map<String, Command>) fieldKnownCommands.get(commandMap);
knownCommands.put(command, commandObject);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}

View File

@ -1,63 +0,0 @@
package com.songoda.core.command;
import com.songoda.core.SongodaCore;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import java.util.ArrayList;
import java.util.List;
public class TabManager implements TabCompleter {
private final CommandManager commandManager;
TabManager(CommandManager commandManager) {
this.commandManager = commandManager;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] strings) {
for (AbstractCommand abstractCommand : commandManager.getCommands()) {
if (abstractCommand.getCommand() != null && abstractCommand.getCommand().equalsIgnoreCase(command.getName()) && !abstractCommand.hasArgs()) {
if (strings.length == 1) {
List<String> subs = new ArrayList<>();
for (AbstractCommand ac : commandManager.getCommands()) {
if (ac.getSubCommand() == null) continue;
subs.addAll(ac.getSubCommand());
}
subs.removeIf(s -> !s.toLowerCase().startsWith(strings[0].toLowerCase()));
return subs;
}
} else if (strings.length != 0
&& abstractCommand.getCommand() != null
&& abstractCommand.getCommand().equalsIgnoreCase(command.getName().toLowerCase())) {
String cmd = strings[0];
String cmd2 = strings.length >= 2 ? String.join(" ", strings[0], strings[1]) : null;
if (abstractCommand.hasArgs()) {
return onCommand(abstractCommand, strings, sender);
} else {
for (String cmds : abstractCommand.getSubCommand()) {
if (cmd.equalsIgnoreCase(cmds) || (cmd2 != null && cmd2.equalsIgnoreCase(cmds))) {
return onCommand(abstractCommand, strings, sender);
}
}
}
}
}
return null;
}
private List<String> onCommand(AbstractCommand abstractCommand, String[] strings, CommandSender sender) {
List<String> list = abstractCommand.onTab(SongodaCore.getInstance(), sender, strings);
String str = strings[strings.length - 1];
if (list != null && str != null && str.length() >= 1) {
try {
list.removeIf(s -> !s.toLowerCase().startsWith(str.toLowerCase()));
} catch (UnsupportedOperationException ignored) {
}
}
return list;
}
}

View File

@ -1,42 +0,0 @@
package com.songoda.core.command.commands;
import com.songoda.core.SongodaCore;
import com.songoda.core.command.AbstractCommand;
import com.songoda.core.gui.GUIOverview;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
public class CommandSongoda extends AbstractCommand {
public CommandSongoda() {
super(true, false, "songoda");
}
@Override
protected ReturnType runCommand(SongodaCore instance, CommandSender sender, String... args) {
new GUIOverview(instance, (Player) sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(SongodaCore instance, CommandSender sender, String... args) {
return null;
}
@Override
public String getPermissionNode() {
return "songoda.admin";
}
@Override
public String getSyntax() {
return "/songoda";
}
@Override
public String getDescription() {
return "Displays this interface.";
}
}

View File

@ -1,6 +1,8 @@
package com.songoda.core.library.commands;
import com.songoda.core.utils.Methods;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -13,8 +15,12 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.plugin.Plugin;
public class CommandManager implements CommandExecutor, TabCompleter {
@ -205,4 +211,31 @@ public class CommandManager implements CommandExecutor, TabCompleter {
}
return list;
}
public static void registerCommandDynamically(Plugin plugin, String command, CommandExecutor executor, TabCompleter tabManager) {
try {
// Retrieve the SimpleCommandMap from the server
Class<?> classCraftServer = Bukkit.getServer().getClass();
Field fieldCommandMap = classCraftServer.getDeclaredField("commandMap");
fieldCommandMap.setAccessible(true);
SimpleCommandMap commandMap = (SimpleCommandMap) fieldCommandMap.get(Bukkit.getServer());
// Construct a new Command object
Constructor<PluginCommand> constructorPluginCommand = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructorPluginCommand.setAccessible(true);
PluginCommand commandObject = constructorPluginCommand.newInstance(command, plugin);
commandObject.setExecutor(executor);
// Set tab complete
commandObject.setTabCompleter(tabManager);
// Register the command
Field fieldKnownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands");
fieldKnownCommands.setAccessible(true);
Map<String, Command> knownCommands = (Map<String, Command>) fieldKnownCommands.get(commandMap);
knownCommands.put(command, commandObject);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}