From 725bf2c31563be6e927cef11cc9d8142586cf29f Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 00:07:02 +0000 Subject: [PATCH 01/11] Start work on Velocity support --- build.gradle | 3 +- .../william278/husksync/HuskSyncBukkit.java | 4 +- .../husksync/bukkit/data/DataViewer.java | 2 +- .../bukkit/listener/BukkitRedisListener.java | 2 +- .../husksync/bukkit/util/PlayerSetter.java | 4 +- bungeecord/build.gradle | 1 - .../husksync/HuskSyncBungeeCord.java | 71 +++--- .../bungeecord/command/HuskSyncCommand.java | 18 +- .../bungeecord/config/ConfigLoader.java | 2 +- .../bungeecord/config/ConfigManager.java | 20 +- .../listener/BungeeEventListener.java | 9 +- .../listener/BungeeRedisListener.java | 22 +- .../bungeecord/migrator/MPDBMigrator.java | 64 +++--- .../bungeecord/util/BungeeLogger.java | 33 +++ .../bungeecord/util/BungeeUpdateChecker.java | 2 +- common/build.gradle | 1 + .../java/me/william278/husksync/Server.java | 10 + .../java/me/william278/husksync/Settings.java | 2 +- .../husksync/proxy}/data/DataManager.java | 120 ++++++++--- .../husksync/proxy}/data/sql/Database.java | 10 +- .../husksync/proxy}/data/sql/MySQL.java | 10 +- .../husksync/proxy}/data/sql/SQLite.java | 21 +- .../me/william278/husksync/util/Logger.java | 19 ++ .../src/main/resources/languages/en-gb.yml | 0 .../src/main/resources/proxy-config.yml | 0 plugin/build.gradle | 1 + settings.gradle | 1 + velocity/build.gradle | 20 ++ .../william278/husksync/HuskSyncVelocity.java | 187 ++++++++++++++++ .../velocity/VelocityUpdateChecker.java | 20 ++ .../velocity/command/HuskSyncCommand.java | 34 +++ .../velocity/config/ConfigLoader.java | 97 +++++++++ .../velocity/config/ConfigManager.java | 92 ++++++++ .../listener/VelocityEventListener.java | 46 ++++ .../listener/VelocityRedisListener.java | 203 ++++++++++++++++++ .../velocity/util/VelocityLogger.java | 44 ++++ 36 files changed, 1019 insertions(+), 176 deletions(-) create mode 100644 bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeLogger.java create mode 100644 common/src/main/java/me/william278/husksync/Server.java rename {bungeecord/src/main/java/me/william278/husksync/bungeecord => common/src/main/java/me/william278/husksync/proxy}/data/DataManager.java (77%) rename {bungeecord/src/main/java/me/william278/husksync/bungeecord => common/src/main/java/me/william278/husksync/proxy}/data/sql/Database.java (80%) rename {bungeecord/src/main/java/me/william278/husksync/bungeecord => common/src/main/java/me/william278/husksync/proxy}/data/sql/MySQL.java (91%) rename {bungeecord/src/main/java/me/william278/husksync/bungeecord => common/src/main/java/me/william278/husksync/proxy}/data/sql/SQLite.java (82%) create mode 100644 common/src/main/java/me/william278/husksync/util/Logger.java rename {bungeecord => common}/src/main/resources/languages/en-gb.yml (100%) rename bungeecord/src/main/resources/bungee-config.yml => common/src/main/resources/proxy-config.yml (100%) create mode 100644 velocity/build.gradle create mode 100644 velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/util/VelocityLogger.java diff --git a/build.gradle b/build.gradle index bc39c5fb..96ada90e 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { allprojects { group 'me.William278' - version '1.1.3' + version '1.2-dev' compileJava { options.encoding = 'UTF-8' } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } @@ -33,6 +33,7 @@ subprojects { mavenLocal() mavenCentral() maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } + maven { url 'https://repo.velocitypowered.com/snapshots/' } maven { url 'https://repo.minebench.de/' } maven { url 'https://repo.codemc.org/repository/maven-public' } maven { url 'https://jitpack.io' } diff --git a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java index 78b3eab6..a654e082 100644 --- a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java +++ b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java @@ -55,7 +55,7 @@ public final class HuskSyncBukkit extends JavaPlugin { } try { new RedisMessage(RedisMessage.MessageType.CONNECTION_HANDSHAKE, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), serverUUID.toString(), Boolean.toString(isMySqlPlayerDataBridgeInstalled), Bukkit.getName(), @@ -75,7 +75,7 @@ public final class HuskSyncBukkit extends JavaPlugin { if (!handshakeCompleted) return; try { new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), serverUUID.toString(), Bukkit.getName()).send(); } catch (IOException e) { diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataViewer.java b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataViewer.java index ca09a94f..43134b51 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataViewer.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataViewer.java @@ -57,7 +57,7 @@ public class DataViewer { // Send a redis message with the updated data after the viewing new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), RedisMessage.serialize(playerData)) .send(); } diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java index c647eee2..eac248af 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/listener/BukkitRedisListener.java @@ -97,7 +97,7 @@ public class BukkitRedisListener extends RedisListener { try { MPDBPlayerData data = (MPDBPlayerData) RedisMessage.deserialize(encodedData); new RedisMessage(RedisMessage.MessageType.DECODED_MPDB_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), RedisMessage.serialize(MPDBDeserializer.convertMPDBData(data)), data.playerName) .send(); diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java index 0b5c3670..5414d17c 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/util/PlayerSetter.java @@ -104,7 +104,7 @@ public class PlayerSetter { try { final String serializedPlayerData = getNewSerializedPlayerData(player); new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_UPDATE, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), serializedPlayerData).send(); } catch (IOException e) { plugin.getLogger().log(Level.SEVERE, "Failed to send a PlayerData update to the proxy", e); @@ -123,7 +123,7 @@ public class PlayerSetter { */ public static void requestPlayerData(UUID playerUUID) throws IOException { new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_REQUEST, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), + new RedisMessage.MessageTarget(Settings.ServerType.PROXY, null, Settings.cluster), playerUUID.toString()).send(); } diff --git a/bungeecord/build.gradle b/bungeecord/build.gradle index 0b09ebf1..35707feb 100644 --- a/bungeecord/build.gradle +++ b/bungeecord/build.gradle @@ -4,7 +4,6 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-bungeecord:2.2.1' - implementation 'com.zaxxer:HikariCP:5.0.0' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' diff --git a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java index f3ab7ca2..8f6f2f50 100644 --- a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java +++ b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java @@ -3,26 +3,21 @@ package me.william278.husksync; import me.william278.husksync.bungeecord.command.HuskSyncCommand; import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigManager; -import me.william278.husksync.bungeecord.data.DataManager; -import me.william278.husksync.bungeecord.data.sql.Database; -import me.william278.husksync.bungeecord.data.sql.MySQL; -import me.william278.husksync.bungeecord.data.sql.SQLite; +import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.bungeecord.listener.BungeeEventListener; import me.william278.husksync.bungeecord.listener.BungeeRedisListener; import me.william278.husksync.bungeecord.migrator.MPDBMigrator; +import me.william278.husksync.bungeecord.util.BungeeLogger; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.redis.RedisMessage; +import me.william278.husksync.util.Logger; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.bstats.bungeecord.Metrics; import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.HashMap; import java.util.HashSet; import java.util.Objects; -import java.util.UUID; import java.util.logging.Level; public final class HuskSyncBungeeCord extends Plugin { @@ -47,17 +42,20 @@ public final class HuskSyncBungeeCord extends Plugin { */ public static HashSet synchronisedServers; - private static HashMap clusterDatabases; - - public static Connection getConnection(String clusterId) throws SQLException { - return clusterDatabases.get(clusterId).getConnection(); - } + public static DataManager dataManager; public static MPDBMigrator mpdbMigrator; + private Logger logger; + + public Logger getBungeeLogger() { + return logger; + } + @Override public void onLoad() { instance = this; + logger = new BungeeLogger(getLogger()); } @Override @@ -82,35 +80,23 @@ public final class HuskSyncBungeeCord extends Plugin { new BungeeUpdateChecker(getDescription().getVersion()).logToConsole(); } - // Initialize the database - clusterDatabases = new HashMap<>(); - for (Settings.SynchronisationCluster cluster : Settings.clusters) { - Database clusterDatabase = switch (Settings.dataStorageType) { - case SQLITE -> new SQLite(this, cluster); - case MYSQL -> new MySQL(this, cluster); - }; - clusterDatabase.load(); - clusterDatabase.createTables(); - clusterDatabases.put(cluster.clusterId(), clusterDatabase); - } + // Setup data manager + dataManager = new DataManager(getBungeeLogger(), getDataFolder()); - // Abort loading if the database failed to initialize - for (Database database : clusterDatabases.values()) { - if (database.isInactive()) { - getLogger().severe("Failed to initialize the database(s); HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion()); - return; - } + // Ensure the data manager initialized correctly + if (dataManager.hasFailedInitialization) { + getBungeeLogger().severe("Failed to initialize the HuskSync database(s).\n" + + "HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion()); } - // Setup player data cache for (Settings.SynchronisationCluster cluster : Settings.clusters) { - DataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache()); + dataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache()); } // Initialize the redis listener if (!new BungeeRedisListener().isActiveAndEnabled) { - getLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion()); + getBungeeLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion()); return; } @@ -127,11 +113,11 @@ public final class HuskSyncBungeeCord extends Plugin { try { new Metrics(this, METRICS_ID); } catch (Exception e) { - getLogger().info("Skipped metrics initialization"); + getBungeeLogger().info("Skipped metrics initialization"); } // Log to console - getLogger().info("Enabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); + getBungeeLogger().info("Enabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); // Mark as ready for redis message processing readyForRedis = true; @@ -150,23 +136,14 @@ public final class HuskSyncBungeeCord extends Plugin { server.serverUUID().toString(), ProxyServer.getInstance().getName()).send(); } catch (IOException e) { - getInstance().getLogger().log(Level.SEVERE, "Failed to serialize Redis message for handshake termination", e); + getBungeeLogger().log(Level.SEVERE, "Failed to serialize Redis message for handshake termination", e); } } - // Close the database - for (Database database : clusterDatabases.values()) { - database.close(); - } + dataManager.closeDatabases(); // Log to console - getLogger().info("Disabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); + getBungeeLogger().info("Disabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); } - /** - * A record representing a server synchronised on the network and whether it has MySqlPlayerDataBridge installed - */ - public record Server(UUID serverUUID, boolean hasMySqlPlayerDataBridge, String huskSyncVersion, String serverBrand, - String clusterId) { - } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java index b3783f4e..8fc3e823 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java @@ -2,13 +2,13 @@ package me.william278.husksync.bungeecord.command; import de.themoep.minedown.MineDown; import me.william278.husksync.HuskSyncBungeeCord; +import me.william278.husksync.Server; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.util.MessageManager; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigManager; -import me.william278.husksync.bungeecord.data.DataManager; import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.CommandSender; @@ -60,7 +60,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { int updatesNeeded = 0; String bukkitBrand = "Spigot"; String bukkitVersion = "1.0"; - for (HuskSyncBungeeCord.Server server : HuskSyncBungeeCord.synchronisedServers) { + for (Server server : HuskSyncBungeeCord.synchronisedServers) { BungeeUpdateChecker updateChecker = new BungeeUpdateChecker(server.huskSyncVersion()); if (!updateChecker.isUpToDate()) { updatesNeeded++; @@ -156,7 +156,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { } int playerDataSize = 0; for (Settings.SynchronisationCluster cluster : Settings.clusters) { - playerDataSize += DataManager.playerDataCache.get(cluster).playerData.size(); + playerDataSize += HuskSyncBungeeCord.dataManager.playerDataCache.get(cluster).playerData.size(); } sender.sendMessage(new MineDown(MessageManager.PLUGIN_STATUS.toString() .replaceAll("%1%", String.valueOf(HuskSyncBungeeCord.synchronisedServers.size())) @@ -180,7 +180,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { "reload") .send(); } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Failed to serialize reload notification message data"); + plugin.getBungeeLogger().log(Level.WARNING, "Failed to serialize reload notification message data"); } sender.sendMessage(new MineDown(MessageManager.getMessage("reload_complete")).toComponent()); @@ -324,7 +324,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (!cluster.clusterId().equals(clusterId)) continue; - PlayerData playerData = DataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); + PlayerData playerData = HuskSyncBungeeCord.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); if (playerData == null) { viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); return; @@ -337,7 +337,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_inventory_of").replaceAll("%1%", targetPlayerName)).toComponent()); } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + plugin.getBungeeLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); } return; } @@ -358,7 +358,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (!cluster.clusterId().equals(clusterId)) continue; - PlayerData playerData = DataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); + PlayerData playerData = HuskSyncBungeeCord.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); if (playerData == null) { viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); return; @@ -371,7 +371,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_ender_chest_of").replaceAll("%1%", targetPlayerName)).toComponent()); } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + plugin.getBungeeLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); } return; } @@ -390,7 +390,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, player.getUniqueId(), null), plugin.getProxy().getName(), plugin.getDescription().getVersion()).send(); } catch (IOException e) { - plugin.getLogger().log(Level.WARNING, "Failed to serialize plugin information to send", e); + plugin.getBungeeLogger().log(Level.WARNING, "Failed to serialize plugin information to send", e); } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java index 04e5298b..5880f3b2 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigLoader.java @@ -37,7 +37,7 @@ public class ConfigLoader { Settings.language = config.getString("language", "en-gb"); - Settings.serverType = Settings.ServerType.BUNGEECORD; + Settings.serverType = Settings.ServerType.PROXY; Settings.automaticUpdateChecks = config.getBoolean("check_for_updates", true); Settings.redisHost = config.getString("redis_settings.host", "localhost"); Settings.redisPort = config.getInt("redis_settings.port", 6379); diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java index f3f94477..2bb222a2 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/config/ConfigManager.java @@ -19,16 +19,16 @@ public class ConfigManager { try { if (!plugin.getDataFolder().exists()) { if (plugin.getDataFolder().mkdir()) { - plugin.getLogger().info("Created HuskSync data folder"); + plugin.getBungeeLogger().info("Created HuskSync data folder"); } } File configFile = new File(plugin.getDataFolder(), "config.yml"); if (!configFile.exists()) { - Files.copy(plugin.getResourceAsStream("bungee-config.yml"), configFile.toPath()); - plugin.getLogger().info("Created HuskSync config file"); + Files.copy(plugin.getResourceAsStream("proxy-config.yml"), configFile.toPath()); + plugin.getBungeeLogger().info("Created HuskSync config file"); } } catch (Exception e) { - plugin.getLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); + plugin.getBungeeLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); } } @@ -36,7 +36,7 @@ public class ConfigManager { try { ConfigurationProvider.getProvider(YamlConfiguration.class).save(config, new File(plugin.getDataFolder(), "config.yml")); } catch (IOException e) { - plugin.getLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); + plugin.getBungeeLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); } } @@ -44,16 +44,16 @@ public class ConfigManager { try { if (!plugin.getDataFolder().exists()) { if (plugin.getDataFolder().mkdir()) { - plugin.getLogger().info("Created HuskSync data folder"); + plugin.getBungeeLogger().info("Created HuskSync data folder"); } } File messagesFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); if (!messagesFile.exists()) { Files.copy(plugin.getResourceAsStream("languages/" + Settings.language + ".yml"), messagesFile.toPath()); - plugin.getLogger().info("Created HuskSync messages file"); + plugin.getBungeeLogger().info("Created HuskSync messages file"); } } catch (Exception e) { - plugin.getLogger().log(Level.CONFIG, "An exception occurred loading the messages file", e); + plugin.getBungeeLogger().log(Level.CONFIG, "An exception occurred loading the messages file", e); } } @@ -62,7 +62,7 @@ public class ConfigManager { File configFile = new File(plugin.getDataFolder(), "config.yml"); return ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); } catch (IOException e) { - plugin.getLogger().log(Level.CONFIG, "An IOException occurred fetching the configuration file", e); + plugin.getBungeeLogger().log(Level.CONFIG, "An IOException occurred fetching the configuration file", e); return null; } } @@ -72,7 +72,7 @@ public class ConfigManager { File configFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); return ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); } catch (IOException e) { - plugin.getLogger().log(Level.CONFIG, "An IOException occurred fetching the messages file", e); + plugin.getBungeeLogger().log(Level.CONFIG, "An IOException occurred fetching the messages file", e); return null; } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeEventListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeEventListener.java index fdc21aeb..73c3b884 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeEventListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeEventListener.java @@ -2,7 +2,6 @@ package me.william278.husksync.bungeecord.listener; import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.PlayerData; -import me.william278.husksync.bungeecord.data.DataManager; import me.william278.husksync.Settings; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.ProxyServer; @@ -24,15 +23,15 @@ public class BungeeEventListener implements Listener { final ProxiedPlayer player = event.getPlayer(); ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { // Ensure the player has data on SQL and that it is up-to-date - DataManager.ensurePlayerExists(player.getUniqueId(), player.getName()); + HuskSyncBungeeCord.dataManager.ensurePlayerExists(player.getUniqueId(), player.getName()); // Get the player's data from SQL - final Map data = DataManager.getPlayerData(player.getUniqueId()); + final Map data = HuskSyncBungeeCord.dataManager.getPlayerData(player.getUniqueId()); // Update the player's data from SQL onto the cache assert data != null; for (Settings.SynchronisationCluster cluster : data.keySet()) { - DataManager.playerDataCache.get(cluster).updatePlayer(data.get(cluster)); + HuskSyncBungeeCord.dataManager.playerDataCache.get(cluster).updatePlayer(data.get(cluster)); } // Send a message asking the bukkit to request data on join @@ -41,7 +40,7 @@ public class BungeeEventListener implements Listener { new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null), RedisMessage.RequestOnJoinUpdateType.ADD_REQUESTER.toString(), player.getUniqueId().toString()).send(); } catch (IOException e) { - plugin.getLogger().log(Level.SEVERE, "Failed to serialize request data on join message data"); + plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize request data on join message data"); e.printStackTrace(); } }); diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java index f1b128b8..1e47ac96 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java @@ -2,10 +2,10 @@ package me.william278.husksync.bungeecord.listener; import de.themoep.minedown.MineDown; import me.william278.husksync.HuskSyncBungeeCord; +import me.william278.husksync.Server; import me.william278.husksync.util.MessageManager; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; -import me.william278.husksync.bungeecord.data.DataManager; import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisMessage; @@ -32,13 +32,13 @@ public class BungeeRedisListener extends RedisListener { for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (cluster.clusterId().equals(clusterId)) { // Get the player data from the cache - PlayerData cachedData = DataManager.playerDataCache.get(cluster).getPlayer(uuid); + PlayerData cachedData = HuskSyncBungeeCord.dataManager.playerDataCache.get(cluster).getPlayer(uuid); if (cachedData != null) { return cachedData; } - data = Objects.requireNonNull(DataManager.getPlayerData(uuid)).get(cluster); // Get their player data from MySQL - DataManager.playerDataCache.get(cluster).updatePlayer(data); // Update the cache + data = Objects.requireNonNull(HuskSyncBungeeCord.dataManager.getPlayerData(uuid)).get(cluster); // Get their player data from MySQL + HuskSyncBungeeCord.dataManager.playerDataCache.get(cluster).updatePlayer(data); // Update the cache break; } } @@ -53,7 +53,7 @@ public class BungeeRedisListener extends RedisListener { @Override public void handleMessage(RedisMessage message) { // Ignore messages destined for Bukkit servers - if (message.getMessageTarget().targetServerType() != Settings.ServerType.BUNGEECORD) { + if (message.getMessageTarget().targetServerType() != Settings.ServerType.PROXY) { return; } // Only process redis messages when ready @@ -107,7 +107,7 @@ public class BungeeRedisListener extends RedisListener { // Update the data in the cache and SQL for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (cluster.clusterId().equals(message.getMessageTarget().targetClusterId())) { - DataManager.updatePlayerData(playerData, cluster); + HuskSyncBungeeCord.dataManager.updatePlayerData(playerData, cluster); break; } } @@ -144,7 +144,7 @@ public class BungeeRedisListener extends RedisListener { serverUUID.toString(), plugin.getProxy().getName()) .send(); HuskSyncBungeeCord.synchronisedServers.add( - new HuskSyncBungeeCord.Server(serverUUID, hasMySqlPlayerDataBridge, + new Server(serverUUID, hasMySqlPlayerDataBridge, huskSyncVersion, bukkitBrand, message.getMessageTarget().targetClusterId())); log(Level.INFO, "Completed handshake with " + bukkitBrand + " server (" + serverUUID + ")"); } catch (IOException e) { @@ -158,8 +158,8 @@ public class BungeeRedisListener extends RedisListener { final String bukkitBrand = message.getMessageDataElements()[1]; // Remove a server from the synchronised server list - HuskSyncBungeeCord.Server serverToRemove = null; - for (HuskSyncBungeeCord.Server server : HuskSyncBungeeCord.synchronisedServers) { + Server serverToRemove = null; + for (Server server : HuskSyncBungeeCord.synchronisedServers) { if (server.serverUUID().equals(serverUUID)) { serverToRemove = server; break; @@ -186,7 +186,7 @@ public class BungeeRedisListener extends RedisListener { // Increment players migrated MPDBMigrator.playersMigrated++; - plugin.getLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); + plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); // When all the data has been received, save it if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) { @@ -204,6 +204,6 @@ public class BungeeRedisListener extends RedisListener { */ @Override public void log(Level level, String message) { - plugin.getLogger().log(level, message); + plugin.getBungeeLogger().log(level, message); } } \ No newline at end of file diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java index bda345af..2f355d8e 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java @@ -2,11 +2,11 @@ package me.william278.husksync.bungeecord.migrator; import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.PlayerData; +import me.william278.husksync.Server; import me.william278.husksync.Settings; -import me.william278.husksync.bungeecord.data.DataManager; -import me.william278.husksync.bungeecord.data.sql.Database; -import me.william278.husksync.bungeecord.data.sql.MySQL; import me.william278.husksync.migrator.MPDBPlayerData; +import me.william278.husksync.proxy.data.sql.Database; +import me.william278.husksync.proxy.data.sql.MySQL; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.ProxyServer; @@ -23,8 +23,8 @@ import java.util.logging.Level; /** * Class to handle migration of data from MySQLPlayerDataBridge *

- * The migrator accesses and decodes MPDB's format directly. - * It does this by establishing a connection + * The migrator accesses and decodes MPDB's format directly, + * by communicating with a Spigot server */ public class MPDBMigrator { @@ -43,19 +43,19 @@ public class MPDBMigrator { public void start() { if (ProxyServer.getInstance().getPlayers().size() > 0) { - plugin.getLogger().log(Level.WARNING, "Failed to start migration because there are players online. " + + plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because there are players online. " + "Your network has to be empty to migrate data for safety reasons."); return; } int synchronisedServersWithMpdb = 0; - for (HuskSyncBungeeCord.Server server : HuskSyncBungeeCord.synchronisedServers) { + for (Server server : HuskSyncBungeeCord.synchronisedServers) { if (server.hasMySqlPlayerDataBridge()) { synchronisedServersWithMpdb++; } } if (synchronisedServersWithMpdb < 1) { - plugin.getLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " + + plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " + "Please start one Spigot server with HuskSync installed to begin migration."); return; } @@ -67,7 +67,7 @@ public class MPDBMigrator { } } if (targetCluster == null) { - plugin.getLogger().log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " + + plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " + "Please ensure the target cluster is correct, configured in the proxy config file, then try again"); return; } @@ -83,7 +83,7 @@ public class MPDBMigrator { settings.sourceDatabase, settings.sourceUsername, settings.sourcePassword, targetCluster); sourceDatabase.load(); if (sourceDatabase.isInactive()) { - plugin.getLogger().log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " + + plugin.getBungeeLogger().log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " + "Please check you have input the correct connection details and try again."); return; } @@ -103,8 +103,8 @@ public class MPDBMigrator { // Clear the new database out of current data private void prepareTargetDatabase() { - plugin.getLogger().log(Level.INFO, "Preparing target database..."); - try (Connection connection = HuskSyncBungeeCord.getConnection(targetCluster.clusterId())) { + plugin.getBungeeLogger().log(Level.INFO, "Preparing target database..."); + try (Connection connection = HuskSyncBungeeCord.dataManager.getConnection(targetCluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) { statement.executeUpdate(); } @@ -112,14 +112,14 @@ public class MPDBMigrator { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An exception occurred preparing the target database", e); + plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred preparing the target database", e); } finally { - plugin.getLogger().log(Level.INFO, "Finished preparing target database!"); + plugin.getBungeeLogger().log(Level.INFO, "Finished preparing target database!"); } } private void getInventoryData() { - plugin.getLogger().log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge..."); + plugin.getBungeeLogger().log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.inventoryDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -135,14 +135,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An exception occurred getting inventory data", e); + plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting inventory data", e); } finally { - plugin.getLogger().log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge"); + plugin.getBungeeLogger().log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge"); } } private void getEnderChestData() { - plugin.getLogger().log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge..."); + plugin.getBungeeLogger().log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.enderChestDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -158,14 +158,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e); + plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e); } finally { - plugin.getLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); + plugin.getBungeeLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); } } private void getExperienceData() { - plugin.getLogger().log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge..."); + plugin.getBungeeLogger().log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.expDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -183,14 +183,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An exception occurred getting experience data", e); + plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting experience data", e); } finally { - plugin.getLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); + plugin.getBungeeLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); } } private void sendEncodedData() { - for (HuskSyncBungeeCord.Server processingServer : HuskSyncBungeeCord.synchronisedServers) { + for (Server processingServer : HuskSyncBungeeCord.synchronisedServers) { if (processingServer.hasMySqlPlayerDataBridge()) { for (MPDBPlayerData data : mpdbPlayerData) { try { @@ -201,10 +201,10 @@ public class MPDBMigrator { .send(); migratedDataSent++; } catch (IOException e) { - plugin.getLogger().log(Level.SEVERE, "Failed to serialize encoded MPDB data", e); + plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize encoded MPDB data", e); } } - plugin.getLogger().log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish"); + plugin.getBungeeLogger().log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish"); } return; } @@ -218,26 +218,26 @@ public class MPDBMigrator { public static void loadIncomingData(HashMap dataToLoad) { ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { int playersSaved = 0; - plugin.getLogger().log(Level.INFO, "Saving data for " + playersMigrated + " players..."); + plugin.getBungeeLogger().log(Level.INFO, "Saving data for " + playersMigrated + " players..."); for (PlayerData playerData : dataToLoad.keySet()) { String playerName = dataToLoad.get(playerData); // Add the player to the MySQL table - DataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); + HuskSyncBungeeCord.dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); // Update the data in the cache and SQL for (Settings.SynchronisationCluster cluster : Settings.clusters) { - DataManager.updatePlayerData(playerData, cluster); + HuskSyncBungeeCord.dataManager.updatePlayerData(playerData, cluster); break; } playersSaved++; - plugin.getLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); + plugin.getBungeeLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); } // Mark as done when done - plugin.getLogger().log(Level.INFO, """ + plugin.getBungeeLogger().log(Level.INFO, """ === MySQLPlayerDataBridge Migration Wizard ========== Migration complete! @@ -288,7 +288,7 @@ public class MPDBMigrator { */ public static class MigratorMySQL extends MySQL { public MigratorMySQL(HuskSyncBungeeCord instance, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) { - super(instance, cluster); + super(cluster, instance.getBungeeLogger()); super.host = host; super.port = port; super.database = database; diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeLogger.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeLogger.java new file mode 100644 index 00000000..cd1f7828 --- /dev/null +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeLogger.java @@ -0,0 +1,33 @@ +package me.william278.husksync.bungeecord.util; + +import me.william278.husksync.util.Logger; + +import java.util.logging.Level; + +public record BungeeLogger(java.util.logging.Logger parent) implements Logger { + + @Override + public void log(Level level, String message, Exception e) { + parent.log(level, message, e); + } + + @Override + public void log(Level level, String message) { + parent.log(level, message); + } + + @Override + public void info(String message) { + parent.info(message); + } + + @Override + public void severe(String message) { + parent.severe(message); + } + + @Override + public void config(String message) { + parent.config(message); + } +} diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeUpdateChecker.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeUpdateChecker.java index 610e5317..0457a467 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeUpdateChecker.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/util/BungeeUpdateChecker.java @@ -15,6 +15,6 @@ public class BungeeUpdateChecker extends UpdateChecker { @Override public void log(Level level, String message) { - plugin.getLogger().log(level, message); + plugin.getBungeeLogger().log(level, message); } } diff --git a/common/build.gradle b/common/build.gradle index 5ce26ca2..7f529c67 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,5 +1,6 @@ dependencies { implementation 'redis.clients:jedis:3.7.0' + implementation 'com.zaxxer:HikariCP:5.0.0' } import org.apache.tools.ant.filters.ReplaceTokens diff --git a/common/src/main/java/me/william278/husksync/Server.java b/common/src/main/java/me/william278/husksync/Server.java new file mode 100644 index 00000000..986ca4c4 --- /dev/null +++ b/common/src/main/java/me/william278/husksync/Server.java @@ -0,0 +1,10 @@ +package me.william278.husksync; + +import java.util.UUID; + +/** + * A record representing a server synchronised on the network and whether it has MySqlPlayerDataBridge installed + */ +public record Server(UUID serverUUID, boolean hasMySqlPlayerDataBridge, String huskSyncVersion, String serverBrand, + String clusterId) { +} diff --git a/common/src/main/java/me/william278/husksync/Settings.java b/common/src/main/java/me/william278/husksync/Settings.java index eabeed4f..34349242 100644 --- a/common/src/main/java/me/william278/husksync/Settings.java +++ b/common/src/main/java/me/william278/husksync/Settings.java @@ -76,7 +76,7 @@ public class Settings { public enum ServerType { BUKKIT, - BUNGEECORD + PROXY, } public enum DataStorageType { diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java b/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java similarity index 77% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java rename to common/src/main/java/me/william278/husksync/proxy/data/DataManager.java index 5bc4d8fe..9e3c73e6 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java +++ b/common/src/main/java/me/william278/husksync/proxy/data/DataManager.java @@ -1,10 +1,13 @@ -package me.william278.husksync.bungeecord.data; +package me.william278.husksync.proxy.data; import me.william278.husksync.PlayerData; -import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.Settings; -import me.william278.husksync.bungeecord.data.sql.Database; +import me.william278.husksync.proxy.data.sql.Database; +import me.william278.husksync.proxy.data.sql.MySQL; +import me.william278.husksync.proxy.data.sql.SQLite; +import me.william278.husksync.util.Logger; +import java.io.File; import java.sql.*; import java.time.Instant; import java.util.*; @@ -12,12 +15,65 @@ import java.util.logging.Level; public class DataManager { - private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); - /** * The player data cache for each cluster ID */ - public static HashMap playerDataCache = new HashMap<>(); + public HashMap playerDataCache = new HashMap<>(); + + /** + * Map of the database assigned for each cluster + */ + private final HashMap clusterDatabases; + + // Retrieve database connection for a cluster + public Connection getConnection(String clusterId) throws SQLException { + return clusterDatabases.get(clusterId).getConnection(); + } + + // Console logger for errors + private final Logger logger; + + // Plugin data folder + private final File dataFolder; + + // Flag variable identifying if the data manager failed to initialize + public boolean hasFailedInitialization = false; + + public DataManager(Logger logger, File dataFolder) { + this.logger = logger; + this.dataFolder = dataFolder; + clusterDatabases = new HashMap<>(); + initializeDatabases(); + } + + private void initializeDatabases() { + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + Database clusterDatabase = switch (Settings.dataStorageType) { + case SQLITE -> new SQLite(cluster, dataFolder, logger); + case MYSQL -> new MySQL(cluster, logger); + }; + clusterDatabase.load(); + clusterDatabase.createTables(); + clusterDatabases.put(cluster.clusterId(), clusterDatabase); + } + + // Abort loading if the database failed to initialize + for (Database database : clusterDatabases.values()) { + if (database.isInactive()) { + hasFailedInitialization = true; + return; + } + } + } + + /** + * Close the database connections + */ + public void closeDatabases() { + for (Database database : clusterDatabases.values()) { + database.close(); + } + } /** * Checks if the player is registered on the database. @@ -26,7 +82,7 @@ public class DataManager { * * @param playerUUID The UUID of the player to register */ - public static void ensurePlayerExists(UUID playerUUID, String playerName) { + public void ensurePlayerExists(UUID playerUUID, String playerName) { for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (!playerExists(playerUUID, cluster)) { createPlayerEntry(playerUUID, playerName, cluster); @@ -42,8 +98,8 @@ public class DataManager { * @param playerUUID The UUID of the player * @return {@code true} if the player is on the player table */ - private static boolean playerExists(UUID playerUUID, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + private boolean playerExists(UUID playerUUID, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "SELECT * FROM " + cluster.playerTableName() + " WHERE `uuid`=?;")) { statement.setString(1, playerUUID.toString()); @@ -51,13 +107,13 @@ public class DataManager { return resultSet.next(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); return false; } } - private static void createPlayerEntry(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + private void createPlayerEntry(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "INSERT INTO " + cluster.playerTableName() + " (`uuid`,`username`) VALUES(?,?);")) { statement.setString(1, playerUUID.toString()); @@ -65,12 +121,12 @@ public class DataManager { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); } } - public static void updatePlayerName(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + public void updatePlayerName(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "UPDATE " + cluster.playerTableName() + " SET `username`=? WHERE `uuid`=?;")) { statement.setString(1, playerName); @@ -78,7 +134,7 @@ public class DataManager { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); } } @@ -88,11 +144,11 @@ public class DataManager { * @param playerName The PlayerName of the data to get * @return Their {@link PlayerData}; or {@code null} if the player does not exist */ - public static PlayerData getPlayerDataByName(String playerName, String clusterId) { + public PlayerData getPlayerDataByName(String playerName, String clusterId) { PlayerData playerData = null; for (Settings.SynchronisationCluster cluster : Settings.clusters) { if (cluster.clusterId().equals(clusterId)) { - try (Connection connection = HuskSyncBungeeCord.getConnection(clusterId)) { + try (Connection connection = getConnection(clusterId)) { try (PreparedStatement statement = connection.prepareStatement( "SELECT * FROM " + cluster.playerTableName() + " WHERE `username`=? LIMIT 1;")) { statement.setString(1, playerName); @@ -110,7 +166,7 @@ public class DataManager { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); } break; } @@ -119,10 +175,10 @@ public class DataManager { return playerData; } - public static Map getPlayerData(UUID playerUUID) { + public Map getPlayerData(UUID playerUUID) { HashMap data = new HashMap<>(); for (Settings.SynchronisationCluster cluster : Settings.clusters) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "SELECT * FROM " + cluster.dataTableName() + " WHERE `player_id`=(SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?);")) { statement.setString(1, playerUUID.toString()); @@ -158,14 +214,14 @@ public class DataManager { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); return null; } } return data; } - public static void updatePlayerData(PlayerData playerData, Settings.SynchronisationCluster cluster) { + public void updatePlayerData(PlayerData playerData, Settings.SynchronisationCluster cluster) { // Ignore if the Spigot server didn't properly sync the previous data // Add the new player data to the cache @@ -179,8 +235,8 @@ public class DataManager { } } - private static void updatePlayerSQLData(PlayerData playerData, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + private void updatePlayerSQLData(PlayerData playerData, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "UPDATE " + cluster.dataTableName() + " SET `version_uuid`=?, `timestamp`=?, `inventory`=?, `ender_chest`=?, `health`=?, `max_health`=?, `health_scale`=?, `hunger`=?, `saturation`=?, `saturation_exhaustion`=?, `selected_slot`=?, `status_effects`=?, `total_experience`=?, `exp_level`=?, `exp_progress`=?, `game_mode`=?, `statistics`=?, `is_flying`=?, `advancements`=?, `location`=? WHERE `player_id`=(SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?);")) { statement.setString(1, playerData.getDataVersionUUID().toString()); @@ -208,12 +264,12 @@ public class DataManager { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); } } - private static void insertPlayerData(PlayerData playerData, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + private void insertPlayerData(PlayerData playerData, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "INSERT INTO " + cluster.dataTableName() + " (`player_id`,`version_uuid`,`timestamp`,`inventory`,`ender_chest`,`health`,`max_health`,`health_scale`,`hunger`,`saturation`,`saturation_exhaustion`,`selected_slot`,`status_effects`,`total_experience`,`exp_level`,`exp_progress`,`game_mode`,`statistics`,`is_flying`,`advancements`,`location`) VALUES((SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) { statement.setString(1, playerData.getPlayerUUID().toString()); @@ -241,7 +297,7 @@ public class DataManager { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); } } @@ -251,8 +307,8 @@ public class DataManager { * @param playerUUID The UUID of the player * @return {@code true} if the player has an entry in the data table */ - private static boolean playerHasCachedData(UUID playerUUID, Settings.SynchronisationCluster cluster) { - try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { + private boolean playerHasCachedData(UUID playerUUID, Settings.SynchronisationCluster cluster) { + try (Connection connection = getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( "SELECT * FROM " + cluster.dataTableName() + " WHERE `player_id`=(SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?);")) { statement.setString(1, playerUUID.toString()); @@ -260,7 +316,7 @@ public class DataManager { return resultSet.next(); } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + logger.log(Level.SEVERE, "An SQL exception occurred", e); return false; } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java b/common/src/main/java/me/william278/husksync/proxy/data/sql/Database.java similarity index 80% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java rename to common/src/main/java/me/william278/husksync/proxy/data/sql/Database.java index 28e1d344..d25f64e2 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java +++ b/common/src/main/java/me/william278/husksync/proxy/data/sql/Database.java @@ -1,21 +1,21 @@ -package me.william278.husksync.bungeecord.data.sql; +package me.william278.husksync.proxy.data.sql; -import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.Settings; +import me.william278.husksync.util.Logger; import java.sql.Connection; import java.sql.SQLException; public abstract class Database { - protected HuskSyncBungeeCord plugin; public String dataPoolName; public Settings.SynchronisationCluster cluster; + public final Logger logger; - public Database(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { - this.plugin = instance; + public Database(Settings.SynchronisationCluster cluster, Logger logger) { this.cluster = cluster; this.dataPoolName = cluster != null ? "HuskSyncHikariPool-" + cluster.clusterId() : "HuskSyncMigratorPool"; + this.logger = logger; } public abstract Connection getConnection() throws SQLException; diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java b/common/src/main/java/me/william278/husksync/proxy/data/sql/MySQL.java similarity index 91% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java rename to common/src/main/java/me/william278/husksync/proxy/data/sql/MySQL.java index 317e3a4d..f5b8530d 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java +++ b/common/src/main/java/me/william278/husksync/proxy/data/sql/MySQL.java @@ -1,8 +1,8 @@ -package me.william278.husksync.bungeecord.data.sql; +package me.william278.husksync.proxy.data.sql; import com.zaxxer.hikari.HikariDataSource; -import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.Settings; +import me.william278.husksync.util.Logger; import java.sql.Connection; import java.sql.SQLException; @@ -58,8 +58,8 @@ public class MySQL extends Database { private HikariDataSource dataSource; - public MySQL(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { - super(instance, cluster); + public MySQL(Settings.SynchronisationCluster cluster, Logger logger) { + super(cluster, logger); } @Override @@ -96,7 +96,7 @@ public class MySQL extends Database { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An error occurred creating tables on the MySQL database: ", e); + logger.log(Level.SEVERE, "An error occurred creating tables on the MySQL database: ", e); } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java b/common/src/main/java/me/william278/husksync/proxy/data/sql/SQLite.java similarity index 82% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java rename to common/src/main/java/me/william278/husksync/proxy/data/sql/SQLite.java index 14dc325b..158f9a30 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java +++ b/common/src/main/java/me/william278/husksync/proxy/data/sql/SQLite.java @@ -1,8 +1,8 @@ -package me.william278.husksync.bungeecord.data.sql; +package me.william278.husksync.proxy.data.sql; import com.zaxxer.hikari.HikariDataSource; -import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.Settings; +import me.william278.husksync.util.Logger; import java.io.File; import java.io.IOException; @@ -54,22 +54,25 @@ public class SQLite extends Database { return cluster.databaseName() + "Data"; } + private final File dataFolder; + private HikariDataSource dataSource; - public SQLite(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { - super(instance, cluster); + public SQLite(Settings.SynchronisationCluster cluster, File dataFolder, Logger logger) { + super(cluster, logger); + this.dataFolder = dataFolder; } // Create the database file if it does not exist yet private void createDatabaseFileIfNotExist() { - File databaseFile = new File(plugin.getDataFolder(), getDatabaseName() + ".db"); + File databaseFile = new File(dataFolder, getDatabaseName() + ".db"); if (!databaseFile.exists()) { try { if (!databaseFile.createNewFile()) { - plugin.getLogger().log(Level.SEVERE, "Failed to write new file: " + getDatabaseName() + ".db (file already exists)"); + logger.log(Level.SEVERE, "Failed to write new file: " + getDatabaseName() + ".db (file already exists)"); } } catch (IOException e) { - plugin.getLogger().log(Level.SEVERE, "An error occurred writing a file: " + getDatabaseName() + ".db (" + e.getCause() + ")"); + logger.log(Level.SEVERE, "An error occurred writing a file: " + getDatabaseName() + ".db (" + e.getCause() + ")", e); } } } @@ -85,7 +88,7 @@ public class SQLite extends Database { createDatabaseFileIfNotExist(); // Create new HikariCP data source - final String jdbcUrl = "jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + getDatabaseName() + ".db"; + final String jdbcUrl = "jdbc:sqlite:" + dataFolder.getAbsolutePath() + File.separator + getDatabaseName() + ".db"; dataSource = new HikariDataSource(); dataSource.setDataSourceClassName("org.sqlite.SQLiteDataSource"); dataSource.addDataSourceProperty("url", jdbcUrl); @@ -109,7 +112,7 @@ public class SQLite extends Database { } } } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An error occurred creating tables on the SQLite database: ", e); + logger.log(Level.SEVERE, "An error occurred creating tables on the SQLite database", e); } } diff --git a/common/src/main/java/me/william278/husksync/util/Logger.java b/common/src/main/java/me/william278/husksync/util/Logger.java new file mode 100644 index 00000000..e6e1db7d --- /dev/null +++ b/common/src/main/java/me/william278/husksync/util/Logger.java @@ -0,0 +1,19 @@ +package me.william278.husksync.util; + +import java.util.logging.Level; + +/** + * Logger interface to allow for implementation of different logger platforms used by Bungee and Velocity + */ +public interface Logger { + + void log(Level level, String message, Exception e); + + void log(Level level, String message); + + void info(String message); + + void severe(String message); + + void config(String message); +} \ No newline at end of file diff --git a/bungeecord/src/main/resources/languages/en-gb.yml b/common/src/main/resources/languages/en-gb.yml similarity index 100% rename from bungeecord/src/main/resources/languages/en-gb.yml rename to common/src/main/resources/languages/en-gb.yml diff --git a/bungeecord/src/main/resources/bungee-config.yml b/common/src/main/resources/proxy-config.yml similarity index 100% rename from bungeecord/src/main/resources/bungee-config.yml rename to common/src/main/resources/proxy-config.yml diff --git a/plugin/build.gradle b/plugin/build.gradle index dd8cb259..042f5d91 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -3,6 +3,7 @@ dependencies { implementation project(path: ":api", configuration: 'shadow') implementation project(path: ":bukkit", configuration: 'shadow') implementation project(path: ":bungeecord", configuration: 'shadow') + implementation project(path: ":velocity", configuration: 'shadow') } shadowJar { diff --git a/settings.gradle b/settings.gradle index c0198879..26d34c30 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,4 +10,5 @@ include 'common' include 'api' include 'bukkit' include 'bungeecord' +include 'velocity' include 'plugin' \ No newline at end of file diff --git a/velocity/build.gradle b/velocity/build.gradle new file mode 100644 index 00000000..d3c2b1ec --- /dev/null +++ b/velocity/build.gradle @@ -0,0 +1,20 @@ +dependencies { + compileOnly project(':common') + implementation project(path: ':common', configuration: 'shadow') + + compileOnly 'redis.clients:jedis:3.7.0' + implementation 'org.bstats:bstats-velocity:2.2.1' + implementation 'com.zaxxer:HikariCP:5.0.0' + implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT' + + compileOnly 'com.velocitypowered:velocity-api:3.1.0' + annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' +} + +shadowJar { + relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' + relocate 'org.bstats', 'me.William278.husksync.libraries.plan' + relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' +} + +tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java new file mode 100644 index 00000000..9983926f --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -0,0 +1,187 @@ +package me.william278.husksync; + +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import me.william278.husksync.proxy.data.DataManager; +import me.william278.husksync.redis.RedisMessage; +import me.william278.husksync.velocity.VelocityUpdateChecker; +import me.william278.husksync.velocity.command.HuskSyncCommand; +import me.william278.husksync.velocity.config.ConfigLoader; +import me.william278.husksync.velocity.config.ConfigManager; +import me.william278.husksync.velocity.listener.VelocityEventListener; +import me.william278.husksync.velocity.listener.VelocityRedisListener; +import me.william278.husksync.velocity.util.VelocityLogger; +import org.bstats.velocity.Metrics; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Objects; +import java.util.logging.Level; + +import static me.william278.husksync.HuskSyncVelocity.VERSION; + +@Plugin( + id = "velocity", + name = "HuskSync", + version = VERSION, + description = "HuskSync for velocity", + authors = {"William278"} +) +public class HuskSyncVelocity { + + // Plugin version + public static final String VERSION = "1.2-dev"; + + // Velocity bStats ID (different from Bukkit and BungeeCord) + private static final int METRICS_ID = 13489; + private final Metrics.Factory metricsFactory; + + private static HuskSyncVelocity instance; + + public static HuskSyncVelocity getInstance() { + return instance; + } + + // Whether the plugin is ready to accept redis messages + public static boolean readyForRedis = false; + + // Whether the plugin is in the process of disabling and should skip responding to handshake confirmations + public static boolean isDisabling = false; + + /** + * Set of all the {@link Server}s that have completed the synchronisation handshake with HuskSync on the proxy + */ + public static HashSet synchronisedServers; + + public static DataManager dataManager; + + //public static MPDBMigrator mpdbMigrator; + + private final Logger logger; + private final ProxyServer server; + private final Path dataDirectory; + + // Get the data folder + public File getDataFolder() { + return dataDirectory.toFile(); + } + + // Get the proxy server + public ProxyServer getProxyServer() { + return server; + } + + // Velocity logger handling + private VelocityLogger velocityLogger; + + public VelocityLogger getVelocityLogger() { + return velocityLogger; + } + + @Inject + public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + this.metricsFactory = metricsFactory; + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + // Set instance + instance = this; + + // Setup logger + velocityLogger = new VelocityLogger(logger); + + // Load config + ConfigManager.loadConfig(); + + // Load settings from config + ConfigLoader.loadSettings(ConfigManager.getConfig()); + + // Load messages + ConfigManager.loadMessages(); + + // Load locales from messages + ConfigLoader.loadMessageStrings(ConfigManager.getMessages()); + + // Do update checker + if (Settings.automaticUpdateChecks) { + new VelocityUpdateChecker(VERSION).logToConsole(); + } + + // Setup data manager + dataManager = new DataManager(getVelocityLogger(), getDataFolder()); + + // Setup player data cache + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + dataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache()); + } + + // Initialize the redis listener + if (!new VelocityRedisListener().isActiveAndEnabled) { + getVelocityLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (Velocity) v" + VERSION); + return; + } + + // Register listener + server.getEventManager().register(this, new VelocityEventListener()); + + // Register command + CommandManager commandManager = getProxyServer().getCommandManager(); + CommandMeta meta = commandManager.metaBuilder("husksync") + .aliases("hs") + .build(); + commandManager.register(meta, new HuskSyncCommand()); + + // Prepare the migrator for use if needed + //todo migrator + + // Initialize bStats metrics + try { + metricsFactory.make(this, METRICS_ID); + } catch (Exception e) { + getVelocityLogger().info("Skipped metrics initialization"); + } + + // Log to console + getVelocityLogger().info("Enabled HuskSync (Velocity) v" + VERSION); + + // Mark as ready for redis message processing + readyForRedis = true; + } + + @Subscribe + public void onProxyShutdown(ProxyShutdownEvent event) { + // Plugin shutdown logic + isDisabling = true; + + // Send terminating handshake message + for (Server server : synchronisedServers) { + try { + new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, server.clusterId()), + server.serverUUID().toString(), + "Velocity").send(); + } catch (IOException e) { + getVelocityLogger().log(Level.SEVERE, "Failed to serialize Redis message for handshake termination", e); + } + } + + dataManager.closeDatabases(); + + // Log to console + getVelocityLogger().info("Disabled HuskSync (Velocity) v" + VERSION); + } +} diff --git a/velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java b/velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java new file mode 100644 index 00000000..2e8992c1 --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java @@ -0,0 +1,20 @@ +package me.william278.husksync.velocity; + +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.util.UpdateChecker; + +import java.util.logging.Level; + +public class VelocityUpdateChecker extends UpdateChecker { + + private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); + + public VelocityUpdateChecker(String versionToCheck) { + super(versionToCheck); + } + + @Override + public void log(Level level, String message) { + plugin.getVelocityLogger().log(level, message); + } +} diff --git a/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java b/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java new file mode 100644 index 00000000..1e21df94 --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java @@ -0,0 +1,34 @@ +package me.william278.husksync.velocity.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class HuskSyncCommand implements SimpleCommand { + + /** + * Executes the command for the specified invocation. + * + * @param invocation the invocation context + */ + @Override + public void execute(Invocation invocation) { + final String[] args = invocation.arguments(); + final CommandSource source = invocation.source(); + + } + + /** + * Provides tab complete suggestions for the specified invocation. + * + * @param invocation the invocation context + * @return the tab complete suggestions + */ + @Override + public List suggest(Invocation invocation) { + return new ArrayList<>(); + } +} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java new file mode 100644 index 00000000..7649ab15 --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java @@ -0,0 +1,97 @@ +package me.william278.husksync.velocity.config; + +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.Settings; +import me.william278.husksync.util.MessageManager; +import ninja.leaping.configurate.ConfigurationNode; + +import java.util.HashMap; + +public class ConfigLoader { + + private static ConfigurationNode copyDefaults(ConfigurationNode configRoot) { + // Get the config version and update if needed + String configVersion = getConfigString(configRoot, "1.0", "config_file_version"); + if (configVersion.contains("-dev")) { + configVersion = configVersion.replaceAll("-dev", ""); + } + if (!configVersion.equals(HuskSyncVelocity.VERSION)) { + if (configVersion.equalsIgnoreCase("1.0")) { + configRoot.getNode("check_for_updates").setValue(true); + } + if (configVersion.equalsIgnoreCase("1.0") || configVersion.equalsIgnoreCase("1.0.1") || configVersion.equalsIgnoreCase("1.0.2") || configVersion.equalsIgnoreCase("1.0.3")) { + configRoot.getNode("clusters.main.player_table").setValue("husksync_players"); + configRoot.getNode("clusters.main.data_table").setValue("husksync_data"); + } + configRoot.getNode("config_file_version").setValue(HuskSyncVelocity.VERSION); + } + // Save the config back + ConfigManager.saveConfig(configRoot); + return configRoot; + } + + private static String getConfigString(ConfigurationNode rootNode, String defaultValue, String... nodePath) { + return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getString() : defaultValue; + } + + private static boolean getConfigBoolean(ConfigurationNode rootNode, boolean defaultValue, String... nodePath) { + return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getBoolean() : defaultValue; + } + + private static int getConfigInt(ConfigurationNode rootNode, int defaultValue, String... nodePath) { + return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getInt() : defaultValue; + } + + private static long getConfigLong(ConfigurationNode rootNode, long defaultValue, String... nodePath) { + return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getLong() : defaultValue; + } + + public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException { + ConfigurationNode config = copyDefaults(loadedConfig); + + Settings.language = getConfigString(config, "en-gb", "language"); + + Settings.serverType = Settings.ServerType.PROXY; + Settings.automaticUpdateChecks = getConfigBoolean(config, true, "check_for_updates"); + Settings.redisHost = getConfigString(config, "localhost", "redis_settings", "host"); + Settings.redisPort = getConfigInt(config, 6379, "redis_settings", "port"); + Settings.redisPassword = getConfigString(config, "", "redis_settings", "password"); + + Settings.dataStorageType = Settings.DataStorageType.valueOf(getConfigString(config, "sqlite", "data_storage_settings", "database_type").toUpperCase()); + if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) { + Settings.mySQLHost = getConfigString(config, "localhost", "data_storage_settings", "mysql_settings", "host"); + Settings.mySQLPort = getConfigInt(config, 3306, "data_storage_settings", "mysql_settings", "port"); + Settings.mySQLDatabase = getConfigString(config, "HuskSync", "data_storage_settings", "mysql_settings", "database"); + Settings.mySQLUsername = getConfigString(config, "root", "data_storage_settings", "mysql_settings", "username"); + Settings.mySQLPassword = getConfigString(config, "pa55w0rd", "data_storage_settings", "mysql_settings", "password"); + Settings.mySQLParams = getConfigString(config, "?autoReconnect=true&useSSL=false", "data_storage_settings", "mysql_settings", "params"); + } + + Settings.hikariMaximumPoolSize = getConfigInt(config, 10, "data_storage_settings", "hikari_pool_settings", "maximum_pool_size"); + Settings.hikariMinimumIdle = getConfigInt(config, 10, "data_storage_settings", "hikari_pool_settings", "minimum_idle"); + Settings.hikariMaximumLifetime = getConfigLong(config, 1800000, "data_storage_settings", "hikari_pool_settings", "maximum_lifetime"); + Settings.hikariKeepAliveTime = getConfigLong(config, 0, "data_storage_settings", "hikari_pool_settings", "keepalive_time"); + Settings.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout"); + + // Read cluster data + ConfigurationNode clusterSection = config.getNode("clusters"); + final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; + for (ConfigurationNode cluster : clusterSection.getChildrenList()) { + final String clusterId = (String) cluster.getKey(); + final String playerTableName = getConfigString(config, "husksync_players", "clusters", clusterId, "player_table"); + final String dataTableName = getConfigString(config, "husksync_data", "clusters", clusterId, "data_table"); + final String databaseName = getConfigString(config, settingDatabaseName, "clusters", clusterId, "database"); + Settings.clusters.add(new Settings.SynchronisationCluster(clusterId, databaseName, playerTableName, dataTableName)); + } + } + + public static void loadMessageStrings(ConfigurationNode config) { + final HashMap messages = new HashMap<>(); + for (ConfigurationNode message : config.getChildrenList()) { + final String messageId = (String) message.getKey(); + messages.put(messageId, getConfigString(config, "", messageId)); + } + MessageManager.setMessages(messages); + } + +} diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java new file mode 100644 index 00000000..940df485 --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java @@ -0,0 +1,92 @@ +package me.william278.husksync.velocity.config; + +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.Settings; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Objects; +import java.util.logging.Level; + +public class ConfigManager { + + private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); + + public static void loadConfig() { + try { + if (!plugin.getDataFolder().exists()) { + if (plugin.getDataFolder().mkdir()) { + plugin.getVelocityLogger().info("Created HuskSync data folder"); + } + } + File configFile = new File(plugin.getDataFolder(), "config.yml"); + if (!configFile.exists()) { + Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("proxy-config.yml")), configFile.toPath()); + plugin.getVelocityLogger().info("Created HuskSync config file"); + } + } catch (Exception e) { + plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); + } + } + + public static void saveConfig(ConfigurationNode rootNode) { + try { + getConfigLoader().save(rootNode); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e); + } + } + + public static void loadMessages() { + try { + if (!plugin.getDataFolder().exists()) { + if (plugin.getDataFolder().mkdir()) { + plugin.getVelocityLogger().info("Created HuskSync data folder"); + } + } + File messagesFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); + if (!messagesFile.exists()) { + Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("languages/" + Settings.language + ".yml")), + messagesFile.toPath()); + plugin.getVelocityLogger().info("Created HuskSync messages file"); + } + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the messages file", e); + } + } + + private static YAMLConfigurationLoader getConfigLoader() { + File configFile = new File(plugin.getDataFolder(), "config.yml"); + return YAMLConfigurationLoader.builder() + .setPath(configFile.toPath()) + .build(); + } + + public static ConfigurationNode getConfig() { + try { + return getConfigLoader().load(); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.CONFIG, "An IOException has occurred loading the plugin config."); + return null; + } + } + + public static ConfigurationNode getMessages() { + try { + File configFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); + return YAMLConfigurationLoader.builder() + .setPath(configFile.toPath()) + .build() + .load(); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.CONFIG, "An IOException has occurred loading the plugin messages."); + return null; + } + } + +} + diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java new file mode 100644 index 00000000..116fd71f --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java @@ -0,0 +1,46 @@ +package me.william278.husksync.velocity.listener; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.proxy.Player; +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.PlayerData; +import me.william278.husksync.Settings; +import me.william278.husksync.redis.RedisMessage; + +import java.io.IOException; +import java.util.Map; +import java.util.logging.Level; + +public class VelocityEventListener { + + private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); + + @Subscribe + public void onPostLogin(PostLoginEvent event) { + final Player player = event.getPlayer(); + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { + // Ensure the player has data on SQL and that it is up-to-date + HuskSyncVelocity.dataManager.ensurePlayerExists(player.getUniqueId(), player.getUsername()); + + // Get the player's data from SQL + final Map data = HuskSyncVelocity.dataManager.getPlayerData(player.getUniqueId()); + + // Update the player's data from SQL onto the cache + assert data != null; + for (Settings.SynchronisationCluster cluster : data.keySet()) { + HuskSyncVelocity.dataManager.playerDataCache.get(cluster).updatePlayer(data.get(cluster)); + } + + // Send a message asking the bukkit to request data on join + try { + new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null), + RedisMessage.RequestOnJoinUpdateType.ADD_REQUESTER.toString(), player.getUniqueId().toString()).send(); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize request data on join message data"); + e.printStackTrace(); + } + }); + } +} diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java new file mode 100644 index 00000000..616abbee --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java @@ -0,0 +1,203 @@ +package me.william278.husksync.velocity.listener; + +import com.velocitypowered.api.proxy.Player; +import de.themoep.minedown.adventure.MineDown; +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.PlayerData; +import me.william278.husksync.Server; +import me.william278.husksync.Settings; +import me.william278.husksync.redis.RedisListener; +import me.william278.husksync.redis.RedisMessage; +import me.william278.husksync.util.MessageManager; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.logging.Level; + +public class VelocityRedisListener extends RedisListener { + + private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); + + // Initialize the listener on the bungee + public VelocityRedisListener() { + listen(); + } + + private PlayerData getPlayerCachedData(UUID uuid, String clusterId) { + PlayerData data = null; + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (cluster.clusterId().equals(clusterId)) { + // Get the player data from the cache + PlayerData cachedData = HuskSyncVelocity.dataManager.playerDataCache.get(cluster).getPlayer(uuid); + if (cachedData != null) { + return cachedData; + } + + data = Objects.requireNonNull(HuskSyncVelocity.dataManager.getPlayerData(uuid)).get(cluster); // Get their player data from MySQL + HuskSyncVelocity.dataManager.playerDataCache.get(cluster).updatePlayer(data); // Update the cache + break; + } + } + return data; // Return the data + } + + /** + * Handle an incoming {@link RedisMessage} + * + * @param message The {@link RedisMessage} to handle + */ + @Override + public void handleMessage(RedisMessage message) { + // Ignore messages destined for Bukkit servers + if (message.getMessageTarget().targetServerType() != Settings.ServerType.PROXY) { + return; + } + // Only process redis messages when ready + if (!HuskSyncVelocity.readyForRedis) { + return; + } + + switch (message.getMessageType()) { + case PLAYER_DATA_REQUEST -> { + // Get the UUID of the requesting player + final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData()); + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { + try { + // Send the reply, serializing the message data + new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()), + RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId()))) + .send(); + + // Send an update to all bukkit servers removing the player from the requester cache + new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) + .send(); + + // Send synchronisation complete message + Optional player = plugin.getProxyServer().getPlayer(requestingPlayerUUID); + player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent())); + } catch (IOException e) { + log(Level.SEVERE, "Failed to serialize data when replying to a data request"); + e.printStackTrace(); + } + }); + } + case PLAYER_DATA_UPDATE -> { + // Deserialize the PlayerData received + PlayerData playerData; + final String serializedPlayerData = message.getMessageData(); + try { + playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData); + } catch (IOException | ClassNotFoundException e) { + log(Level.SEVERE, "Failed to deserialize PlayerData when handling a player update request"); + e.printStackTrace(); + return; + } + + // Update the data in the cache and SQL + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (cluster.clusterId().equals(message.getMessageTarget().targetClusterId())) { + HuskSyncVelocity.dataManager.updatePlayerData(playerData, cluster); + break; + } + } + + // Reply with the player data if they are still online (switching server) + Optional updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID()); + updatingPlayer.ifPresent(player -> { + try { + new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), + RedisMessage.serialize(playerData)) + .send(); + + // Send synchronisation complete message + player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()); + } catch (IOException e) { + log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request"); + e.printStackTrace(); + } + }); + + } + case CONNECTION_HANDSHAKE -> { + // Reply to a Bukkit server's connection handshake to complete the process + if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling + final UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]); + final boolean hasMySqlPlayerDataBridge = Boolean.parseBoolean(message.getMessageDataElements()[1]); + final String bukkitBrand = message.getMessageDataElements()[2]; + final String huskSyncVersion = message.getMessageDataElements()[3]; + try { + new RedisMessage(RedisMessage.MessageType.CONNECTION_HANDSHAKE, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), + serverUUID.toString(), "Velocity") + .send(); + HuskSyncVelocity.synchronisedServers.add( + new Server(serverUUID, hasMySqlPlayerDataBridge, + huskSyncVersion, bukkitBrand, message.getMessageTarget().targetClusterId())); + log(Level.INFO, "Completed handshake with " + bukkitBrand + " server (" + serverUUID + ")"); + } catch (IOException e) { + log(Level.SEVERE, "Failed to serialize handshake message data"); + e.printStackTrace(); + } + } + case TERMINATE_HANDSHAKE -> { + // Terminate the handshake with a Bukkit server + final UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]); + final String bukkitBrand = message.getMessageDataElements()[1]; + + // Remove a server from the synchronised server list + Server serverToRemove = null; + for (Server server : HuskSyncVelocity.synchronisedServers) { + if (server.serverUUID().equals(serverUUID)) { + serverToRemove = server; + break; + } + } + HuskSyncVelocity.synchronisedServers.remove(serverToRemove); + log(Level.INFO, "Terminated the handshake with " + bukkitBrand + " server (" + serverUUID + ")"); + } + case DECODED_MPDB_DATA_SET -> { + // Deserialize the PlayerData received + PlayerData playerData; + final String serializedPlayerData = message.getMessageDataElements()[0]; + final String playerName = message.getMessageDataElements()[1]; + try { + playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData); + } catch (IOException | ClassNotFoundException e) { + log(Level.SEVERE, "Failed to deserialize PlayerData when handling incoming decoded MPDB data"); + e.printStackTrace(); + return; + } + + //todo Migrator + /*// Add the incoming data to the data to be saved + MPDBMigrator.incomingPlayerData.put(playerData, playerName); + + // Increment players migrated + MPDBMigrator.playersMigrated++; + plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); + + // When all the data has been received, save it + if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) { + MPDBMigrator.loadIncomingData(MPDBMigrator.incomingPlayerData); + }*/ + } + } + } + + /** + * Log to console + * + * @param level The {@link Level} to log + * @param message Message to log + */ + @Override + public void log(Level level, String message) { + plugin.getVelocityLogger().log(level, message); + } +} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/velocity/util/VelocityLogger.java b/velocity/src/main/java/me/william278/husksync/velocity/util/VelocityLogger.java new file mode 100644 index 00000000..05e364ff --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/util/VelocityLogger.java @@ -0,0 +1,44 @@ +package me.william278.husksync.velocity.util; + +import me.william278.husksync.util.Logger; + +import java.util.logging.Level; + +public record VelocityLogger(org.slf4j.Logger parent) implements Logger { + + @Override + public void log(Level level, String message, Exception e) { + logMessage(level, message); + e.printStackTrace(); + } + + @Override + public void log(Level level, String message) { + logMessage(level, message); + } + + @Override + public void info(String message) { + logMessage(Level.INFO, message); + } + + @Override + public void severe(String message) { + logMessage(Level.SEVERE, message); + } + + @Override + public void config(String message) { + logMessage(Level.CONFIG, message); + } + + // Logs the message using SLF4J + private void logMessage(Level level, String message) { + switch (level.intValue()) { + case 1000 -> parent.error(message); // Severe + case 900 -> parent.warn(message); // Warning + case 70 -> parent.warn("[Config] " + message); + default -> parent.info(message); + } + } +} \ No newline at end of file From e7822baa99ad10f310ef0ae912a9fc82f33c9fe1 Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 00:12:43 +0000 Subject: [PATCH 02/11] Fix varargs call warning --- .../husksync/velocity/config/ConfigLoader.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java index 7649ab15..4d9fee9b 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java @@ -31,19 +31,20 @@ public class ConfigLoader { } private static String getConfigString(ConfigurationNode rootNode, String defaultValue, String... nodePath) { - return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getString() : defaultValue; + return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getString() : defaultValue; } + @SuppressWarnings("SameParameterValue") private static boolean getConfigBoolean(ConfigurationNode rootNode, boolean defaultValue, String... nodePath) { - return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getBoolean() : defaultValue; + return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getBoolean() : defaultValue; } private static int getConfigInt(ConfigurationNode rootNode, int defaultValue, String... nodePath) { - return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getInt() : defaultValue; + return !rootNode.getNode((Object[]) nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getInt() : defaultValue; } private static long getConfigLong(ConfigurationNode rootNode, long defaultValue, String... nodePath) { - return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getLong() : defaultValue; + return !rootNode.getNode((Object[])nodePath).isVirtual() ? rootNode.getNode((Object[])nodePath).getLong() : defaultValue; } public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException { From 635fefba3d13e687cccab0fe427c2f46fc159b1f Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 00:53:35 +0000 Subject: [PATCH 03/11] Add command and migrator --- .../husksync/HuskSyncBungeeCord.java | 8 +- ...uskSyncCommand.java => BungeeCommand.java} | 87 ++-- .../listener/BungeeRedisListener.java | 16 +- .../husksync}/migrator/MPDBMigrator.java | 173 ++++---- .../proxy/command/HuskSyncCommand.java | 17 + .../william278/husksync/HuskSyncVelocity.java | 13 +- .../velocity/command/HuskSyncCommand.java | 34 -- .../velocity/command/VelocityCommand.java | 418 ++++++++++++++++++ .../velocity/config/ConfigManager.java | 1 - 9 files changed, 586 insertions(+), 181 deletions(-) rename bungeecord/src/main/java/me/william278/husksync/bungeecord/command/{HuskSyncCommand.java => BungeeCommand.java} (88%) rename {bungeecord/src/main/java/me/william278/husksync/bungeecord => common/src/main/java/me/william278/husksync}/migrator/MPDBMigrator.java (56%) create mode 100644 common/src/main/java/me/william278/husksync/proxy/command/HuskSyncCommand.java delete mode 100644 velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java create mode 100644 velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java diff --git a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java index 8f6f2f50..9aa7e1bc 100644 --- a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java +++ b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java @@ -1,12 +1,12 @@ package me.william278.husksync; -import me.william278.husksync.bungeecord.command.HuskSyncCommand; +import me.william278.husksync.bungeecord.command.BungeeCommand; import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigManager; import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.bungeecord.listener.BungeeEventListener; import me.william278.husksync.bungeecord.listener.BungeeRedisListener; -import me.william278.husksync.bungeecord.migrator.MPDBMigrator; +import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.bungeecord.util.BungeeLogger; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.redis.RedisMessage; @@ -104,10 +104,10 @@ public final class HuskSyncBungeeCord extends Plugin { getProxy().getPluginManager().registerListener(this, new BungeeEventListener()); // Register command - getProxy().getPluginManager().registerCommand(this, new HuskSyncCommand()); + getProxy().getPluginManager().registerCommand(this, new BungeeCommand()); // Prepare the migrator for use if needed - mpdbMigrator = new MPDBMigrator(); + mpdbMigrator = new MPDBMigrator(getBungeeLogger()); // Initialize bStats metrics try { diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/BungeeCommand.java similarity index 88% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java rename to bungeecord/src/main/java/me/william278/husksync/bungeecord/command/BungeeCommand.java index 8fc3e823..63dcce89 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/HuskSyncCommand.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/command/BungeeCommand.java @@ -4,12 +4,13 @@ import de.themoep.minedown.MineDown; import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.Server; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; +import me.william278.husksync.proxy.command.HuskSyncCommand; import me.william278.husksync.util.MessageManager; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigManager; -import me.william278.husksync.bungeecord.migrator.MPDBMigrator; +import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; @@ -25,17 +26,11 @@ import java.util.Objects; import java.util.logging.Level; import java.util.stream.Collectors; -public class HuskSyncCommand extends Command implements TabExecutor { +public class BungeeCommand extends Command implements TabExecutor, HuskSyncCommand { private final static HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); - private final static SubCommand[] SUB_COMMANDS = {new SubCommand("about", null), - new SubCommand("status", "husksync.command.admin"), - new SubCommand("reload", "husksync.command.admin"), - new SubCommand("update", "husksync.command.admin"), - new SubCommand("invsee", "husksync.command.inventory"), - new SubCommand("echest", "husksync.command.ender_chest")}; - public HuskSyncCommand() { + public BungeeCommand() { super("husksync", null, "hs"); } @@ -195,6 +190,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { // Database migration wizard if (args.length >= 1) { if (args[0].equalsIgnoreCase("migrate")) { + MPDBMigrator migrator = HuskSyncBungeeCord.mpdbMigrator; if (args.length == 1) { sender.sendMessage(new MineDown( """ @@ -210,7 +206,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { Other non-vital data, such as current health, hunger & potion effects will not be migrated to ensure that migration does not take an excessive amount of time. - + To do this, you need to have MySqlPlayerDataBridge and HuskSync installed on one Spigot server as well as HuskSync installed on the proxy (which you have) @@ -234,7 +230,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { sourceInventoryTableName: %6% sourceEnderChestTableName: %7% sourceExperienceTableName: %8% - + targetCluster: %9% To change a setting, type: @@ -256,37 +252,37 @@ public class HuskSyncCommand extends Command implements TabExecutor { data from the source MySQLPlayerDataBridge database. >When done, type: husksync migrate start""" - .replaceAll("%1%", MPDBMigrator.migrationSettings.sourceHost) - .replaceAll("%2%", String.valueOf(MPDBMigrator.migrationSettings.sourcePort)) - .replaceAll("%3%", MPDBMigrator.migrationSettings.sourceDatabase) - .replaceAll("%4%", MPDBMigrator.migrationSettings.sourceUsername) - .replaceAll("%5%", MPDBMigrator.migrationSettings.sourcePassword) - .replaceAll("%6%", MPDBMigrator.migrationSettings.inventoryDataTable) - .replaceAll("%7%", MPDBMigrator.migrationSettings.enderChestDataTable) - .replaceAll("%8%", MPDBMigrator.migrationSettings.expDataTable) - .replaceAll("%9%", MPDBMigrator.migrationSettings.targetCluster) + .replaceAll("%1%", migrator.migrationSettings.sourceHost) + .replaceAll("%2%", String.valueOf(migrator.migrationSettings.sourcePort)) + .replaceAll("%3%", migrator.migrationSettings.sourceDatabase) + .replaceAll("%4%", migrator.migrationSettings.sourceUsername) + .replaceAll("%5%", migrator.migrationSettings.sourcePassword) + .replaceAll("%6%", migrator.migrationSettings.inventoryDataTable) + .replaceAll("%7%", migrator.migrationSettings.enderChestDataTable) + .replaceAll("%8%", migrator.migrationSettings.expDataTable) + .replaceAll("%9%", migrator.migrationSettings.targetCluster) .replaceAll("%10%", Settings.dataStorageType.toString()) ).toComponent()); case "setting" -> { if (args.length == 4) { String value = args[3]; switch (args[2]) { - case "sourceHost", "host" -> MPDBMigrator.migrationSettings.sourceHost = value; + case "sourceHost", "host" -> migrator.migrationSettings.sourceHost = value; case "sourcePort", "port" -> { try { - MPDBMigrator.migrationSettings.sourcePort = Integer.parseInt(value); + migrator.migrationSettings.sourcePort = Integer.parseInt(value); } catch (NumberFormatException e) { sender.sendMessage(new MineDown("Error: Invalid value; port must be a number").toComponent()); return; } } - case "sourceDatabase", "database" -> MPDBMigrator.migrationSettings.sourceDatabase = value; - case "sourceUsername", "username" -> MPDBMigrator.migrationSettings.sourceUsername = value; - case "sourcePassword", "password" -> MPDBMigrator.migrationSettings.sourcePassword = value; - case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> MPDBMigrator.migrationSettings.inventoryDataTable = value; - case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> MPDBMigrator.migrationSettings.enderChestDataTable = value; - case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> MPDBMigrator.migrationSettings.expDataTable = value; - case "targetCluster", "cluster" -> MPDBMigrator.migrationSettings.targetCluster = value; + case "sourceDatabase", "database" -> migrator.migrationSettings.sourceDatabase = value; + case "sourceUsername", "username" -> migrator.migrationSettings.sourceUsername = value; + case "sourcePassword", "password" -> migrator.migrationSettings.sourcePassword = value; + case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> migrator.migrationSettings.inventoryDataTable = value; + case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> migrator.migrationSettings.enderChestDataTable = value; + case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> migrator.migrationSettings.expDataTable = value; + case "targetCluster", "cluster" -> migrator.migrationSettings.targetCluster = value; default -> { sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent()); return; @@ -299,7 +295,14 @@ public class HuskSyncCommand extends Command implements TabExecutor { } case "start" -> { sender.sendMessage(new MineDown("Starting MySQLPlayerDataBridge migration!...").toComponent()); - HuskSyncBungeeCord.mpdbMigrator.start(); + + // If the migrator is ready, execute the migration asynchronously + if (HuskSyncBungeeCord.mpdbMigrator.readyToMigrate(ProxyServer.getInstance().getOnlineCount(), + HuskSyncBungeeCord.synchronisedServers)) { + ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> + HuskSyncBungeeCord.mpdbMigrator.executeMigrationOperations(HuskSyncBungeeCord.dataManager, + HuskSyncBungeeCord.synchronisedServers)); + } } default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent()); } @@ -346,7 +349,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { } // View the ender chest of a player specified by their name - private void openEnderChest(ProxiedPlayer viewer, String targetPlayerName, String clusterId) { + public void openEnderChest(ProxiedPlayer viewer, String targetPlayerName, String clusterId) { if (viewer.getName().equalsIgnoreCase(targetPlayerName)) { viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent()); return; @@ -401,9 +404,12 @@ public class HuskSyncCommand extends Command implements TabExecutor { if (args.length == 1) { final ArrayList subCommands = new ArrayList<>(); for (SubCommand subCommand : SUB_COMMANDS) { - if (subCommand.doesPlayerHavePermission(player)) { - subCommands.add(subCommand.command()); + if (subCommand.permission() != null) { + if (!player.hasPermission(subCommand.permission())) { + continue; + } } + subCommands.add(subCommand.command()); } // Automatically filter the sub commands' order in tab completion by what the player has typed return subCommands.stream().filter(val -> val.startsWith(args[0])) @@ -415,19 +421,4 @@ public class HuskSyncCommand extends Command implements TabExecutor { return Collections.emptyList(); } - /** - * A sub command, that may require a permission - */ - public record SubCommand(String command, String permission) { - /** - * Returns if the player can use the sub command - * - * @param player The {@link ProxiedPlayer} to check - * @return {@code true} if the player can use the sub command; {@code false} otherwise - */ - public boolean doesPlayerHavePermission(ProxiedPlayer player) { - return permission == null || player.hasPermission(permission); - } - } - } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java index 1e47ac96..270e3ce7 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/listener/BungeeRedisListener.java @@ -6,7 +6,7 @@ import me.william278.husksync.Server; import me.william278.husksync.util.MessageManager; import me.william278.husksync.PlayerData; import me.william278.husksync.Settings; -import me.william278.husksync.bungeecord.migrator.MPDBMigrator; +import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisMessage; import net.md_5.bungee.api.ChatMessageType; @@ -181,16 +181,20 @@ public class BungeeRedisListener extends RedisListener { return; } + // Get the migrator + MPDBMigrator migrator = HuskSyncBungeeCord.mpdbMigrator; + // Add the incoming data to the data to be saved - MPDBMigrator.incomingPlayerData.put(playerData, playerName); + migrator.incomingPlayerData.put(playerData, playerName); // Increment players migrated - MPDBMigrator.playersMigrated++; - plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); + migrator.playersMigrated++; + plugin.getBungeeLogger().log(Level.INFO, "Migrated " + migrator.playersMigrated + "/" + migrator.migratedDataSent + " players."); // When all the data has been received, save it - if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) { - MPDBMigrator.loadIncomingData(MPDBMigrator.incomingPlayerData); + if (migrator.migratedDataSent == migrator.playersMigrated) { + ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> migrator.loadIncomingData(migrator.incomingPlayerData, + HuskSyncBungeeCord.dataManager)); } } } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java b/common/src/main/java/me/william278/husksync/migrator/MPDBMigrator.java similarity index 56% rename from bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java rename to common/src/main/java/me/william278/husksync/migrator/MPDBMigrator.java index 2f355d8e..6c6b2b79 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/migrator/MPDBMigrator.java +++ b/common/src/main/java/me/william278/husksync/migrator/MPDBMigrator.java @@ -1,14 +1,13 @@ -package me.william278.husksync.bungeecord.migrator; +package me.william278.husksync.migrator; -import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.PlayerData; import me.william278.husksync.Server; import me.william278.husksync.Settings; -import me.william278.husksync.migrator.MPDBPlayerData; +import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.proxy.data.sql.Database; import me.william278.husksync.proxy.data.sql.MySQL; import me.william278.husksync.redis.RedisMessage; -import net.md_5.bungee.api.ProxyServer; +import me.william278.husksync.util.Logger; import java.io.IOException; import java.sql.Connection; @@ -28,36 +27,40 @@ import java.util.logging.Level; */ public class MPDBMigrator { - public static int migratedDataSent = 0; - public static int playersMigrated = 0; + public int migratedDataSent = 0; + public int playersMigrated = 0; - private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); + public HashMap incomingPlayerData; - public static HashMap incomingPlayerData; + public MigrationSettings migrationSettings = new MigrationSettings(); + private Settings.SynchronisationCluster targetCluster; + private Database sourceDatabase; - public static MigrationSettings migrationSettings = new MigrationSettings(); - private static Settings.SynchronisationCluster targetCluster; - private static Database sourceDatabase; + private HashSet mpdbPlayerData; - private static HashSet mpdbPlayerData; + private final Logger logger; - public void start() { - if (ProxyServer.getInstance().getPlayers().size() > 0) { - plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because there are players online. " + + public MPDBMigrator(Logger logger) { + this.logger = logger; + } + + public boolean readyToMigrate(int networkPlayerCount, HashSet synchronisedServers) { + if (networkPlayerCount > 0) { + logger.log(Level.WARNING, "Failed to start migration because there are players online. " + "Your network has to be empty to migrate data for safety reasons."); - return; + return false; } int synchronisedServersWithMpdb = 0; - for (Server server : HuskSyncBungeeCord.synchronisedServers) { + for (Server server : synchronisedServers) { if (server.hasMySqlPlayerDataBridge()) { synchronisedServersWithMpdb++; } } if (synchronisedServersWithMpdb < 1) { - plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " + + logger.log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " + "Please start one Spigot server with HuskSync installed to begin migration."); - return; + return false; } for (Settings.SynchronisationCluster cluster : Settings.clusters) { @@ -67,9 +70,9 @@ public class MPDBMigrator { } } if (targetCluster == null) { - plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " + - "Please ensure the target cluster is correct, configured in the proxy config file, then try again"); - return; + logger.log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " + + "Please ensure the target cluster is correct, configured in the proxy config file, then try again"); + return false; } migratedDataSent = 0; @@ -79,32 +82,40 @@ public class MPDBMigrator { final MigrationSettings settings = migrationSettings; // Get connection to source database - sourceDatabase = new MigratorMySQL(plugin, settings.sourceHost, settings.sourcePort, + sourceDatabase = new MigratorMySQL(logger, settings.sourceHost, settings.sourcePort, settings.sourceDatabase, settings.sourceUsername, settings.sourcePassword, targetCluster); sourceDatabase.load(); if (sourceDatabase.isInactive()) { - plugin.getBungeeLogger().log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " + + logger.log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " + "Please check you have input the correct connection details and try again."); - return; + return false; } - ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - prepareTargetDatabase(); + return true; + } - getInventoryData(); + // Carry out the migration + public void executeMigrationOperations(DataManager dataManager, HashSet synchronisedServers) { + // Prepare the target database for insertion + prepareTargetDatabase(dataManager); - getEnderChestData(); + // Fetch inventory data from MPDB + getInventoryData(); - getExperienceData(); + // Fetch ender chest data from MPDB + getEnderChestData(); - sendEncodedData(); - }); + // Fetch experience data from MPDB + getExperienceData(); + + // Send the encoded data to the Bukkit servers for conversion + sendEncodedData(synchronisedServers); } // Clear the new database out of current data - private void prepareTargetDatabase() { - plugin.getBungeeLogger().log(Level.INFO, "Preparing target database..."); - try (Connection connection = HuskSyncBungeeCord.dataManager.getConnection(targetCluster.clusterId())) { + private void prepareTargetDatabase(DataManager dataManager) { + logger.log(Level.INFO, "Preparing target database..."); + try (Connection connection = dataManager.getConnection(targetCluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) { statement.executeUpdate(); } @@ -112,14 +123,14 @@ public class MPDBMigrator { statement.executeUpdate(); } } catch (SQLException e) { - plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred preparing the target database", e); + logger.log(Level.SEVERE, "An exception occurred preparing the target database", e); } finally { - plugin.getBungeeLogger().log(Level.INFO, "Finished preparing target database!"); + logger.log(Level.INFO, "Finished preparing target database!"); } } private void getInventoryData() { - plugin.getBungeeLogger().log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge..."); + logger.log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.inventoryDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -135,14 +146,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting inventory data", e); + logger.log(Level.SEVERE, "An exception occurred getting inventory data", e); } finally { - plugin.getBungeeLogger().log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge"); + logger.log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge"); } } private void getEnderChestData() { - plugin.getBungeeLogger().log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge..."); + logger.log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.enderChestDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -158,14 +169,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e); + logger.log(Level.SEVERE, "An exception occurred getting ender chest data", e); } finally { - plugin.getBungeeLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); + logger.log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); } } private void getExperienceData() { - plugin.getBungeeLogger().log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge..."); + logger.log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge..."); try (Connection connection = sourceDatabase.getConnection()) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.expDataTable + ";")) { ResultSet resultSet = statement.executeQuery(); @@ -183,14 +194,14 @@ public class MPDBMigrator { } } } catch (SQLException e) { - plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting experience data", e); + logger.log(Level.SEVERE, "An exception occurred getting experience data", e); } finally { - plugin.getBungeeLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); + logger.log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); } } - private void sendEncodedData() { - for (Server processingServer : HuskSyncBungeeCord.synchronisedServers) { + private void sendEncodedData(HashSet synchronisedServers) { + for (Server processingServer : synchronisedServers) { if (processingServer.hasMySqlPlayerDataBridge()) { for (MPDBPlayerData data : mpdbPlayerData) { try { @@ -201,10 +212,10 @@ public class MPDBMigrator { .send(); migratedDataSent++; } catch (IOException e) { - plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize encoded MPDB data", e); + logger.log(Level.SEVERE, "Failed to serialize encoded MPDB data", e); } } - plugin.getBungeeLogger().log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish"); + logger.log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish"); } return; } @@ -215,41 +226,39 @@ public class MPDBMigrator { * * @param dataToLoad HashMap of the {@link PlayerData} to player Usernames that will be loaded */ - public static void loadIncomingData(HashMap dataToLoad) { - ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - int playersSaved = 0; - plugin.getBungeeLogger().log(Level.INFO, "Saving data for " + playersMigrated + " players..."); - - for (PlayerData playerData : dataToLoad.keySet()) { - String playerName = dataToLoad.get(playerData); + public void loadIncomingData(HashMap dataToLoad, DataManager dataManager) { + int playersSaved = 0; + logger.log(Level.INFO, "Saving data for " + playersMigrated + " players..."); - // Add the player to the MySQL table - HuskSyncBungeeCord.dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); + for (PlayerData playerData : dataToLoad.keySet()) { + String playerName = dataToLoad.get(playerData); - // Update the data in the cache and SQL - for (Settings.SynchronisationCluster cluster : Settings.clusters) { - HuskSyncBungeeCord.dataManager.updatePlayerData(playerData, cluster); - break; - } + // Add the player to the MySQL table + dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); - playersSaved++; - plugin.getBungeeLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); + // Update the data in the cache and SQL + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + dataManager.updatePlayerData(playerData, cluster); + break; } - // Mark as done when done - plugin.getBungeeLogger().log(Level.INFO, """ - === MySQLPlayerDataBridge Migration Wizard ========== - - Migration complete! - - Successfully migrated data for %1%/%2% players. - - You should now uninstall MySQLPlayerDataBridge from - the rest of the Spigot servers, then restart them. - """.replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated)) - .replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent))); - sourceDatabase.close(); // Close source database - }); + playersSaved++; + logger.log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); + } + + // Mark as done when done + logger.log(Level.INFO, """ + === MySQLPlayerDataBridge Migration Wizard ========== + + Migration complete! + + Successfully migrated data for %1%/%2% players. + + You should now uninstall MySQLPlayerDataBridge from + the rest of the Spigot servers, then restart them. + """.replaceAll("%1%", Integer.toString(playersMigrated)) + .replaceAll("%2%", Integer.toString(migratedDataSent))); + sourceDatabase.close(); // Close source database } /** @@ -287,8 +296,8 @@ public class MPDBMigrator { * MySQL class used for importing data from MPDB */ public static class MigratorMySQL extends MySQL { - public MigratorMySQL(HuskSyncBungeeCord instance, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) { - super(cluster, instance.getBungeeLogger()); + public MigratorMySQL(Logger logger, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) { + super(cluster, logger); super.host = host; super.port = port; super.database = database; diff --git a/common/src/main/java/me/william278/husksync/proxy/command/HuskSyncCommand.java b/common/src/main/java/me/william278/husksync/proxy/command/HuskSyncCommand.java new file mode 100644 index 00000000..bb62d9d0 --- /dev/null +++ b/common/src/main/java/me/william278/husksync/proxy/command/HuskSyncCommand.java @@ -0,0 +1,17 @@ +package me.william278.husksync.proxy.command; + +public interface HuskSyncCommand { + + SubCommand[] SUB_COMMANDS = {new SubCommand("about", null), + new SubCommand("status", "husksync.command.admin"), + new SubCommand("reload", "husksync.command.admin"), + new SubCommand("update", "husksync.command.admin"), + new SubCommand("invsee", "husksync.command.inventory"), + new SubCommand("echest", "husksync.command.ender_chest")}; + + /** + * A sub command, that may require a permission + */ + record SubCommand(String command, String permission) { } + +} diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index 9983926f..130419fd 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -9,10 +9,11 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; +import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.velocity.VelocityUpdateChecker; -import me.william278.husksync.velocity.command.HuskSyncCommand; +import me.william278.husksync.velocity.command.VelocityCommand; import me.william278.husksync.velocity.config.ConfigLoader; import me.william278.husksync.velocity.config.ConfigManager; import me.william278.husksync.velocity.listener.VelocityEventListener; @@ -65,7 +66,7 @@ public class HuskSyncVelocity { public static DataManager dataManager; - //public static MPDBMigrator mpdbMigrator; + public static MPDBMigrator mpdbMigrator; private final Logger logger; private final ProxyServer server; @@ -108,13 +109,13 @@ public class HuskSyncVelocity { ConfigManager.loadConfig(); // Load settings from config - ConfigLoader.loadSettings(ConfigManager.getConfig()); + ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig())); // Load messages ConfigManager.loadMessages(); // Load locales from messages - ConfigLoader.loadMessageStrings(ConfigManager.getMessages()); + ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages())); // Do update checker if (Settings.automaticUpdateChecks) { @@ -143,10 +144,10 @@ public class HuskSyncVelocity { CommandMeta meta = commandManager.metaBuilder("husksync") .aliases("hs") .build(); - commandManager.register(meta, new HuskSyncCommand()); + commandManager.register(meta, new VelocityCommand()); // Prepare the migrator for use if needed - //todo migrator + mpdbMigrator = new MPDBMigrator(getVelocityLogger()); // Initialize bStats metrics try { diff --git a/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java b/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java deleted file mode 100644 index 1e21df94..00000000 --- a/velocity/src/main/java/me/william278/husksync/velocity/command/HuskSyncCommand.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.william278.husksync.velocity.command; - -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.SimpleCommand; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public class HuskSyncCommand implements SimpleCommand { - - /** - * Executes the command for the specified invocation. - * - * @param invocation the invocation context - */ - @Override - public void execute(Invocation invocation) { - final String[] args = invocation.arguments(); - final CommandSource source = invocation.source(); - - } - - /** - * Provides tab complete suggestions for the specified invocation. - * - * @param invocation the invocation context - * @return the tab complete suggestions - */ - @Override - public List suggest(Invocation invocation) { - return new ArrayList<>(); - } -} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java new file mode 100644 index 00000000..818de9a7 --- /dev/null +++ b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java @@ -0,0 +1,418 @@ +package me.william278.husksync.velocity.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import de.themoep.minedown.adventure.MineDown; +import me.william278.husksync.HuskSyncVelocity; +import me.william278.husksync.PlayerData; +import me.william278.husksync.Server; +import me.william278.husksync.Settings; +import me.william278.husksync.migrator.MPDBMigrator; +import me.william278.husksync.proxy.command.HuskSyncCommand; +import me.william278.husksync.redis.RedisMessage; +import me.william278.husksync.util.MessageManager; +import me.william278.husksync.velocity.VelocityUpdateChecker; +import me.william278.husksync.velocity.config.ConfigLoader; +import me.william278.husksync.velocity.config.ConfigManager; + +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class VelocityCommand implements SimpleCommand, HuskSyncCommand { + + private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance(); + + @Override + public void execute(Invocation invocation) { + final String[] args = invocation.arguments(); + final CommandSource sender = invocation.source(); + if (sender instanceof Player player) { + if (HuskSyncVelocity.synchronisedServers.size() == 0) { + player.sendMessage(new MineDown(MessageManager.getMessage("error_no_servers_proxied")).toComponent()); + return; + } + if (args.length >= 1) { + switch (args[0].toLowerCase(Locale.ROOT)) { + case "about", "info" -> sendAboutInformation(player); + case "update" -> { + if (!player.hasPermission("husksync.command.inventory")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + sender.sendMessage(new MineDown("[Checking for HuskSync updates...](gray)").toComponent()); + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { + // Check Bukkit servers needing updates + int updatesNeeded = 0; + String bukkitBrand = "Spigot"; + String bukkitVersion = "1.0"; + for (Server server : HuskSyncVelocity.synchronisedServers) { + VelocityUpdateChecker updateChecker = new VelocityUpdateChecker(server.huskSyncVersion()); + if (!updateChecker.isUpToDate()) { + updatesNeeded++; + bukkitBrand = server.serverBrand(); + bukkitVersion = server.huskSyncVersion(); + } + } + + // Check Velocity servers needing updates and send message + VelocityUpdateChecker proxyUpdateChecker = new VelocityUpdateChecker(HuskSyncVelocity.VERSION); + if (proxyUpdateChecker.isUpToDate() && updatesNeeded == 0) { + sender.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running Version " + proxyUpdateChecker.getLatestVersion() + "](#00fb9a)").toComponent()); + } else { + sender.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Your server(s) are not up-to-date:](#00fb9a)").toComponent()); + if (!proxyUpdateChecker.isUpToDate()) { + sender.sendMessage(new MineDown("[•](white) [HuskSync on the Velocity proxy is outdated (Latest: " + proxyUpdateChecker.getLatestVersion() + ", Running: " + proxyUpdateChecker.getCurrentVersion() + ")](#00fb9a)").toComponent()); + } + if (updatesNeeded > 0) { + sender.sendMessage(new MineDown("[•](white) [HuskSync on " + updatesNeeded + " connected " + bukkitBrand + " server(s) are outdated (Latest: " + proxyUpdateChecker.getLatestVersion() + ", Running: " + bukkitVersion + ")](#00fb9a)").toComponent()); + } + sender.sendMessage(new MineDown("[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husktowns.92672/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husktowns.1056/updates)").toComponent()); + } + }); + } + case "invsee", "openinv", "inventory" -> { + if (!player.hasPermission("husksync.command.inventory")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + String clusterId; + if (Settings.clusters.size() > 1) { + if (args.length == 3) { + clusterId = args[2]; + } else { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); + return; + } + } else { + clusterId = "main"; + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + clusterId = cluster.clusterId(); + break; + } + } + if (args.length == 2 || args.length == 3) { + String playerName = args[1]; + openInventory(player, playerName, clusterId); + } else { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax").replaceAll("%1%", + "/husksync invsee ")).toComponent()); + } + } + case "echest", "enderchest" -> { + if (!player.hasPermission("husksync.command.ender_chest")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + String clusterId; + if (Settings.clusters.size() > 1) { + if (args.length == 3) { + clusterId = args[2]; + } else { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); + return; + } + } else { + clusterId = "main"; + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + clusterId = cluster.clusterId(); + break; + } + } + if (args.length == 2 || args.length == 3) { + String playerName = args[1]; + openEnderChest(player, playerName, clusterId); + } else { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax") + .replaceAll("%1%", "/husksync echest ")).toComponent()); + } + } + case "migrate" -> { + if (!player.hasPermission("husksync.command.admin")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + sender.sendMessage(new MineDown(MessageManager.getMessage("error_console_command_only") + .replaceAll("%1%", "Velocity")).toComponent()); + } + case "status" -> { + if (!player.hasPermission("husksync.command.admin")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + int playerDataSize = 0; + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + playerDataSize += HuskSyncVelocity.dataManager.playerDataCache.get(cluster).playerData.size(); + } + sender.sendMessage(new MineDown(MessageManager.PLUGIN_STATUS.toString() + .replaceAll("%1%", String.valueOf(HuskSyncVelocity.synchronisedServers.size())) + .replaceAll("%2%", String.valueOf(playerDataSize))).toComponent()); + } + case "reload" -> { + if (!player.hasPermission("husksync.command.admin")) { + sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); + return; + } + ConfigManager.loadConfig(); + ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig())); + + ConfigManager.loadMessages(); + ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages())); + + // Send reload request to all bukkit servers + try { + new RedisMessage(RedisMessage.MessageType.RELOAD_CONFIG, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null), + "reload") + .send(); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize reload notification message data"); + } + + sender.sendMessage(new MineDown(MessageManager.getMessage("reload_complete")).toComponent()); + } + default -> sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax").replaceAll("%1%", + "/husksync ")).toComponent()); + } + } else { + sendAboutInformation(player); + } + } else { + // Database migration wizard + if (args.length >= 1) { + if (args[0].equalsIgnoreCase("migrate")) { + MPDBMigrator migrator = HuskSyncVelocity.mpdbMigrator; + if (args.length == 1) { + sender.sendMessage(new MineDown( + """ + === MySQLPlayerDataBridge Migration Wizard ========== + This will migrate data from the MySQLPlayerDataBridge + plugin to HuskSync. + + Data that will be migrated: + - Inventories + - Ender Chests + - Experience points + + Other non-vital data, such as current health, hunger + & potion effects will not be migrated to ensure that + migration does not take an excessive amount of time. + + To do this, you need to have MySqlPlayerDataBridge + and HuskSync installed on one Spigot server as well + as HuskSync installed on the proxy (which you have) + + >To proceed, type: husksync migrate setup""").toComponent()); + } else { + switch (args[1].toLowerCase()) { + case "setup" -> sender.sendMessage(new MineDown( + """ + === MySQLPlayerDataBridge Migration Wizard ========== + The following database settings will be used. + Please make sure they match the correct settings to + access your MySQLPlayerDataBridge Data + + sourceHost: %1% + sourcePort: %2% + sourceDatabase: %3% + sourceUsername: %4% + sourcePassword: %5% + + sourceInventoryTableName: %6% + sourceEnderChestTableName: %7% + sourceExperienceTableName: %8% + + targetCluster: %9% + + To change a setting, type: + husksync migrate setting + + Please ensure no players are logged in to the network + and that at least one Spigot server is online with + both HuskSync AND MySqlPlayerDataBridge installed AND + that the server has been configured with the correct + Redis credentials. + + Warning: Data will be saved to your configured data + source, which is currently a %10% database. + Please make sure you are happy with this, or stop + the proxy server and edit this in config.yml + + Warning: Migration will overwrite any current data + saved by HuskSync. It will not, however, delete any + data from the source MySQLPlayerDataBridge database. + + >When done, type: husksync migrate start""" + .replaceAll("%1%", migrator.migrationSettings.sourceHost) + .replaceAll("%2%", String.valueOf(migrator.migrationSettings.sourcePort)) + .replaceAll("%3%", migrator.migrationSettings.sourceDatabase) + .replaceAll("%4%", migrator.migrationSettings.sourceUsername) + .replaceAll("%5%", migrator.migrationSettings.sourcePassword) + .replaceAll("%6%", migrator.migrationSettings.inventoryDataTable) + .replaceAll("%7%", migrator.migrationSettings.enderChestDataTable) + .replaceAll("%8%", migrator.migrationSettings.expDataTable) + .replaceAll("%9%", migrator.migrationSettings.targetCluster) + .replaceAll("%10%", Settings.dataStorageType.toString()) + ).toComponent()); + case "setting" -> { + if (args.length == 4) { + String value = args[3]; + switch (args[2]) { + case "sourceHost", "host" -> migrator.migrationSettings.sourceHost = value; + case "sourcePort", "port" -> { + try { + migrator.migrationSettings.sourcePort = Integer.parseInt(value); + } catch (NumberFormatException e) { + sender.sendMessage(new MineDown("Error: Invalid value; port must be a number").toComponent()); + return; + } + } + case "sourceDatabase", "database" -> migrator.migrationSettings.sourceDatabase = value; + case "sourceUsername", "username" -> migrator.migrationSettings.sourceUsername = value; + case "sourcePassword", "password" -> migrator.migrationSettings.sourcePassword = value; + case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> migrator.migrationSettings.inventoryDataTable = value; + case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> migrator.migrationSettings.enderChestDataTable = value; + case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> migrator.migrationSettings.expDataTable = value; + case "targetCluster", "cluster" -> migrator.migrationSettings.targetCluster = value; + default -> { + sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent()); + return; + } + } + sender.sendMessage(new MineDown("Successfully updated setting: \"" + args[2] + "\" --> \"" + value + "\"").toComponent()); + } else { + sender.sendMessage(new MineDown("Error: Invalid usage. Syntax: husksync migrate setting ").toComponent()); + } + } + case "start" -> { + sender.sendMessage(new MineDown("Starting MySQLPlayerDataBridge migration!...").toComponent()); + + // If the migrator is ready, execute the migration asynchronously + if (HuskSyncVelocity.mpdbMigrator.readyToMigrate(plugin.getProxyServer().getPlayerCount(), + HuskSyncVelocity.synchronisedServers)) { + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> + HuskSyncVelocity.mpdbMigrator.executeMigrationOperations(HuskSyncVelocity.dataManager, + HuskSyncVelocity.synchronisedServers)); + } + } + default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent()); + } + } + return; + } + } + sender.sendMessage(new MineDown("Error: Invalid syntax. Usage: husksync migrate ").toComponent()); + } + } + + // View the inventory of a player specified by their name + private void openInventory(Player viewer, String targetPlayerName, String clusterId) { + if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent()); + return; + } + if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_inventory_online")).toComponent()); + return; + } + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (!cluster.clusterId().equals(clusterId)) continue; + PlayerData playerData = HuskSyncVelocity.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); + if (playerData == null) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); + return; + } + try { + new RedisMessage(RedisMessage.MessageType.OPEN_INVENTORY, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId(), null), + targetPlayerName, RedisMessage.serialize(playerData)) + .send(); + viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_inventory_of").replaceAll("%1%", + targetPlayerName)).toComponent()); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + } + return; + } + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); + }); + } + + // View the ender chest of a player specified by their name + public void openEnderChest(Player viewer, String targetPlayerName, String clusterId) { + if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent()); + return; + } + if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_ender_chest_online")).toComponent()); + return; + } + plugin.getProxyServer().getScheduler().buildTask(plugin, () -> { + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (!cluster.clusterId().equals(clusterId)) continue; + PlayerData playerData = HuskSyncVelocity.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId()); + if (playerData == null) { + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); + return; + } + try { + new RedisMessage(RedisMessage.MessageType.OPEN_ENDER_CHEST, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId(), null), + targetPlayerName, RedisMessage.serialize(playerData)) + .send(); + viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_ender_chest_of").replaceAll("%1%", + targetPlayerName)).toComponent()); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + } + return; + } + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); + }); + } + + /** + * Send information about the plugin + * + * @param player The player to send it to + */ + private void sendAboutInformation(Player player) { + try { + new RedisMessage(RedisMessage.MessageType.SEND_PLUGIN_INFORMATION, + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, player.getUniqueId(), null), + "Velocity", HuskSyncVelocity.VERSION).send(); + } catch (IOException e) { + plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize plugin information to send", e); + } + } + + @Override + public List suggest(Invocation invocation) { + final CommandSource sender = invocation.source(); + final String[] args = invocation.arguments(); + + if (sender instanceof Player player) { + if (args.length == 1) { + final ArrayList subCommands = new ArrayList<>(); + for (SubCommand subCommand : SUB_COMMANDS) { + if (subCommand.permission() != null) { + if (!player.hasPermission(subCommand.permission())) { + continue; + } + } + subCommands.add(subCommand.command()); + } + // Automatically filter the sub commands' order in tab completion by what the player has typed + return subCommands.stream().filter(val -> val.startsWith(args[0])) + .sorted().collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java index 940df485..05081861 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java @@ -3,7 +3,6 @@ package me.william278.husksync.velocity.config; import me.william278.husksync.HuskSyncVelocity; import me.william278.husksync.Settings; import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.commented.CommentedConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import java.io.File; From c081d38795a06ca6691c7f7344aecd05dc54c459 Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 00:56:51 +0000 Subject: [PATCH 04/11] Minor refactoring and cleanup --- .../src/main/java/me/william278/husksync/HuskSyncVelocity.java | 3 ++- .../william278/husksync/velocity/command/VelocityCommand.java | 2 +- .../husksync/velocity/{ => util}/VelocityUpdateChecker.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename velocity/src/main/java/me/william278/husksync/velocity/{ => util}/VelocityUpdateChecker.java (91%) diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index 130419fd..8ed9bfa7 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -12,7 +12,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.redis.RedisMessage; -import me.william278.husksync.velocity.VelocityUpdateChecker; +import me.william278.husksync.velocity.util.VelocityUpdateChecker; import me.william278.husksync.velocity.command.VelocityCommand; import me.william278.husksync.velocity.config.ConfigLoader; import me.william278.husksync.velocity.config.ConfigManager; @@ -180,6 +180,7 @@ public class HuskSyncVelocity { } } + // Close database connections dataManager.closeDatabases(); // Log to console diff --git a/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java index 818de9a7..d8898d1c 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java @@ -12,7 +12,7 @@ import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.proxy.command.HuskSyncCommand; import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.util.MessageManager; -import me.william278.husksync.velocity.VelocityUpdateChecker; +import me.william278.husksync.velocity.util.VelocityUpdateChecker; import me.william278.husksync.velocity.config.ConfigLoader; import me.william278.husksync.velocity.config.ConfigManager; diff --git a/velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java b/velocity/src/main/java/me/william278/husksync/velocity/util/VelocityUpdateChecker.java similarity index 91% rename from velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java rename to velocity/src/main/java/me/william278/husksync/velocity/util/VelocityUpdateChecker.java index 2e8992c1..f7c55c81 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/VelocityUpdateChecker.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/util/VelocityUpdateChecker.java @@ -1,4 +1,4 @@ -package me.william278.husksync.velocity; +package me.william278.husksync.velocity.util; import me.william278.husksync.HuskSyncVelocity; import me.william278.husksync.util.UpdateChecker; From 861a0b2824dc7460421e45164d44cb2dfc0e9cf0 Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 01:01:15 +0000 Subject: [PATCH 05/11] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8945f88..6aaa702d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Are you a developer? [Read below for information about code bounty licensing](#C ## Setup ### Requirements -* A BungeeCord-based proxy server +* A BungeeCord or Velocity-based proxy server * A Spigot-based game server * A Redis server @@ -99,7 +99,7 @@ Short answer: Not right now, but improved support for this is planned in the fut Long answer: This is a difficult question to unpack because of the wide variety of setups that involve multiple proxies, however currently the architecture of how messages are sent between servers assumes that one proxy will serve multiple Bukkit servers, so having multiple proxies will lead to data going out of sync, among other issues. #### Does it work with Velocity? -I'd like to add support for Velocity in the future, but right now it is not supported. +Yes! Servers running the Velocity proxy software are supported as of HuskSync 1.2+. #### Is this faster than MySqlPlayerDataBridge (MPDB)? It's difficult to say, and will depend on your server. From eb0a8dbf47e06e80b86bdc211860edcd858d81ef Mon Sep 17 00:00:00 2001 From: William Date: Wed, 8 Dec 2021 15:02:43 +0000 Subject: [PATCH 06/11] Fix configuration formatting and loading issues --- common/src/main/resources/proxy-config.yml | 2 +- .../main/java/me/william278/husksync/HuskSyncVelocity.java | 2 +- .../william278/husksync/velocity/config/ConfigLoader.java | 5 +++-- .../william278/husksync/velocity/config/ConfigManager.java | 7 +++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/common/src/main/resources/proxy-config.yml b/common/src/main/resources/proxy-config.yml index b54d463e..0da450ad 100644 --- a/common/src/main/resources/proxy-config.yml +++ b/common/src/main/resources/proxy-config.yml @@ -23,4 +23,4 @@ clusters: player_table: 'husksync_players' data_table: 'husksync_data' check_for_updates: true -config_file_version: 1.1 \ No newline at end of file +config_file_version: 1.2 \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index 8ed9bfa7..4968f6d6 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -32,7 +32,7 @@ import java.util.logging.Level; import static me.william278.husksync.HuskSyncVelocity.VERSION; @Plugin( - id = "velocity", + id = "husksync", name = "HuskSync", version = VERSION, description = "HuskSync for velocity", diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java index 4d9fee9b..0b57eaae 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java @@ -20,8 +20,8 @@ public class ConfigLoader { configRoot.getNode("check_for_updates").setValue(true); } if (configVersion.equalsIgnoreCase("1.0") || configVersion.equalsIgnoreCase("1.0.1") || configVersion.equalsIgnoreCase("1.0.2") || configVersion.equalsIgnoreCase("1.0.3")) { - configRoot.getNode("clusters.main.player_table").setValue("husksync_players"); - configRoot.getNode("clusters.main.data_table").setValue("husksync_data"); + configRoot.getNode("clusters", "main", "player_table").setValue("husksync_players"); + configRoot.getNode("clusters", "main", "data_table").setValue("husksync_data"); } configRoot.getNode("config_file_version").setValue(HuskSyncVelocity.VERSION); } @@ -49,6 +49,7 @@ public class ConfigLoader { public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException { ConfigurationNode config = copyDefaults(loadedConfig); + //ConfigurationNode config = copyDefaults(loadedConfig); Settings.language = getConfigString(config, "en-gb", "language"); diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java index 05081861..a9024205 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java @@ -4,6 +4,7 @@ import me.william278.husksync.HuskSyncVelocity; import me.william278.husksync.Settings; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.yaml.snakeyaml.DumperOptions; import java.io.File; import java.io.IOException; @@ -24,7 +25,7 @@ public class ConfigManager { } File configFile = new File(plugin.getDataFolder(), "config.yml"); if (!configFile.exists()) { - Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("proxy-config.yml")), configFile.toPath()); + Files.copy(Objects.requireNonNull(HuskSyncVelocity.class.getClassLoader().getResourceAsStream("proxy-config.yml")), configFile.toPath()); plugin.getVelocityLogger().info("Created HuskSync config file"); } } catch (Exception e) { @@ -49,7 +50,7 @@ public class ConfigManager { } File messagesFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); if (!messagesFile.exists()) { - Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("languages/" + Settings.language + ".yml")), + Files.copy(Objects.requireNonNull(HuskSyncVelocity.class.getClassLoader().getResourceAsStream("languages/" + Settings.language + ".yml")), messagesFile.toPath()); plugin.getVelocityLogger().info("Created HuskSync messages file"); } @@ -62,6 +63,8 @@ public class ConfigManager { File configFile = new File(plugin.getDataFolder(), "config.yml"); return YAMLConfigurationLoader.builder() .setPath(configFile.toPath()) + .setFlowStyle(DumperOptions.FlowStyle.BLOCK) + .setIndent(2) .build(); } From a518d12e67f5760ca9db63671daf45d2945895a7 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 9 Dec 2021 14:26:04 +0000 Subject: [PATCH 07/11] Re-add MPDB migrator loading on redis listener Use Libby to download SQL dependencies at runtime --- build.gradle | 1 + bungeecord/build.gradle | 2 + .../husksync/HuskSyncBungeeCord.java | 23 ++++++++++++ common/src/main/resources/bungee.yml | 5 +-- velocity/build.gradle | 2 + .../william278/husksync/HuskSyncVelocity.java | 37 ++++++++++++++++++- .../velocity/command/VelocityCommand.java | 13 +++++-- .../velocity/config/ConfigLoader.java | 4 +- .../velocity/config/ConfigManager.java | 2 + .../listener/VelocityEventListener.java | 2 +- .../listener/VelocityRedisListener.java | 21 ++++++----- 11 files changed, 91 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index 96ada90e..c13e49c2 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ subprojects { mavenCentral() maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url 'https://repo.velocitypowered.com/snapshots/' } + maven { url 'https://repo.alessiodp.com/releases/' } maven { url 'https://repo.minebench.de/' } maven { url 'https://repo.codemc.org/repository/maven-public' } maven { url 'https://jitpack.io' } diff --git a/bungeecord/build.gradle b/bungeecord/build.gradle index 35707feb..07118ae8 100644 --- a/bungeecord/build.gradle +++ b/bungeecord/build.gradle @@ -5,6 +5,7 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-bungeecord:2.2.1' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' + implementation 'net.byteflux:libby-bungee:1.1.4' compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' } @@ -13,6 +14,7 @@ shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' + relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.bungee' } tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file diff --git a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java index 9aa7e1bc..c4edc58f 100644 --- a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java +++ b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java @@ -11,6 +11,8 @@ import me.william278.husksync.bungeecord.util.BungeeLogger; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.util.Logger; +import net.byteflux.libby.BungeeLibraryManager; +import net.byteflux.libby.Library; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.bstats.bungeecord.Metrics; @@ -56,6 +58,7 @@ public final class HuskSyncBungeeCord extends Plugin { public void onLoad() { instance = this; logger = new BungeeLogger(getLogger()); + fetchDependencies(); } @Override @@ -146,4 +149,24 @@ public final class HuskSyncBungeeCord extends Plugin { getBungeeLogger().info("Disabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); } + // Load dependencies + private void fetchDependencies() { + BungeeLibraryManager manager = new BungeeLibraryManager(getInstance()); + + Library mySqlLib = Library.builder() + .groupId("mysql") + .artifactId("mysql-connector-java") + .version("8.0.25") + .build(); + + Library sqLiteLib = Library.builder() + .groupId("org.xerial") + .artifactId("sqlite-jdbc") + .version("3.36.0.3") + .build(); + + manager.addMavenCentral(); + manager.loadLibrary(mySqlLib); + manager.loadLibrary(sqLiteLib); + } } diff --git a/common/src/main/resources/bungee.yml b/common/src/main/resources/bungee.yml index 5c2b9e2d..8dd1118c 100644 --- a/common/src/main/resources/bungee.yml +++ b/common/src/main/resources/bungee.yml @@ -2,7 +2,4 @@ name: HuskSync version: @version@ main: me.william278.husksync.HuskSyncBungeeCord author: William278 -description: 'A modern, cross-server player data synchronization system' -libraries: - - mysql:mysql-connector-java:8.0.25 - - org.xerial:sqlite-jdbc:3.36.0.3 \ No newline at end of file +description: 'A modern, cross-server player data synchronization system' \ No newline at end of file diff --git a/velocity/build.gradle b/velocity/build.gradle index d3c2b1ec..e2f12411 100644 --- a/velocity/build.gradle +++ b/velocity/build.gradle @@ -6,6 +6,7 @@ dependencies { implementation 'org.bstats:bstats-velocity:2.2.1' implementation 'com.zaxxer:HikariCP:5.0.0' implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT' + implementation 'net.byteflux:libby-velocity:1.1.4' compileOnly 'com.velocitypowered:velocity-api:3.1.0' annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' @@ -15,6 +16,7 @@ shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' + relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.velocity' } tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index 4968f6d6..7a4595ba 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -1,6 +1,7 @@ package me.william278.husksync; import com.google.inject.Inject; +import com.google.inject.Provides; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.event.Subscribe; @@ -19,6 +20,8 @@ import me.william278.husksync.velocity.config.ConfigManager; import me.william278.husksync.velocity.listener.VelocityEventListener; import me.william278.husksync.velocity.listener.VelocityRedisListener; import me.william278.husksync.velocity.util.VelocityLogger; +import net.byteflux.libby.Library; +import net.byteflux.libby.VelocityLibraryManager; import org.bstats.velocity.Metrics; import org.slf4j.Logger; @@ -72,6 +75,8 @@ public class HuskSyncVelocity { private final ProxyServer server; private final Path dataDirectory; + private final VelocityLibraryManager manager; + // Get the data folder public File getDataFolder() { return dataDirectory.toFile(); @@ -90,11 +95,13 @@ public class HuskSyncVelocity { } @Inject - public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { + public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory, VelocityLibraryManager manager) { this.server = server; this.logger = logger; this.dataDirectory = dataDirectory; this.metricsFactory = metricsFactory; + this.manager = manager; + fetchDependencies(); } @Subscribe @@ -105,6 +112,9 @@ public class HuskSyncVelocity { // Setup logger velocityLogger = new VelocityLogger(logger); + // Prepare synchronised servers tracker + synchronisedServers = new HashSet<>(); + // Load config ConfigManager.loadConfig(); @@ -125,6 +135,12 @@ public class HuskSyncVelocity { // Setup data manager dataManager = new DataManager(getVelocityLogger(), getDataFolder()); + // Ensure the data manager initialized correctly + if (dataManager.hasFailedInitialization) { + getVelocityLogger().severe("Failed to initialize the HuskSync database(s).\n" + + "HuskSync will now abort loading itself (Velocity) v" + VERSION); + } + // Setup player data cache for (Settings.SynchronisationCluster cluster : Settings.clusters) { dataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache()); @@ -186,4 +202,23 @@ public class HuskSyncVelocity { // Log to console getVelocityLogger().info("Disabled HuskSync (Velocity) v" + VERSION); } + + // Load dependencies + private void fetchDependencies() { + Library mySqlLib = Library.builder() + .groupId("mysql") + .artifactId("mysql-connector-java") + .version("8.0.25") + .build(); + + Library sqLiteLib = Library.builder() + .groupId("org.xerial") + .artifactId("sqlite-jdbc") + .version("3.36.0.3") + .build(); + + manager.addMavenCentral(); + manager.loadLibrary(mySqlLib); + manager.loadLibrary(sqLiteLib); + } } diff --git a/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java index d8898d1c..9825574e 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/command/VelocityCommand.java @@ -71,7 +71,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand { } sender.sendMessage(new MineDown("[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husktowns.92672/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husktowns.1056/updates)").toComponent()); } - }); + }).schedule(); } case "invsee", "openinv", "inventory" -> { if (!player.hasPermission("husksync.command.inventory")) { @@ -294,7 +294,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand { HuskSyncVelocity.synchronisedServers)) { plugin.getProxyServer().getScheduler().buildTask(plugin, () -> HuskSyncVelocity.mpdbMigrator.executeMigrationOperations(HuskSyncVelocity.dataManager, - HuskSyncVelocity.synchronisedServers)); + HuskSyncVelocity.synchronisedServers)).schedule(); } } default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent()); @@ -338,7 +338,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand { return; } viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); - }); + }).schedule(); } // View the ender chest of a player specified by their name @@ -372,7 +372,7 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand { return; } viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); - }); + }).schedule(); } /** @@ -406,6 +406,11 @@ public class VelocityCommand implements SimpleCommand, HuskSyncCommand { } subCommands.add(subCommand.command()); } + // Return list of subcommands + if (args[0].length() == 0) { + return subCommands; + } + // Automatically filter the sub commands' order in tab completion by what the player has typed return subCommands.stream().filter(val -> val.startsWith(args[0])) .sorted().collect(Collectors.toList()); diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java index 0b57eaae..a6fc6d83 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigLoader.java @@ -78,7 +78,7 @@ public class ConfigLoader { // Read cluster data ConfigurationNode clusterSection = config.getNode("clusters"); final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync"; - for (ConfigurationNode cluster : clusterSection.getChildrenList()) { + for (ConfigurationNode cluster : clusterSection.getChildrenMap().values()) { final String clusterId = (String) cluster.getKey(); final String playerTableName = getConfigString(config, "husksync_players", "clusters", clusterId, "player_table"); final String dataTableName = getConfigString(config, "husksync_data", "clusters", clusterId, "data_table"); @@ -89,7 +89,7 @@ public class ConfigLoader { public static void loadMessageStrings(ConfigurationNode config) { final HashMap messages = new HashMap<>(); - for (ConfigurationNode message : config.getChildrenList()) { + for (ConfigurationNode message : config.getChildrenMap().values()) { final String messageId = (String) message.getKey(); messages.put(messageId, getConfigString(config, "", messageId)); } diff --git a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java index a9024205..f5731194 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/config/ConfigManager.java @@ -82,6 +82,8 @@ public class ConfigManager { File configFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml"); return YAMLConfigurationLoader.builder() .setPath(configFile.toPath()) + .setFlowStyle(DumperOptions.FlowStyle.BLOCK) + .setIndent(2) .build() .load(); } catch (IOException e) { diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java index 116fd71f..d035e971 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityEventListener.java @@ -41,6 +41,6 @@ public class VelocityEventListener { plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize request data on join message data"); e.printStackTrace(); } - }); + }).schedule(); } } diff --git a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java index 616abbee..1a1b9fa4 100644 --- a/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java +++ b/velocity/src/main/java/me/william278/husksync/velocity/listener/VelocityRedisListener.java @@ -6,6 +6,7 @@ import me.william278.husksync.HuskSyncVelocity; import me.william278.husksync.PlayerData; import me.william278.husksync.Server; import me.william278.husksync.Settings; +import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.util.MessageManager; @@ -84,7 +85,7 @@ public class VelocityRedisListener extends RedisListener { log(Level.SEVERE, "Failed to serialize data when replying to a data request"); e.printStackTrace(); } - }); + }).schedule(); } case PLAYER_DATA_UPDATE -> { // Deserialize the PlayerData received @@ -174,18 +175,20 @@ public class VelocityRedisListener extends RedisListener { return; } - //todo Migrator - /*// Add the incoming data to the data to be saved - MPDBMigrator.incomingPlayerData.put(playerData, playerName); + // Get the MPDB migrator + MPDBMigrator migrator = HuskSyncVelocity.mpdbMigrator; + + // Add the incoming data to the data to be saved + migrator.incomingPlayerData.put(playerData, playerName); // Increment players migrated - MPDBMigrator.playersMigrated++; - plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); + migrator.playersMigrated++; + plugin.getVelocityLogger().log(Level.INFO, "Migrated " + migrator.playersMigrated + "/" + migrator.migratedDataSent + " players."); // When all the data has been received, save it - if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) { - MPDBMigrator.loadIncomingData(MPDBMigrator.incomingPlayerData); - }*/ + if (migrator.migratedDataSent == migrator.playersMigrated) { + migrator.loadIncomingData(migrator.incomingPlayerData, HuskSyncVelocity.dataManager); + } } } } From 3190431ede27c25c33a1eeea8a337345b5806674 Mon Sep 17 00:00:00 2001 From: William Date: Tue, 14 Dec 2021 18:05:29 +0000 Subject: [PATCH 08/11] Fix library loading on Bungee and Velocity, bump to 1.2 --- build.gradle | 3 +-- bukkit/build.gradle | 2 +- .../husksync/bukkit/data/DataSerializer.java | 7 +++---- bungeecord/build.gradle | 4 ++-- velocity/build.gradle | 5 ++--- .../me/william278/husksync/HuskSyncVelocity.java | 14 +++++++------- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/build.gradle b/build.gradle index c13e49c2..9a656549 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { allprojects { group 'me.William278' - version '1.2-dev' + version '1.2' compileJava { options.encoding = 'UTF-8' } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } @@ -34,7 +34,6 @@ subprojects { mavenCentral() maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url 'https://repo.velocitypowered.com/snapshots/' } - maven { url 'https://repo.alessiodp.com/releases/' } maven { url 'https://repo.minebench.de/' } maven { url 'https://repo.codemc.org/repository/maven-public' } maven { url 'https://jitpack.io' } diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 07332468..b6858d4c 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -13,7 +13,7 @@ dependencies { shadowJar { relocate 'org.bstats', 'me.William278.husksync.libraries.plan' - relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' + relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard' } tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java index 26861dba..3a8a7de2 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/data/DataSerializer.java @@ -17,7 +17,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.util.*; -import java.util.stream.Collectors; public class DataSerializer { @@ -262,21 +261,21 @@ public class DataSerializer { switch (statistic.getType()) { case ITEM -> { HashMap itemValues = new HashMap<>(); - for (Material itemMaterial : Arrays.stream(Material.values()).filter(Material::isItem).collect(Collectors.toList())) { + for (Material itemMaterial : Arrays.stream(Material.values()).filter(Material::isItem).toList()) { itemValues.put(itemMaterial, player.getStatistic(statistic, itemMaterial)); } itemStatisticValues.put(statistic, itemValues); } case BLOCK -> { HashMap blockValues = new HashMap<>(); - for (Material blockMaterial : Arrays.stream(Material.values()).filter(Material::isBlock).collect(Collectors.toList())) { + for (Material blockMaterial : Arrays.stream(Material.values()).filter(Material::isBlock).toList()) { blockValues.put(blockMaterial, player.getStatistic(statistic, blockMaterial)); } blockStatisticValues.put(statistic, blockValues); } case ENTITY -> { HashMap entityValues = new HashMap<>(); - for (EntityType type : Arrays.stream(EntityType.values()).filter(EntityType::isAlive).collect(Collectors.toList())) { + for (EntityType type : Arrays.stream(EntityType.values()).filter(EntityType::isAlive).toList()) { entityValues.put(type, player.getStatistic(statistic, type)); } entityStatisticValues.put(statistic, entityValues); diff --git a/bungeecord/build.gradle b/bungeecord/build.gradle index 07118ae8..f5f42835 100644 --- a/bungeecord/build.gradle +++ b/bungeecord/build.gradle @@ -5,7 +5,7 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-bungeecord:2.2.1' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' - implementation 'net.byteflux:libby-bungee:1.1.4' + implementation 'com.github.WiIIiam278.libby:libby-bungee:1.1.5' compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' } @@ -13,7 +13,7 @@ dependencies { shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' - relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' + relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard' relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.bungee' } diff --git a/velocity/build.gradle b/velocity/build.gradle index e2f12411..475df9c8 100644 --- a/velocity/build.gradle +++ b/velocity/build.gradle @@ -4,9 +4,8 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-velocity:2.2.1' - implementation 'com.zaxxer:HikariCP:5.0.0' implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT' - implementation 'net.byteflux:libby-velocity:1.1.4' + implementation 'com.github.WiIIiam278.libby:libby-velocity:1.1.5' compileOnly 'com.velocitypowered:velocity-api:3.1.0' annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' @@ -15,7 +14,7 @@ dependencies { shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' - relocate 'de.themoep', 'me.William278.husksync.libraries.minedown' + relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.adventure' relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.velocity' } diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index 7a4595ba..c1a2e5de 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -1,7 +1,6 @@ package me.william278.husksync; import com.google.inject.Inject; -import com.google.inject.Provides; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.event.Subscribe; @@ -13,13 +12,13 @@ import com.velocitypowered.api.proxy.ProxyServer; import me.william278.husksync.migrator.MPDBMigrator; import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.redis.RedisMessage; -import me.william278.husksync.velocity.util.VelocityUpdateChecker; import me.william278.husksync.velocity.command.VelocityCommand; import me.william278.husksync.velocity.config.ConfigLoader; import me.william278.husksync.velocity.config.ConfigManager; import me.william278.husksync.velocity.listener.VelocityEventListener; import me.william278.husksync.velocity.listener.VelocityRedisListener; import me.william278.husksync.velocity.util.VelocityLogger; +import me.william278.husksync.velocity.util.VelocityUpdateChecker; import net.byteflux.libby.Library; import net.byteflux.libby.VelocityLibraryManager; import org.bstats.velocity.Metrics; @@ -75,8 +74,6 @@ public class HuskSyncVelocity { private final ProxyServer server; private final Path dataDirectory; - private final VelocityLibraryManager manager; - // Get the data folder public File getDataFolder() { return dataDirectory.toFile(); @@ -95,13 +92,11 @@ public class HuskSyncVelocity { } @Inject - public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory, VelocityLibraryManager manager) { + public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { this.server = server; this.logger = logger; this.dataDirectory = dataDirectory; this.metricsFactory = metricsFactory; - this.manager = manager; - fetchDependencies(); } @Subscribe @@ -109,6 +104,9 @@ public class HuskSyncVelocity { // Set instance instance = this; + // Load dependencies + fetchDependencies(); + // Setup logger velocityLogger = new VelocityLogger(logger); @@ -205,6 +203,8 @@ public class HuskSyncVelocity { // Load dependencies private void fetchDependencies() { + VelocityLibraryManager manager = new VelocityLibraryManager<>(logger, dataDirectory, getProxyServer().getPluginManager(), getInstance(), "lib"); + Library mySqlLib = Library.builder() .groupId("mysql") .artifactId("mysql-connector-java") From d3cf99b0e6b3262e744f12c5c6c49806e2247d21 Mon Sep 17 00:00:00 2001 From: William Date: Tue, 14 Dec 2021 20:03:07 +0000 Subject: [PATCH 09/11] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..489fa7aa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright © William278 2021. All rights reserved + +LICENSE +This source code is provided as reference to licensed individuals that have purchased the HuskSync +plugin once from any of the official sources it is provided. The availability of this code does +not grant you the rights to modify, re-distribute, compile or redistribute this source code or +"plugin" outside this intended purpose. This license does not cover libraries developed by third +parties that are utilised in the plugin. + +CONTRIBUTOR AGREEMENT +By contributing code to this repository, contributors agree that they forefeit their contributions +to the copyright holder and only the copyright holder. +In exchange for contributing, the copyright holder may give, at their discretion, permission to use +the plugin in commercial contexts + +DEFINITIONS +"plugin"; the jar file compiled from this source code +"source code"; the java source code and gradle configurations provided in this repository, however +excludes libraries +"copyright holder"; William278 +"contributor(s)"; person(s) who submit (contribute) code through a pull request to this repository From 3c4d95660c06adaddf19850567a012fd63bd0991 Mon Sep 17 00:00:00 2001 From: William Date: Tue, 14 Dec 2021 20:06:29 +0000 Subject: [PATCH 10/11] Update velocity version --- .../src/main/java/me/william278/husksync/HuskSyncVelocity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java index c1a2e5de..c8586ec3 100644 --- a/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java +++ b/velocity/src/main/java/me/william278/husksync/HuskSyncVelocity.java @@ -43,7 +43,7 @@ import static me.william278.husksync.HuskSyncVelocity.VERSION; public class HuskSyncVelocity { // Plugin version - public static final String VERSION = "1.2-dev"; + public static final String VERSION = "1.2"; // Velocity bStats ID (different from Bukkit and BungeeCord) private static final int METRICS_ID = 13489; From c511c3f4af3f4691b7a24856dd6a6af45e33b1be Mon Sep 17 00:00:00 2001 From: William Date: Fri, 17 Dec 2021 01:32:52 +0000 Subject: [PATCH 11/11] Use Alessiodp's libby version again now that 1.1.5 has been merged --- build.gradle | 1 + bungeecord/build.gradle | 4 ++-- velocity/build.gradle | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 9a656549..5e00032e 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ subprojects { maven { url 'https://repo.velocitypowered.com/snapshots/' } maven { url 'https://repo.minebench.de/' } maven { url 'https://repo.codemc.org/repository/maven-public' } + maven { url 'https://repo.alessiodp.com/releases/' } maven { url 'https://jitpack.io' } } } \ No newline at end of file diff --git a/bungeecord/build.gradle b/bungeecord/build.gradle index f5f42835..5c0fba77 100644 --- a/bungeecord/build.gradle +++ b/bungeecord/build.gradle @@ -5,7 +5,7 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-bungeecord:2.2.1' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' - implementation 'com.github.WiIIiam278.libby:libby-bungee:1.1.5' + implementation 'net.byteflux:libby-bungee:1.1.5' compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' } @@ -14,7 +14,7 @@ shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.standard' - relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.bungee' + relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.bungee' } tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file diff --git a/velocity/build.gradle b/velocity/build.gradle index 475df9c8..aac5cba7 100644 --- a/velocity/build.gradle +++ b/velocity/build.gradle @@ -5,7 +5,7 @@ dependencies { compileOnly 'redis.clients:jedis:3.7.0' implementation 'org.bstats:bstats-velocity:2.2.1' implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT' - implementation 'com.github.WiIIiam278.libby:libby-velocity:1.1.5' + implementation 'net.byteflux:libby-velocity:1.1.5' compileOnly 'com.velocitypowered:velocity-api:3.1.0' annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' @@ -15,7 +15,7 @@ shadowJar { relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari' relocate 'org.bstats', 'me.William278.husksync.libraries.plan' relocate 'de.themoep', 'me.William278.husksync.libraries.minedown.adventure' - relocate 'net.byteflux.libby', 'me.William278.husksync.libraries.libby.velocity' + relocate 'net.byteflux', 'me.William278.husksync.libraries.libby.velocity' } tasks.register('prepareKotlinBuildScriptModel'){} \ No newline at end of file