diff --git a/build.gradle b/build.gradle index 3adbf1cb..b88cf3ad 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { allprojects { group 'me.William278' - version '1.0.4' + version '1.1' compileJava { options.encoding = 'UTF-8' } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } diff --git a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java index a15d9e9c..0130c14b 100644 --- a/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java +++ b/bukkit/src/main/java/me/william278/husksync/HuskSyncBukkit.java @@ -21,6 +21,7 @@ import java.util.logging.Level; public final class HuskSyncBukkit extends JavaPlugin { + // Bukkit bStats ID (Different to BungeeCord) private static final int METRICS_ID = 13140; private static HuskSyncBukkit instance; @@ -36,7 +37,7 @@ public final class HuskSyncBukkit extends JavaPlugin { // Has a handshake been established with the Bungee? public static boolean handshakeCompleted = false; - // THe handshake task to execute + // The handshake task to execute private static BukkitTask handshakeTask; // Whether MySqlPlayerDataBridge is installed @@ -54,7 +55,7 @@ public final class HuskSyncBukkit extends JavaPlugin { } try { new RedisMessage(RedisMessage.MessageType.CONNECTION_HANDSHAKE, - new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), serverUUID.toString(), Boolean.toString(isMySqlPlayerDataBridgeInstalled), Bukkit.getName(), @@ -74,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), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), serverUUID.toString(), Bukkit.getName()).send(); } catch (IOException e) { diff --git a/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java b/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java index 46fb4f0b..2477a993 100644 --- a/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java +++ b/bukkit/src/main/java/me/william278/husksync/bukkit/config/ConfigLoader.java @@ -8,6 +8,7 @@ public class ConfigLoader { public static void loadSettings(FileConfiguration config) throws IllegalArgumentException { Settings.serverType = Settings.ServerType.BUKKIT; Settings.automaticUpdateChecks = config.getBoolean("check_for_updates", true); + Settings.cluster = config.getString("cluster_id", "main"); Settings.redisHost = config.getString("redis_settings.host", "localhost"); Settings.redisPort = config.getInt("redis_settings.port", 6379); Settings.redisPassword = config.getString("redis_settings.password", ""); 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 ed0c9f1c..ca09a94f 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), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, 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 4c8caf52..c6423c8a 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 @@ -43,6 +43,13 @@ public class BukkitRedisListener extends RedisListener { if (!plugin.isEnabled()) { return; } + // Ignore messages for other clusters if applicable + final String targetClusterId = message.getMessageTarget().targetClusterId(); + if (targetClusterId != null) { + if (!targetClusterId.equalsIgnoreCase(Settings.cluster)) { + return; + } + } // Handle the incoming redis message; either for a specific player or the system if (message.getMessageTarget().targetPlayerUUID() == null) { @@ -90,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), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, 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 d7b2df49..cfc3e4da 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), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, 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), + new RedisMessage.MessageTarget(Settings.ServerType.BUNGEECORD, null, Settings.cluster), playerUUID.toString()).send(); } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index 77b888eb..7250b037 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -13,4 +13,5 @@ synchronisation_settings: game_mode: true advancements: true location: false +cluster_id: 'main' check_for_updates: true \ 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 e99998ea..84750549 100644 --- a/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java +++ b/bungeecord/src/main/java/me/william278/husksync/HuskSyncBungeeCord.java @@ -19,6 +19,7 @@ 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; @@ -26,6 +27,7 @@ import java.util.logging.Level; public final class HuskSyncBungeeCord extends Plugin { + // BungeeCord bStats ID (different to Bukkit) private static final int METRICS_ID = 13141; private static HuskSyncBungeeCord instance; @@ -41,9 +43,9 @@ public final class HuskSyncBungeeCord extends Plugin { */ public static HashSet synchronisedServers; - private static Database database; - public static Connection getConnection() throws SQLException { - return database.getConnection(); + private static HashMap clusterDatabases; + public static Connection getConnection(String clusterId) throws SQLException { + return clusterDatabases.get(clusterId).getConnection(); } public static MPDBMigrator mpdbMigrator; @@ -76,21 +78,29 @@ public final class HuskSyncBungeeCord extends Plugin { } // Initialize the database - database = switch (Settings.dataStorageType) { - case SQLITE -> new SQLite(this); - case MYSQL -> new MySQL(this); - }; - database.load(); - database.createTables(); + 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); + } // Abort loading if the database failed to initialize - if (database.isInactive()) { - getLogger().severe("Failed to initialize the database; HuskSync will now abort loading itself (" + getProxy().getName() + ") v" + getDescription().getVersion()); - return; + 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; + } } + // Setup player data cache - DataManager.playerDataCache = new DataManager.PlayerDataCache(); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + DataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache()); + } // Initialize the redis listener if (!new BungeeRedisListener().isActiveAndEnabled) { @@ -129,7 +139,7 @@ public final class HuskSyncBungeeCord extends Plugin { for (Server server: synchronisedServers) { try { new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, server.clusterId()), server.serverUUID().toString(), ProxyServer.getInstance().getName()).send(); } catch (IOException e) { @@ -138,7 +148,9 @@ public final class HuskSyncBungeeCord extends Plugin { } // Close the database - database.close(); + for (Database database : clusterDatabases.values()) { + database.close(); + } // Log to console getLogger().info("Disabled HuskSync (" + getProxy().getName() + ") v" + getDescription().getVersion()); @@ -147,5 +159,5 @@ public final class HuskSyncBungeeCord extends Plugin { /** * 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) { } + 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 3aec6806..b3783f4e 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 @@ -90,9 +90,24 @@ public class HuskSyncCommand extends Command implements TabExecutor { sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); return; } - if (args.length == 2) { + 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); + openInventory(player, playerName, clusterId); } else { sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax").replaceAll("%1%", "/husksync invsee ")).toComponent()); @@ -103,9 +118,24 @@ public class HuskSyncCommand extends Command implements TabExecutor { sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); return; } - if (args.length == 2) { + 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); + openEnderChest(player, playerName, clusterId); } else { sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax") .replaceAll("%1%", "/husksync echest ")).toComponent()); @@ -124,9 +154,13 @@ public class HuskSyncCommand extends Command implements TabExecutor { sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent()); return; } + int playerDataSize = 0; + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + playerDataSize += DataManager.playerDataCache.get(cluster).playerData.size(); + } sender.sendMessage(new MineDown(MessageManager.PLUGIN_STATUS.toString() .replaceAll("%1%", String.valueOf(HuskSyncBungeeCord.synchronisedServers.size())) - .replaceAll("%2%", String.valueOf(DataManager.playerDataCache.playerData.size()))).toComponent()); + .replaceAll("%2%", String.valueOf(playerDataSize))).toComponent()); } case "reload" -> { if (!player.hasPermission("husksync.command.admin")) { @@ -142,7 +176,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { // Send reload request to all bukkit servers try { new RedisMessage(RedisMessage.MessageType.RELOAD_CONFIG, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null), "reload") .send(); } catch (IOException e) { @@ -200,6 +234,8 @@ public class HuskSyncCommand extends Command implements TabExecutor { sourceInventoryTableName: %6% sourceEnderChestTableName: %7% sourceExperienceTableName: %8% + + targetCluster: %9% To change a setting, type: husksync migrate setting @@ -211,7 +247,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { Redis credentials. Warning: Data will be saved to your configured data - source, which is currently a %9% database. + 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 @@ -228,7 +264,8 @@ public class HuskSyncCommand extends Command implements TabExecutor { .replaceAll("%6%", MPDBMigrator.migrationSettings.inventoryDataTable) .replaceAll("%7%", MPDBMigrator.migrationSettings.enderChestDataTable) .replaceAll("%8%", MPDBMigrator.migrationSettings.expDataTable) - .replaceAll("%9%", Settings.dataStorageType.toString()) + .replaceAll("%9%", MPDBMigrator.migrationSettings.targetCluster) + .replaceAll("%10%", Settings.dataStorageType.toString()) ).toComponent()); case "setting" -> { if (args.length == 4) { @@ -249,6 +286,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { 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; default -> { sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent()); return; @@ -274,7 +312,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { } // View the inventory of a player specified by their name - private void openInventory(ProxiedPlayer viewer, String targetPlayerName) { + private void openInventory(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; @@ -284,26 +322,31 @@ public class HuskSyncCommand extends Command implements TabExecutor { return; } ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - PlayerData playerData = DataManager.getPlayerDataByName(targetPlayerName); - if (playerData == null) { - viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (!cluster.clusterId().equals(clusterId)) continue; + PlayerData playerData = 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.getLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + } return; } - try { - new RedisMessage(RedisMessage.MessageType.OPEN_INVENTORY, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId()), - targetPlayerName, RedisMessage.serialize(playerData)) - .send(); - 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); - } + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); }); } // View the ender chest of a player specified by their name - private void openEnderChest(ProxiedPlayer viewer, String targetPlayerName) { + private 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; @@ -313,21 +356,26 @@ public class HuskSyncCommand extends Command implements TabExecutor { return; } ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - PlayerData playerData = DataManager.getPlayerDataByName(targetPlayerName); - if (playerData == null) { - viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent()); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (!cluster.clusterId().equals(clusterId)) continue; + PlayerData playerData = 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.getLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e); + } return; } - try { - new RedisMessage(RedisMessage.MessageType.OPEN_ENDER_CHEST, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId()), - targetPlayerName, RedisMessage.serialize(playerData)) - .send(); - 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); - } + viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent()); }); } @@ -339,7 +387,7 @@ public class HuskSyncCommand extends Command implements TabExecutor { private void sendAboutInformation(ProxiedPlayer player) { try { new RedisMessage(RedisMessage.MessageType.SEND_PLUGIN_INFORMATION, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, player.getUniqueId()), + 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); 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 0b765126..b117a39b 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 @@ -32,6 +32,15 @@ public class ConfigLoader { Settings.hikariMaximumLifetime = config.getLong("data_storage_settings.hikari_pool_settings.maximum_lifetime", 1800000); Settings.hikariKeepAliveTime = config.getLong("data_storage_settings.hikari_pool_settings.keepalive_time", 0); Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000); + + // Read cluster data + Configuration section = config.getSection("clusters"); + for (String clusterId : section.getKeys()) { + final String playerTableName = config.getString("clusters." + clusterId + ".player_table", "husksync_players"); + final String dataTableName = config.getString("clusters." + clusterId + ".data_table", "husksync_data"); + final String databaseName = config.getString("clusters." + clusterId + ".database", Settings.mySQLDatabase); + Settings.clusters.add(new Settings.SynchronisationCluster(clusterId, databaseName, playerTableName, dataTableName)); + } } public static void loadMessageStrings(Configuration config) { diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java index f3620e9a..5bc4d8fe 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/DataManager.java @@ -2,18 +2,22 @@ package me.william278.husksync.bungeecord.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 java.sql.*; import java.time.Instant; -import java.util.HashSet; -import java.util.UUID; +import java.util.*; import java.util.logging.Level; public class DataManager { private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); - public static PlayerDataCache playerDataCache; + + /** + * The player data cache for each cluster ID + */ + public static HashMap playerDataCache = new HashMap<>(); /** * Checks if the player is registered on the database. @@ -23,10 +27,12 @@ public class DataManager { * @param playerUUID The UUID of the player to register */ public static void ensurePlayerExists(UUID playerUUID, String playerName) { - if (!playerExists(playerUUID)) { - createPlayerEntry(playerUUID, playerName); - } else { - updatePlayerName(playerUUID, playerName); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (!playerExists(playerUUID, cluster)) { + createPlayerEntry(playerUUID, playerName, cluster); + } else { + updatePlayerName(playerUUID, playerName, cluster); + } } } @@ -36,10 +42,10 @@ 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) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + private static boolean playerExists(UUID playerUUID, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "SELECT * FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?;")) { + "SELECT * FROM " + cluster.playerTableName() + " WHERE `uuid`=?;")) { statement.setString(1, playerUUID.toString()); ResultSet resultSet = statement.executeQuery(); return resultSet.next(); @@ -50,10 +56,10 @@ public class DataManager { } } - private static void createPlayerEntry(UUID playerUUID, String playerName) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + private static void createPlayerEntry(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "INSERT INTO " + Database.PLAYER_TABLE_NAME + " (`uuid`,`username`) VALUES(?,?);")) { + "INSERT INTO " + cluster.playerTableName() + " (`uuid`,`username`) VALUES(?,?);")) { statement.setString(1, playerUUID.toString()); statement.setString(2, playerName); statement.executeUpdate(); @@ -63,10 +69,10 @@ public class DataManager { } } - public static void updatePlayerName(UUID playerUUID, String playerName) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + public static void updatePlayerName(UUID playerUUID, String playerName, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "UPDATE " + Database.PLAYER_TABLE_NAME + " SET `username`=? WHERE `uuid`=?;")) { + "UPDATE " + cluster.playerTableName() + " SET `username`=? WHERE `uuid`=?;")) { statement.setString(1, playerName); statement.setString(2, playerUUID.toString()); statement.executeUpdate(); @@ -78,93 +84,105 @@ public class DataManager { /** * Returns a player's PlayerData by their username + * * @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) { + public static PlayerData getPlayerDataByName(String playerName, String clusterId) { PlayerData playerData = null; - try (Connection connection = HuskSyncBungeeCord.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement( - "SELECT * FROM " + Database.PLAYER_TABLE_NAME + " WHERE `username`=? LIMIT 1;")) { - statement.setString(1, playerName); - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - final UUID uuid = UUID.fromString(resultSet.getString("uuid")); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (cluster.clusterId().equals(clusterId)) { + try (Connection connection = HuskSyncBungeeCord.getConnection(clusterId)) { + try (PreparedStatement statement = connection.prepareStatement( + "SELECT * FROM " + cluster.playerTableName() + " WHERE `username`=? LIMIT 1;")) { + statement.setString(1, playerName); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + final UUID uuid = UUID.fromString(resultSet.getString("uuid")); - // Get the player data from the cache if it's there, otherwise pull from SQL - playerData = playerDataCache.getPlayer(uuid); - if (playerData == null) { - playerData = getPlayerData(uuid); + // Get the player data from the cache if it's there, otherwise pull from SQL + playerData = playerDataCache.get(cluster).getPlayer(uuid); + if (playerData == null) { + playerData = Objects.requireNonNull(getPlayerData(uuid)).get(cluster); + } + break; + + } } + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); } + break; } - } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + } return playerData; } - public static PlayerData getPlayerData(UUID playerUUID) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement( - "SELECT * FROM " + Database.DATA_TABLE_NAME + " WHERE `player_id`=(SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?);")) { - statement.setString(1, playerUUID.toString()); - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid")); - //final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp"); - final String serializedInventory = resultSet.getString("inventory"); - final String serializedEnderChest = resultSet.getString("ender_chest"); - final double health = resultSet.getDouble("health"); - final double maxHealth = resultSet.getDouble("max_health"); - final double healthScale = resultSet.getDouble("health_scale"); - final int hunger = resultSet.getInt("hunger"); - final float saturation = resultSet.getFloat("saturation"); - final float saturationExhaustion = resultSet.getFloat("saturation_exhaustion"); - final int selectedSlot = resultSet.getInt("selected_slot"); - final String serializedStatusEffects = resultSet.getString("status_effects"); - final int totalExperience = resultSet.getInt("total_experience"); - final int expLevel = resultSet.getInt("exp_level"); - final float expProgress = resultSet.getFloat("exp_progress"); - final String gameMode = resultSet.getString("game_mode"); - final boolean isFlying = resultSet.getBoolean("is_flying"); - final String serializedAdvancementData = resultSet.getString("advancements"); - final String serializedLocationData = resultSet.getString( "location"); - - final String serializedStatisticData = resultSet.getString("statistics"); + public static Map getPlayerData(UUID playerUUID) { + HashMap data = new HashMap<>(); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + try (Connection connection = HuskSyncBungeeCord.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()); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid")); + //final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp"); + final String serializedInventory = resultSet.getString("inventory"); + final String serializedEnderChest = resultSet.getString("ender_chest"); + final double health = resultSet.getDouble("health"); + final double maxHealth = resultSet.getDouble("max_health"); + final double healthScale = resultSet.getDouble("health_scale"); + final int hunger = resultSet.getInt("hunger"); + final float saturation = resultSet.getFloat("saturation"); + final float saturationExhaustion = resultSet.getFloat("saturation_exhaustion"); + final int selectedSlot = resultSet.getInt("selected_slot"); + final String serializedStatusEffects = resultSet.getString("status_effects"); + final int totalExperience = resultSet.getInt("total_experience"); + final int expLevel = resultSet.getInt("exp_level"); + final float expProgress = resultSet.getFloat("exp_progress"); + final String gameMode = resultSet.getString("game_mode"); + final boolean isFlying = resultSet.getBoolean("is_flying"); + final String serializedAdvancementData = resultSet.getString("advancements"); + final String serializedLocationData = resultSet.getString("location"); + final String serializedStatisticData = resultSet.getString("statistics"); - return new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, - health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects, - totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying, - serializedAdvancementData, serializedLocationData); - } else { - return PlayerData.DEFAULT_PLAYER_DATA(playerUUID); + data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest, + health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects, + totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying, + serializedAdvancementData, serializedLocationData)); + } else { + data.put(cluster, PlayerData.DEFAULT_PLAYER_DATA(playerUUID)); + } } + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); + return null; } - } catch (SQLException e) { - plugin.getLogger().log(Level.SEVERE, "An SQL exception occurred", e); - return null; } + return data; } - public static void updatePlayerData(PlayerData playerData) { + public static 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 - playerDataCache.updatePlayer(playerData); + playerDataCache.get(cluster).updatePlayer(playerData); // SQL: If the player has cached data, update it, otherwise insert new data. - if (playerHasCachedData(playerData.getPlayerUUID())) { - updatePlayerSQLData(playerData); + if (playerHasCachedData(playerData.getPlayerUUID(), cluster)) { + updatePlayerSQLData(playerData, cluster); } else { - insertPlayerData(playerData); + insertPlayerData(playerData, cluster); } } - private static void updatePlayerSQLData(PlayerData playerData) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + private static void updatePlayerSQLData(PlayerData playerData, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "UPDATE " + Database.DATA_TABLE_NAME + " 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 " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?);")) { + "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()); statement.setTimestamp(2, new Timestamp(Instant.now().getEpochSecond())); statement.setString(3, playerData.getSerializedInventory()); @@ -194,10 +212,10 @@ public class DataManager { } } - private static void insertPlayerData(PlayerData playerData) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + private static void insertPlayerData(PlayerData playerData, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "INSERT INTO " + Database.DATA_TABLE_NAME + " (`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 " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);")) { + "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()); statement.setString(2, playerData.getDataVersionUUID().toString()); statement.setTimestamp(3, new Timestamp(Instant.now().getEpochSecond())); @@ -233,10 +251,10 @@ 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) { - try (Connection connection = HuskSyncBungeeCord.getConnection()) { + private static boolean playerHasCachedData(UUID playerUUID, Settings.SynchronisationCluster cluster) { + try (Connection connection = HuskSyncBungeeCord.getConnection(cluster.clusterId())) { try (PreparedStatement statement = connection.prepareStatement( - "SELECT * FROM " + Database.DATA_TABLE_NAME + " WHERE `player_id`=(SELECT `id` FROM " + Database.PLAYER_TABLE_NAME + " WHERE `uuid`=?);")) { + "SELECT * FROM " + cluster.dataTableName() + " WHERE `player_id`=(SELECT `id` FROM " + cluster.playerTableName() + " WHERE `uuid`=?);")) { statement.setString(1, playerUUID.toString()); ResultSet resultSet = statement.executeQuery(); return resultSet.next(); diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java index 1ce23bc9..7c162bc6 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/Database.java @@ -9,12 +9,13 @@ import java.sql.SQLException; public abstract class Database { protected HuskSyncBungeeCord plugin; - public final static String DATA_POOL_NAME = "HuskSyncHikariPool"; - public final static String PLAYER_TABLE_NAME = "husksync_players"; - public final static String DATA_TABLE_NAME = "husksync_data"; + public String dataPoolName; + public Settings.SynchronisationCluster cluster; - public Database(HuskSyncBungeeCord instance) { - plugin = instance; + public Database(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { + this.plugin = instance; + this.cluster = cluster; + this.dataPoolName = "HuskSyncHikariPool-" + cluster.clusterId(); } public abstract Connection getConnection() throws SQLException; @@ -33,9 +34,9 @@ public abstract class Database { public abstract void close(); - public final int hikariMaximumPoolSize = me.william278.husksync.Settings.hikariMaximumPoolSize; - public final int hikariMinimumIdle = me.william278.husksync.Settings.hikariMinimumIdle; - public final long hikariMaximumLifetime = me.william278.husksync.Settings.hikariMaximumLifetime; - public final long hikariKeepAliveTime = me.william278.husksync.Settings.hikariKeepAliveTime; + public final int hikariMaximumPoolSize = Settings.hikariMaximumPoolSize; + public final int hikariMinimumIdle = Settings.hikariMinimumIdle; + public final long hikariMaximumLifetime = Settings.hikariMaximumLifetime; + public final long hikariKeepAliveTime = Settings.hikariKeepAliveTime; public final long hikariConnectionTimeOut = Settings.hikariConnectionTimeOut; } diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java index c68f65c4..317e3a4d 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/MySQL.java @@ -11,8 +11,8 @@ import java.util.logging.Level; public class MySQL extends Database { - final static String[] SQL_SETUP_STATEMENTS = { - "CREATE TABLE IF NOT EXISTS " + PLAYER_TABLE_NAME + " (" + + final String[] SQL_SETUP_STATEMENTS = { + "CREATE TABLE IF NOT EXISTS " + cluster.playerTableName() + " (" + "`id` integer NOT NULL AUTO_INCREMENT," + "`uuid` char(36) NOT NULL UNIQUE," + "`username` varchar(16) NOT NULL," + @@ -20,7 +20,7 @@ public class MySQL extends Database { "PRIMARY KEY (`id`)" + ");", - "CREATE TABLE IF NOT EXISTS " + DATA_TABLE_NAME + " (" + + "CREATE TABLE IF NOT EXISTS " + cluster.dataTableName() + " (" + "`player_id` integer NOT NULL," + "`version_uuid` char(36) NOT NULL UNIQUE," + "`timestamp` datetime NOT NULL," + @@ -44,7 +44,7 @@ public class MySQL extends Database { "`location` text NOT NULL," + "PRIMARY KEY (`player_id`,`version_uuid`)," + - "FOREIGN KEY (`player_id`) REFERENCES " + PLAYER_TABLE_NAME + " (`id`)" + + "FOREIGN KEY (`player_id`) REFERENCES " + cluster.playerTableName() + " (`id`)" + ");" }; @@ -55,12 +55,11 @@ public class MySQL extends Database { public String username = Settings.mySQLUsername; public String password = Settings.mySQLPassword; public String params = Settings.mySQLParams; - public String dataPoolName = DATA_POOL_NAME; private HikariDataSource dataSource; - public MySQL(HuskSyncBungeeCord instance) { - super(instance); + public MySQL(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { + super(instance, cluster); } @Override diff --git a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java index 9e0bb2c5..14dc325b 100644 --- a/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java +++ b/bungeecord/src/main/java/me/william278/husksync/bungeecord/data/sql/SQLite.java @@ -2,6 +2,7 @@ package me.william278.husksync.bungeecord.data.sql; import com.zaxxer.hikari.HikariDataSource; import me.william278.husksync.HuskSyncBungeeCord; +import me.william278.husksync.Settings; import java.io.File; import java.io.IOException; @@ -12,18 +13,18 @@ import java.util.logging.Level; public class SQLite extends Database { - final static String[] SQL_SETUP_STATEMENTS = { + final String[] SQL_SETUP_STATEMENTS = { "PRAGMA foreign_keys = ON;", "PRAGMA encoding = 'UTF-8';", - "CREATE TABLE IF NOT EXISTS " + PLAYER_TABLE_NAME + " (" + + "CREATE TABLE IF NOT EXISTS " + cluster.playerTableName() + " (" + "`id` integer PRIMARY KEY," + "`uuid` char(36) NOT NULL UNIQUE," + "`username` varchar(16) NOT NULL" + ");", - "CREATE TABLE IF NOT EXISTS " + DATA_TABLE_NAME + " (" + - "`player_id` integer NOT NULL REFERENCES " + PLAYER_TABLE_NAME + "(`id`)," + + "CREATE TABLE IF NOT EXISTS " + cluster.dataTableName() + " (" + + "`player_id` integer NOT NULL REFERENCES " + cluster.playerTableName() + "(`id`)," + "`version_uuid` char(36) NOT NULL UNIQUE," + "`timestamp` datetime NOT NULL," + "`inventory` longtext NOT NULL," + @@ -49,24 +50,26 @@ public class SQLite extends Database { ");" }; - private static final String DATABASE_NAME = "HuskSyncData"; + private String getDatabaseName() { + return cluster.databaseName() + "Data"; + } private HikariDataSource dataSource; - public SQLite(HuskSyncBungeeCord instance) { - super(instance); + public SQLite(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) { + super(instance, cluster); } // Create the database file if it does not exist yet private void createDatabaseFileIfNotExist() { - File databaseFile = new File(plugin.getDataFolder(), DATABASE_NAME + ".db"); + File databaseFile = new File(plugin.getDataFolder(), getDatabaseName() + ".db"); if (!databaseFile.exists()) { try { if (!databaseFile.createNewFile()) { - plugin.getLogger().log(Level.SEVERE, "Failed to write new file: " + DATABASE_NAME + ".db (file already exists)"); + plugin.getLogger().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: " + DATABASE_NAME + ".db (" + e.getCause() + ")"); + plugin.getLogger().log(Level.SEVERE, "An error occurred writing a file: " + getDatabaseName() + ".db (" + e.getCause() + ")"); } } } @@ -82,7 +85,7 @@ public class SQLite extends Database { createDatabaseFileIfNotExist(); // Create new HikariCP data source - final String jdbcUrl = "jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + DATABASE_NAME + ".db"; + final String jdbcUrl = "jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + getDatabaseName() + ".db"; dataSource = new HikariDataSource(); dataSource.setDataSourceClassName("org.sqlite.SQLiteDataSource"); dataSource.addDataSourceProperty("url", jdbcUrl); @@ -93,7 +96,7 @@ public class SQLite extends Database { dataSource.setMaxLifetime(hikariMaximumLifetime); dataSource.setKeepaliveTime(hikariKeepAliveTime); dataSource.setConnectionTimeout(hikariConnectionTimeOut); - dataSource.setPoolName(DATA_POOL_NAME); + dataSource.setPoolName(dataPoolName); } @Override 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 cbcd8eb9..fdc21aeb 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 @@ -12,6 +12,7 @@ import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; import java.io.IOException; +import java.util.Map; import java.util.logging.Level; public class BungeeEventListener implements Listener { @@ -26,15 +27,18 @@ public class BungeeEventListener implements Listener { DataManager.ensurePlayerExists(player.getUniqueId(), player.getName()); // Get the player's data from SQL - final PlayerData data = DataManager.getPlayerData(player.getUniqueId()); + final Map data = DataManager.getPlayerData(player.getUniqueId()); // Update the player's data from SQL onto the cache - DataManager.playerDataCache.updatePlayer(data); + assert data != null; + for (Settings.SynchronisationCluster cluster : data.keySet()) { + DataManager.playerDataCache.get(cluster).updatePlayer(data.get(cluster)); + } // Send a message asking the bukkit to request data on join try { - new me.william278.husksync.redis.RedisMessage(me.william278.husksync.redis.RedisMessage.MessageType.REQUEST_DATA_ON_JOIN, - new me.william278.husksync.redis.RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null), + 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.getLogger().log(Level.SEVERE, "Failed to serialize request data on join message data"); 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 9a243325..3a3cacf9 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 @@ -14,6 +14,7 @@ import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.io.IOException; +import java.util.Objects; import java.util.UUID; import java.util.logging.Level; @@ -26,15 +27,21 @@ public class BungeeRedisListener extends RedisListener { listen(); } - private PlayerData getPlayerCachedData(UUID uuid) { - // Get the player data from the cache - PlayerData cachedData = DataManager.playerDataCache.getPlayer(uuid); - if (cachedData != null) { - return cachedData; - } + 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 = DataManager.playerDataCache.get(cluster).getPlayer(uuid); + if (cachedData != null) { + return cachedData; + } - final PlayerData data = DataManager.getPlayerData(uuid); // Get their player data from MySQL - DataManager.playerDataCache.updatePlayer(data); // Update the cache + data = Objects.requireNonNull(DataManager.getPlayerData(uuid)).get(cluster); // Get their player data from MySQL + DataManager.playerDataCache.get(cluster).updatePlayer(data); // Update the cache + break; + } + } return data; // Return the data } @@ -62,13 +69,13 @@ public class BungeeRedisListener extends RedisListener { try { // Send the reply, serializing the message data new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID), - RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID))) + 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), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString()) .send(); @@ -96,7 +103,12 @@ public class BungeeRedisListener extends RedisListener { } // Update the data in the cache and SQL - DataManager.updatePlayerData(playerData); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (cluster.clusterId().equals(message.getMessageTarget().targetClusterId())) { + DataManager.updatePlayerData(playerData, cluster); + break; + } + } // Reply with the player data if they are still online (switching server) try { @@ -104,7 +116,7 @@ public class BungeeRedisListener extends RedisListener { if (player != null) { if (player.isConnected()) { new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID()), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()), RedisMessage.serialize(playerData)) .send(); @@ -125,12 +137,12 @@ public class BungeeRedisListener extends RedisListener { final String huskSyncVersion = message.getMessageDataElements()[3]; try { new RedisMessage(RedisMessage.MessageType.CONNECTION_HANDSHAKE, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()), serverUUID.toString(), plugin.getProxy().getName()) .send(); HuskSyncBungeeCord.synchronisedServers.add( new HuskSyncBungeeCord.Server(serverUUID, hasMySqlPlayerDataBridge, - huskSyncVersion, bukkitBrand)); + 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"); 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 374a16d5..890e0bc8 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 @@ -36,6 +36,7 @@ public class MPDBMigrator { public static HashMap incomingPlayerData; public static MigrationSettings migrationSettings = new MigrationSettings(); + private static Settings.SynchronisationCluster targetCluster; private static Database sourceDatabase; private static HashSet mpdbPlayerData; @@ -59,6 +60,18 @@ public class MPDBMigrator { return; } + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + if (migrationSettings.targetCluster.equals(cluster.clusterId())) { + targetCluster = cluster; + break; + } + } + if (targetCluster == null) { + plugin.getLogger().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; + } + migratedDataSent = 0; playersMigrated = 0; mpdbPlayerData = new HashSet<>(); @@ -91,11 +104,11 @@ 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()) { - try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + Database.PLAYER_TABLE_NAME + ";")) { + try (Connection connection = HuskSyncBungeeCord.getConnection(targetCluster.clusterId())) { + try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) { statement.executeUpdate(); } - try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + Database.DATA_TABLE_NAME + ";")) { + try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.dataTableName() + ";")) { statement.executeUpdate(); } } catch (SQLException e) { @@ -182,7 +195,7 @@ public class MPDBMigrator { for (MPDBPlayerData data : mpdbPlayerData) { try { new RedisMessage(RedisMessage.MessageType.DECODE_MPDB_DATA, - new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null), + new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null), processingServer.serverUUID().toString(), RedisMessage.serialize(data)) .send(); @@ -214,7 +227,10 @@ public class MPDBMigrator { DataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); // Update the data in the cache and SQL - DataManager.updatePlayerData(playerData); + for (Settings.SynchronisationCluster cluster : Settings.clusters) { + DataManager.updatePlayerData(playerData, cluster); + break; + } playersSaved++; plugin.getLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); @@ -250,6 +266,8 @@ public class MPDBMigrator { public String enderChestDataTable; public String expDataTable; + public String targetCluster; + public MigrationSettings() { sourceHost = "localhost"; sourcePort = 3306; @@ -257,6 +275,8 @@ public class MPDBMigrator { sourceUsername = "root"; sourcePassword = "pa55w0rd"; + targetCluster = "main"; + inventoryDataTable = "mpdb_inventory"; enderChestDataTable = "mpdb_enderchest"; expDataTable = "mpdb_experience"; @@ -268,14 +288,14 @@ public class MPDBMigrator { */ public static class MigratorMySQL extends MySQL { public MigratorMySQL(HuskSyncBungeeCord instance, String host, int port, String database, String username, String password) { - super(instance); + super(instance, null); super.host = host; super.port = port; super.database = database; super.username = username; super.password = password; super.params = "?useSSL=false"; - super.dataPoolName = DATA_POOL_NAME + "Migrator"; + super.dataPoolName = super.dataPoolName + "Migrator"; } } diff --git a/bungeecord/src/main/resources/bungee-config.yml b/bungeecord/src/main/resources/bungee-config.yml index 69edf94f..e561c22a 100644 --- a/bungeecord/src/main/resources/bungee-config.yml +++ b/bungeecord/src/main/resources/bungee-config.yml @@ -18,5 +18,10 @@ data_storage_settings: maximum_lifetime: 1800000 keepalive_time: 0 connection_timeout: 5000 +clusters: + main: + player_table: 'husksync_players' + data_table: 'husksync_data' + check_for_updates: true -config_file_version: 1.0.2 \ No newline at end of file +config_file_version: 1.1 \ No newline at end of file diff --git a/bungeecord/src/main/resources/languages/en-gb.yml b/bungeecord/src/main/resources/languages/en-gb.yml index d7bd97b5..cedb844b 100644 --- a/bungeecord/src/main/resources/languages/en-gb.yml +++ b/bungeecord/src/main/resources/languages/en-gb.yml @@ -10,4 +10,5 @@ error_cannot_view_ender_chest_online: '[Error:](#ff3300) [You can''t access the error_cannot_view_own_inventory: '[Error:](#ff3300) [You can''t access your own inventory!](#ff7e5e)' error_cannot_view_own_ender_chest: '[Error:](#ff3300) [You can''t access your own ender chest!](#ff7e5e)' error_console_command_only: '[Error:](#ff3300) [That command can only be run through the %1% console](#ff7e5e)' -error_no_servers_proxied: '[Error:](#ff3300) [Failed to process operation; no servers are online that have HuskSync installed. Please ensure HuskSync is installed on both the Proxy server and all servers you wish to synchronise data between.](#ff7e5e)' \ No newline at end of file +error_no_servers_proxied: '[Error:](#ff3300) [Failed to process operation; no servers are online that have HuskSync installed. Please ensure HuskSync is installed on both the Proxy server and all servers you wish to synchronise data between.](#ff7e5e)' +error_invalid_cluster: '[Error:](#ff3300) [Please specify the ID of a valid cluster.](#ff7e5e)' \ No newline at end of file diff --git a/common/src/main/java/me/william278/husksync/Settings.java b/common/src/main/java/me/william278/husksync/Settings.java index 97f97a2d..88b6ad89 100644 --- a/common/src/main/java/me/william278/husksync/Settings.java +++ b/common/src/main/java/me/william278/husksync/Settings.java @@ -1,5 +1,7 @@ package me.william278.husksync; +import java.util.ArrayList; + /** * Settings class, holds values loaded from the plugin config (either Bukkit or Bungee) */ @@ -27,6 +29,9 @@ public class Settings { // Messages language public static String language; + // Cluster IDs + public static ArrayList clusters = new ArrayList<>(); + // SQL settings public static DataStorageType dataStorageType; @@ -61,6 +66,9 @@ public class Settings { public static boolean syncAdvancements; public static boolean syncLocation; + // This Cluster ID + public static String cluster; + /* * Enum definitions */ @@ -74,4 +82,10 @@ public class Settings { MYSQL, SQLITE } + + /** + * Defines information for a synchronisation cluster as listed on the proxy + */ + public record SynchronisationCluster(String clusterId, String databaseName, String playerTableName, String dataTableName) { + } } diff --git a/common/src/main/java/me/william278/husksync/redis/RedisMessage.java b/common/src/main/java/me/william278/husksync/redis/RedisMessage.java index 209aeee8..4383300c 100644 --- a/common/src/main/java/me/william278/husksync/redis/RedisMessage.java +++ b/common/src/main/java/me/william278/husksync/redis/RedisMessage.java @@ -159,7 +159,7 @@ public class RedisMessage { * A record that defines the target of a plugin message; a spigot server or the proxy server(s). * For Bukkit servers, the name of the server must also be specified */ - public record MessageTarget(Settings.ServerType targetServerType, UUID targetPlayerUUID) implements Serializable { } + public record MessageTarget(Settings.ServerType targetServerType, UUID targetPlayerUUID, String targetClusterId) implements Serializable { } /** * Deserialize an object from a Base64 string