Cluster support

feat/data-edit-commands
William 3 years ago
parent 2367b14738
commit cf6b81200b

@ -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' }

@ -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) {

@ -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", "");

@ -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();
}

@ -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();

@ -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();
}

@ -13,4 +13,5 @@ synchronisation_settings:
game_mode: true
advancements: true
location: false
cluster_id: 'main'
check_for_updates: true

@ -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<Server> synchronisedServers;
private static Database database;
public static Connection getConnection() throws SQLException {
return database.getConnection();
private static HashMap<String,Database> 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) { }
}

@ -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 <player>")).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 <player>")).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 <settingName> <value>
@ -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);

@ -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) {

@ -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<Settings.SynchronisationCluster, PlayerDataCache> 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<Settings.SynchronisationCluster, PlayerData> getPlayerData(UUID playerUUID) {
HashMap<Settings.SynchronisationCluster, PlayerData> 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();

@ -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;
}

@ -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

@ -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

@ -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<Settings.SynchronisationCluster,PlayerData> 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");

@ -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");

@ -36,6 +36,7 @@ public class MPDBMigrator {
public static HashMap<PlayerData, String> incomingPlayerData;
public static MigrationSettings migrationSettings = new MigrationSettings();
private static Settings.SynchronisationCluster targetCluster;
private static Database sourceDatabase;
private static HashSet<MPDBPlayerData> 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";
}
}

@ -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
config_file_version: 1.1

@ -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)'
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)'

@ -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<SynchronisationCluster> 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) {
}
}

@ -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

Loading…
Cancel
Save