From 4af754fec528d15475e0adff18888baf7ea7b6cf Mon Sep 17 00:00:00 2001 From: Teriuihi Date: Mon, 9 May 2022 19:42:14 +0200 Subject: [PATCH] Added config, and reload command Added queue for try-lock --- .gitignore | 3 +- build.gradle | 2 +- src/main/java/com/alttd/datalock/Config.java | 159 ++++++++++++++++++ .../java/com/alttd/datalock/DataLock.java | 20 ++- .../com/alttd/datalock/EventListener.java | 109 ++++++++++-- src/main/java/com/alttd/datalock/Lock.java | 8 + src/main/java/com/alttd/datalock/Reload.java | 34 ++++ 7 files changed, 318 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/alttd/datalock/Config.java create mode 100644 src/main/java/com/alttd/datalock/Reload.java diff --git a/.gitignore b/.gitignore index e6eed51..ab87f3f 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,5 @@ run/ !gradle-wrapper.jar # My stuff -gradle/ \ No newline at end of file +gradle/ +*.bat \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9775635..f28b830 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ dependencies { annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' } -def targetJavaVersion = 11 +def targetJavaVersion = 17 java { def javaVersion = JavaVersion.toVersion(targetJavaVersion) sourceCompatibility = javaVersion diff --git a/src/main/java/com/alttd/datalock/Config.java b/src/main/java/com/alttd/datalock/Config.java new file mode 100644 index 0000000..65b9392 --- /dev/null +++ b/src/main/java/com/alttd/datalock/Config.java @@ -0,0 +1,159 @@ +package com.alttd.datalock; + +import com.google.common.base.Throwables; +import com.google.common.reflect.TypeToken; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.yaml.snakeyaml.DumperOptions; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public final class Config { + private static final Pattern PATH_PATTERN = Pattern.compile("\\."); + private static final String HEADER = ""; + + public static ConfigurationNode config; + public static YAMLConfigurationLoader configLoader; + + static int version; + static boolean verbose; + + public static File CONFIG_PATH; + + public static void init() { // todo setup share for the config + CONFIG_PATH = new File(DataLock.getDataDirectory().toAbsolutePath() + File.separator + "DataLock"); + File configFile = new File(CONFIG_PATH, "config.yml"); + + configLoader = YAMLConfigurationLoader.builder() + .setFile(configFile) + .setFlowStyle(DumperOptions.FlowStyle.BLOCK) + .build(); + if (!configFile.getParentFile().exists()) { + if (!configFile.getParentFile().mkdirs()) { + return; + } + } + if (!configFile.exists()) { + try { + if (!configFile.createNewFile()) { + return; + } + } catch (IOException error) { + error.printStackTrace(); + } + } + + try { + config = configLoader.load(ConfigurationOptions.defaults().setHeader(HEADER)); + } catch (IOException e) { + e.printStackTrace(); + } + + verbose = getBoolean("verbose", true); + version = getInt("config-version", 1); + + readConfig(Config.class, null); + try { + configLoader.save(config); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void readConfig(Class clazz, Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPrivate(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException | IllegalAccessException ex) { + throw Throwables.propagate(ex.getCause()); + } + } + } + } + try { + configLoader.save(config); + } catch (IOException ex) { + throw Throwables.propagate(ex.getCause()); + } + } + + public static void saveConfig() { + try { + configLoader.save(config); + } catch (IOException ex) { + throw Throwables.propagate(ex.getCause()); + } + } + + private static Object[] splitPath(String key) { + return PATH_PATTERN.split(key); + } + + private static void set(String path, Object def) { + if (config.getNode(splitPath(path)).isVirtual()) + config.getNode(splitPath(path)).setValue(def); + } + + private static void setString(String path, String def) { + try { + if (config.getNode(splitPath(path)).isVirtual()) + config.getNode(splitPath(path)).setValue(TypeToken.of(String.class), def); + } catch (ObjectMappingException ex) { + } + } + + private static boolean getBoolean(String path, boolean def) { + set(path, def); + return config.getNode(splitPath(path)).getBoolean(def); + } + + private static double getDouble(String path, double def) { + set(path, def); + return config.getNode(splitPath(path)).getDouble(def); + } + + private static int getInt(String path, int def) { + set(path, def); + return config.getNode(splitPath(path)).getInt(def); + } + + private static String getString(String path, String def) { + setString(path, def); + return config.getNode(splitPath(path)).getString(def); + } + + private static Long getLong(String path, Long def) { + set(path, def); + return config.getNode(splitPath(path)).getLong(def); + } + + private static List getList(String path, T def) { + try { + set(path, def); + return config.getNode(splitPath(path)).getList(TypeToken.of(String.class)); + } catch (ObjectMappingException ex) { + } + return new ArrayList<>(); + } + + /** + * ONLY EDIT ANYTHING BELOW THIS LINE + **/ + public static List PLUGIN_MESSAGE_CHANNELS = new ArrayList<>(List.of("example_plugin:table_1")); + + private static void loadGroups() { + PLUGIN_MESSAGE_CHANNELS = getList("settings.channels", new ArrayList<>(List.of("example_plugin:table_1"))); + } +} diff --git a/src/main/java/com/alttd/datalock/DataLock.java b/src/main/java/com/alttd/datalock/DataLock.java index e8d7017..4c723bb 100644 --- a/src/main/java/com/alttd/datalock/DataLock.java +++ b/src/main/java/com/alttd/datalock/DataLock.java @@ -26,15 +26,18 @@ public class DataLock { private final Path dataDirectory; @Inject - public DataLock(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxydataDirectory) { + public DataLock(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxyDataDirectory) { instance = this; server = proxyServer; logger = proxyLogger; - dataDirectory = proxydataDirectory; + dataDirectory = proxyDataDirectory; } @Subscribe public void onProxyInitialization(ProxyInitializeEvent event) { + reloadConfig(); + server.getEventManager().register(this, EventListener.getInstance()); + new Reload(server); } public static DataLock getInstance() { @@ -44,4 +47,17 @@ public class DataLock { public static Logger getLogger() { return getInstance().logger; } + + public static ProxyServer getServer() { + return getInstance().server; + } + + public static Path getDataDirectory() { + return getInstance().dataDirectory; + } + + public void reloadConfig() { + Config.init(); + EventListener.reload(); + } } diff --git a/src/main/java/com/alttd/datalock/EventListener.java b/src/main/java/com/alttd/datalock/EventListener.java index 37d150e..4ec0db5 100644 --- a/src/main/java/com/alttd/datalock/EventListener.java +++ b/src/main/java/com/alttd/datalock/EventListener.java @@ -8,23 +8,40 @@ import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; import java.util.*; public class EventListener { + private final HashMap> queuedLocks = new HashMap<>(); private final HashMap> channelLockMap = new HashMap<>(); private final static List channelIdentifierList = new ArrayList<>(); + private static EventListener instance = null; - public EventListener(List channelIdentifierList) - { - EventListener.reload(channelIdentifierList); + public static EventListener getInstance() { + return instance; } - public static void reload(List channelIdentifierList) + public static void reload() { + if (instance == null) + instance = new EventListener(); EventListener.channelIdentifierList.clear(); - EventListener.channelIdentifierList.addAll(channelIdentifierList); + for (String s : Config.PLUGIN_MESSAGE_CHANNELS) { + String[] split = s.split(":"); + if (split.length != 2) { + Logger.warn("Invalid message channel [%] in config.", s); + continue; + } + MinecraftChannelIdentifier minecraftChannelIdentifier = MinecraftChannelIdentifier.create(split[0], split[1]); + if (EventListener.channelIdentifierList.contains(minecraftChannelIdentifier)) { + Logger.warn("Duplicate message channel [%] in config.", s); + continue; + } + EventListener.channelIdentifierList.add(minecraftChannelIdentifier); + } } @Subscribe @@ -76,19 +93,30 @@ public class EventListener { out.writeUTF("try-lock-result"); Lock lock = new Lock(serverConnection.getServerInfo().hashCode(), data); - if (lockSet.contains(lock)) //An entry from this server already exists, so we can say that it's locked - { + if (lockSet.contains(lock)) { + //An entry from this server already exists, so we can say that it's locked out.writeBoolean(true); serverConnection.sendPluginMessage(identifier, out.toByteArray()); return; } - Optional first = lockSet.stream().filter(a -> a.compareTo(lock) == 0).findFirst(); - if (first.isPresent()) //An entry from another server exists, so we can't lock it - { - out.writeBoolean(false); - serverConnection.sendPluginMessage(identifier, out.toByteArray()); - return; + Optional optionalActiveLock = lockSet.stream().filter(a -> a.compareTo(lock) == 0).findAny(); + if (optionalActiveLock.isPresent()) { + Lock activeLock = optionalActiveLock.get(); + if (DataLock.getServer().getAllServers().stream() + .filter(sc -> !sc.getPlayersConnected().isEmpty()) + .noneMatch(sc -> activeLock.getServerHash() == sc.getServerInfo().hashCode())) { + //The server the active lock belongs to is no longer present, we can remove it and apply the new lock + Logger.warn("Removing lock [%] due to being unable to find a server where that lock was active", activeLock.getData()); + lockSet.remove(activeLock); + } + else { + //An entry from another server exists, so we can't lock it + out.writeBoolean(false); + serverConnection.sendPluginMessage(identifier, out.toByteArray()); + queueLock(queuedLocks.getOrDefault(identifier, new HashSet<>()), identifier, lock, serverConnection); + return; + } } //Lock the data @@ -125,6 +153,7 @@ public class EventListener { lockSet.remove(lock); channelLockMap.put(identifier, lockSet); serverConnection.sendPluginMessage(identifier, out.toByteArray()); + queueNextLock(lockSet, lock, identifier); return; } @@ -133,6 +162,7 @@ public class EventListener { { out.writeBoolean(true); serverConnection.sendPluginMessage(identifier, out.toByteArray()); + queueNextLock(lockSet, lock, identifier); return; } @@ -141,4 +171,57 @@ public class EventListener { serverConnection.sendPluginMessage(identifier, out.toByteArray()); } + private void queueLock(HashSet lockSet, ChannelIdentifier identifier, Lock lock, ServerConnection serverConnection) { + if (lockSet.contains(lock)) { + //Lock already queued we don't have to queue it again + return; + } + Optional optionalQueuedLock = lockSet.stream().filter(a -> a.compareTo(lock) != 0).findAny(); + if (optionalQueuedLock.isPresent()) { + Lock queuedLock = optionalQueuedLock.get(); + Optional optionalRegisteredServer = DataLock.getServer().getAllServers().stream() + .filter(sc -> queuedLock.getServerHash() == sc.getServerInfo().hashCode()) + .findAny(); + if (optionalRegisteredServer.isPresent()) { + //The server that queued this lock is still active, so we can't queue a new one + RegisteredServer registeredServer = optionalRegisteredServer.get(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("queue-lock-failed"); + out.writeUTF(queuedLock.getData()); + out.writeUTF(registeredServer.getServerInfo().getName()); + serverConnection.sendPluginMessage(identifier, out.toByteArray()); + return; + } + Logger.warn("Removing queued lock [%] due to being unable to find a server where that lock could be active", queuedLock.getData()); + lockSet.remove(queuedLock); + } + lockSet.add(lock); + queuedLocks.put(identifier, lockSet); + } + + private void queueNextLock(HashSet lockSet, Lock lock, ChannelIdentifier identifier) { + if (!queuedLocks.containsKey(identifier)) + return; + HashSet queuedLockSet = queuedLocks.get(identifier); + Optional optionalQueuedLock = queuedLockSet.stream().filter(l -> l.compareTo(lock) == 0).findFirst(); + if (optionalQueuedLock.isEmpty()) + return; + Lock queuedLock = optionalQueuedLock.get(); + queuedLockSet.remove(lock); + + Optional optionalRegisteredServer = DataLock.getServer().getAllServers().stream() + .filter(registeredServer -> registeredServer.getServerInfo().hashCode() == queuedLock.getServerHash()) + .findAny(); + if (optionalRegisteredServer.isEmpty()) { + Logger.warn("Removing queued lock [%] due to being unable to find a server where that lock could be active", queuedLock.getData()); + return; + } + RegisteredServer registeredServer = optionalRegisteredServer.get(); + lockSet.add(queuedLock); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("locked-queued-lock"); + out.writeUTF(queuedLock.getData()); + registeredServer.sendPluginMessage(identifier, out.toByteArray()); + } + } diff --git a/src/main/java/com/alttd/datalock/Lock.java b/src/main/java/com/alttd/datalock/Lock.java index 07c3218..59b0f6a 100644 --- a/src/main/java/com/alttd/datalock/Lock.java +++ b/src/main/java/com/alttd/datalock/Lock.java @@ -15,6 +15,14 @@ public class Lock implements Comparable { this.data = data; } + public int getServerHash() { + return serverHash; + } + + public String getData() { + return data; + } + @Override public final boolean equals(@Nullable Object o) { if (this == o) { diff --git a/src/main/java/com/alttd/datalock/Reload.java b/src/main/java/com/alttd/datalock/Reload.java new file mode 100644 index 0000000..b615985 --- /dev/null +++ b/src/main/java/com/alttd/datalock/Reload.java @@ -0,0 +1,34 @@ +package com.alttd.datalock; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.velocitypowered.api.command.BrigadierCommand; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; + +public class Reload { + + public Reload(ProxyServer proxyServer) { + LiteralCommandNode command = LiteralArgumentBuilder + .literal("reloaddatalock") + .requires(ctx -> ctx.hasPermission("command.datalock.reload")) + .executes(context -> { + DataLock.getInstance().reloadConfig(); + context.getSource().sendMessage(Component.text(TextColor.color(85, 255, 85) + "Reloaded DataLock.")); + return 1; + }) + .build(); + + BrigadierCommand brigadierCommand = new BrigadierCommand(command); + + CommandMeta.Builder metaBuilder = proxyServer.getCommandManager().metaBuilder(brigadierCommand); + + CommandMeta meta = metaBuilder.build(); + + proxyServer.getCommandManager().register(meta, brigadierCommand); + } + +}