Add command and migrator

feat/data-edit-commands
William 3 years ago
parent e7822baa99
commit 635fefba3d

@ -1,12 +1,12 @@
package me.william278.husksync; package me.william278.husksync;
import me.william278.husksync.bungeecord.command.HuskSyncCommand; import me.william278.husksync.bungeecord.command.BungeeCommand;
import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigLoader;
import me.william278.husksync.bungeecord.config.ConfigManager; import me.william278.husksync.bungeecord.config.ConfigManager;
import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.bungeecord.listener.BungeeEventListener; import me.william278.husksync.bungeecord.listener.BungeeEventListener;
import me.william278.husksync.bungeecord.listener.BungeeRedisListener; import me.william278.husksync.bungeecord.listener.BungeeRedisListener;
import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.bungeecord.util.BungeeLogger; import me.william278.husksync.bungeecord.util.BungeeLogger;
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
@ -104,10 +104,10 @@ public final class HuskSyncBungeeCord extends Plugin {
getProxy().getPluginManager().registerListener(this, new BungeeEventListener()); getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
// Register command // Register command
getProxy().getPluginManager().registerCommand(this, new HuskSyncCommand()); getProxy().getPluginManager().registerCommand(this, new BungeeCommand());
// Prepare the migrator for use if needed // Prepare the migrator for use if needed
mpdbMigrator = new MPDBMigrator(); mpdbMigrator = new MPDBMigrator(getBungeeLogger());
// Initialize bStats metrics // Initialize bStats metrics
try { try {

@ -4,12 +4,13 @@ import de.themoep.minedown.MineDown;
import me.william278.husksync.HuskSyncBungeeCord; import me.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.Server; import me.william278.husksync.Server;
import me.william278.husksync.bungeecord.util.BungeeUpdateChecker; import me.william278.husksync.bungeecord.util.BungeeUpdateChecker;
import me.william278.husksync.proxy.command.HuskSyncCommand;
import me.william278.husksync.util.MessageManager; import me.william278.husksync.util.MessageManager;
import me.william278.husksync.PlayerData; import me.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import me.william278.husksync.Settings;
import me.william278.husksync.bungeecord.config.ConfigLoader; import me.william278.husksync.bungeecord.config.ConfigLoader;
import me.william278.husksync.bungeecord.config.ConfigManager; import me.william278.husksync.bungeecord.config.ConfigManager;
import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@ -25,17 +26,11 @@ import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class HuskSyncCommand extends Command implements TabExecutor { public class BungeeCommand extends Command implements TabExecutor, HuskSyncCommand {
private final static HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); private final static HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance();
private final static SubCommand[] SUB_COMMANDS = {new SubCommand("about", null),
new SubCommand("status", "husksync.command.admin"),
new SubCommand("reload", "husksync.command.admin"),
new SubCommand("update", "husksync.command.admin"),
new SubCommand("invsee", "husksync.command.inventory"),
new SubCommand("echest", "husksync.command.ender_chest")};
public HuskSyncCommand() { public BungeeCommand() {
super("husksync", null, "hs"); super("husksync", null, "hs");
} }
@ -195,6 +190,7 @@ public class HuskSyncCommand extends Command implements TabExecutor {
// Database migration wizard // Database migration wizard
if (args.length >= 1) { if (args.length >= 1) {
if (args[0].equalsIgnoreCase("migrate")) { if (args[0].equalsIgnoreCase("migrate")) {
MPDBMigrator migrator = HuskSyncBungeeCord.mpdbMigrator;
if (args.length == 1) { if (args.length == 1) {
sender.sendMessage(new MineDown( sender.sendMessage(new MineDown(
""" """
@ -210,7 +206,7 @@ public class HuskSyncCommand extends Command implements TabExecutor {
Other non-vital data, such as current health, hunger Other non-vital data, such as current health, hunger
& potion effects will not be migrated to ensure that & potion effects will not be migrated to ensure that
migration does not take an excessive amount of time. migration does not take an excessive amount of time.
To do this, you need to have MySqlPlayerDataBridge To do this, you need to have MySqlPlayerDataBridge
and HuskSync installed on one Spigot server as well and HuskSync installed on one Spigot server as well
as HuskSync installed on the proxy (which you have) as HuskSync installed on the proxy (which you have)
@ -234,7 +230,7 @@ public class HuskSyncCommand extends Command implements TabExecutor {
sourceInventoryTableName: %6% sourceInventoryTableName: %6%
sourceEnderChestTableName: %7% sourceEnderChestTableName: %7%
sourceExperienceTableName: %8% sourceExperienceTableName: %8%
targetCluster: %9% targetCluster: %9%
To change a setting, type: To change a setting, type:
@ -256,37 +252,37 @@ public class HuskSyncCommand extends Command implements TabExecutor {
data from the source MySQLPlayerDataBridge database. data from the source MySQLPlayerDataBridge database.
>When done, type: husksync migrate start""" >When done, type: husksync migrate start"""
.replaceAll("%1%", MPDBMigrator.migrationSettings.sourceHost) .replaceAll("%1%", migrator.migrationSettings.sourceHost)
.replaceAll("%2%", String.valueOf(MPDBMigrator.migrationSettings.sourcePort)) .replaceAll("%2%", String.valueOf(migrator.migrationSettings.sourcePort))
.replaceAll("%3%", MPDBMigrator.migrationSettings.sourceDatabase) .replaceAll("%3%", migrator.migrationSettings.sourceDatabase)
.replaceAll("%4%", MPDBMigrator.migrationSettings.sourceUsername) .replaceAll("%4%", migrator.migrationSettings.sourceUsername)
.replaceAll("%5%", MPDBMigrator.migrationSettings.sourcePassword) .replaceAll("%5%", migrator.migrationSettings.sourcePassword)
.replaceAll("%6%", MPDBMigrator.migrationSettings.inventoryDataTable) .replaceAll("%6%", migrator.migrationSettings.inventoryDataTable)
.replaceAll("%7%", MPDBMigrator.migrationSettings.enderChestDataTable) .replaceAll("%7%", migrator.migrationSettings.enderChestDataTable)
.replaceAll("%8%", MPDBMigrator.migrationSettings.expDataTable) .replaceAll("%8%", migrator.migrationSettings.expDataTable)
.replaceAll("%9%", MPDBMigrator.migrationSettings.targetCluster) .replaceAll("%9%", migrator.migrationSettings.targetCluster)
.replaceAll("%10%", Settings.dataStorageType.toString()) .replaceAll("%10%", Settings.dataStorageType.toString())
).toComponent()); ).toComponent());
case "setting" -> { case "setting" -> {
if (args.length == 4) { if (args.length == 4) {
String value = args[3]; String value = args[3];
switch (args[2]) { switch (args[2]) {
case "sourceHost", "host" -> MPDBMigrator.migrationSettings.sourceHost = value; case "sourceHost", "host" -> migrator.migrationSettings.sourceHost = value;
case "sourcePort", "port" -> { case "sourcePort", "port" -> {
try { try {
MPDBMigrator.migrationSettings.sourcePort = Integer.parseInt(value); migrator.migrationSettings.sourcePort = Integer.parseInt(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
sender.sendMessage(new MineDown("Error: Invalid value; port must be a number").toComponent()); sender.sendMessage(new MineDown("Error: Invalid value; port must be a number").toComponent());
return; return;
} }
} }
case "sourceDatabase", "database" -> MPDBMigrator.migrationSettings.sourceDatabase = value; case "sourceDatabase", "database" -> migrator.migrationSettings.sourceDatabase = value;
case "sourceUsername", "username" -> MPDBMigrator.migrationSettings.sourceUsername = value; case "sourceUsername", "username" -> migrator.migrationSettings.sourceUsername = value;
case "sourcePassword", "password" -> MPDBMigrator.migrationSettings.sourcePassword = value; case "sourcePassword", "password" -> migrator.migrationSettings.sourcePassword = value;
case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> MPDBMigrator.migrationSettings.inventoryDataTable = value; case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> migrator.migrationSettings.inventoryDataTable = value;
case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> MPDBMigrator.migrationSettings.enderChestDataTable = value; case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> migrator.migrationSettings.enderChestDataTable = value;
case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> MPDBMigrator.migrationSettings.expDataTable = value; case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> migrator.migrationSettings.expDataTable = value;
case "targetCluster", "cluster" -> MPDBMigrator.migrationSettings.targetCluster = value; case "targetCluster", "cluster" -> migrator.migrationSettings.targetCluster = value;
default -> { default -> {
sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent()); sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent());
return; return;
@ -299,7 +295,14 @@ public class HuskSyncCommand extends Command implements TabExecutor {
} }
case "start" -> { case "start" -> {
sender.sendMessage(new MineDown("Starting MySQLPlayerDataBridge migration!...").toComponent()); sender.sendMessage(new MineDown("Starting MySQLPlayerDataBridge migration!...").toComponent());
HuskSyncBungeeCord.mpdbMigrator.start();
// If the migrator is ready, execute the migration asynchronously
if (HuskSyncBungeeCord.mpdbMigrator.readyToMigrate(ProxyServer.getInstance().getOnlineCount(),
HuskSyncBungeeCord.synchronisedServers)) {
ProxyServer.getInstance().getScheduler().runAsync(plugin, () ->
HuskSyncBungeeCord.mpdbMigrator.executeMigrationOperations(HuskSyncBungeeCord.dataManager,
HuskSyncBungeeCord.synchronisedServers));
}
} }
default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent()); default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent());
} }
@ -346,7 +349,7 @@ public class HuskSyncCommand extends Command implements TabExecutor {
} }
// View the ender chest of a player specified by their name // View the ender chest of a player specified by their name
private void openEnderChest(ProxiedPlayer viewer, String targetPlayerName, String clusterId) { public void openEnderChest(ProxiedPlayer viewer, String targetPlayerName, String clusterId) {
if (viewer.getName().equalsIgnoreCase(targetPlayerName)) { if (viewer.getName().equalsIgnoreCase(targetPlayerName)) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent()); viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent());
return; return;
@ -401,9 +404,12 @@ public class HuskSyncCommand extends Command implements TabExecutor {
if (args.length == 1) { if (args.length == 1) {
final ArrayList<String> subCommands = new ArrayList<>(); final ArrayList<String> subCommands = new ArrayList<>();
for (SubCommand subCommand : SUB_COMMANDS) { for (SubCommand subCommand : SUB_COMMANDS) {
if (subCommand.doesPlayerHavePermission(player)) { if (subCommand.permission() != null) {
subCommands.add(subCommand.command()); if (!player.hasPermission(subCommand.permission())) {
continue;
}
} }
subCommands.add(subCommand.command());
} }
// Automatically filter the sub commands' order in tab completion by what the player has typed // Automatically filter the sub commands' order in tab completion by what the player has typed
return subCommands.stream().filter(val -> val.startsWith(args[0])) return subCommands.stream().filter(val -> val.startsWith(args[0]))
@ -415,19 +421,4 @@ public class HuskSyncCommand extends Command implements TabExecutor {
return Collections.emptyList(); return Collections.emptyList();
} }
/**
* A sub command, that may require a permission
*/
public record SubCommand(String command, String permission) {
/**
* Returns if the player can use the sub command
*
* @param player The {@link ProxiedPlayer} to check
* @return {@code true} if the player can use the sub command; {@code false} otherwise
*/
public boolean doesPlayerHavePermission(ProxiedPlayer player) {
return permission == null || player.hasPermission(permission);
}
}
} }

@ -6,7 +6,7 @@ import me.william278.husksync.Server;
import me.william278.husksync.util.MessageManager; import me.william278.husksync.util.MessageManager;
import me.william278.husksync.PlayerData; import me.william278.husksync.PlayerData;
import me.william278.husksync.Settings; import me.william278.husksync.Settings;
import me.william278.husksync.bungeecord.migrator.MPDBMigrator; import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.redis.RedisListener; import me.william278.husksync.redis.RedisListener;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
@ -181,16 +181,20 @@ public class BungeeRedisListener extends RedisListener {
return; return;
} }
// Get the migrator
MPDBMigrator migrator = HuskSyncBungeeCord.mpdbMigrator;
// Add the incoming data to the data to be saved // Add the incoming data to the data to be saved
MPDBMigrator.incomingPlayerData.put(playerData, playerName); migrator.incomingPlayerData.put(playerData, playerName);
// Increment players migrated // Increment players migrated
MPDBMigrator.playersMigrated++; migrator.playersMigrated++;
plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players."); plugin.getBungeeLogger().log(Level.INFO, "Migrated " + migrator.playersMigrated + "/" + migrator.migratedDataSent + " players.");
// When all the data has been received, save it // When all the data has been received, save it
if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) { if (migrator.migratedDataSent == migrator.playersMigrated) {
MPDBMigrator.loadIncomingData(MPDBMigrator.incomingPlayerData); ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> migrator.loadIncomingData(migrator.incomingPlayerData,
HuskSyncBungeeCord.dataManager));
} }
} }
} }

@ -1,14 +1,13 @@
package me.william278.husksync.bungeecord.migrator; package me.william278.husksync.migrator;
import me.william278.husksync.HuskSyncBungeeCord;
import me.william278.husksync.PlayerData; import me.william278.husksync.PlayerData;
import me.william278.husksync.Server; import me.william278.husksync.Server;
import me.william278.husksync.Settings; import me.william278.husksync.Settings;
import me.william278.husksync.migrator.MPDBPlayerData; import me.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.proxy.data.sql.Database; import me.william278.husksync.proxy.data.sql.Database;
import me.william278.husksync.proxy.data.sql.MySQL; import me.william278.husksync.proxy.data.sql.MySQL;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import net.md_5.bungee.api.ProxyServer; import me.william278.husksync.util.Logger;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection; import java.sql.Connection;
@ -28,36 +27,40 @@ import java.util.logging.Level;
*/ */
public class MPDBMigrator { public class MPDBMigrator {
public static int migratedDataSent = 0; public int migratedDataSent = 0;
public static int playersMigrated = 0; public int playersMigrated = 0;
private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance(); public HashMap<PlayerData, String> incomingPlayerData;
public static HashMap<PlayerData, String> incomingPlayerData; public MigrationSettings migrationSettings = new MigrationSettings();
private Settings.SynchronisationCluster targetCluster;
private Database sourceDatabase;
public static MigrationSettings migrationSettings = new MigrationSettings(); private HashSet<MPDBPlayerData> mpdbPlayerData;
private static Settings.SynchronisationCluster targetCluster;
private static Database sourceDatabase;
private static HashSet<MPDBPlayerData> mpdbPlayerData; private final Logger logger;
public void start() { public MPDBMigrator(Logger logger) {
if (ProxyServer.getInstance().getPlayers().size() > 0) { this.logger = logger;
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because there are players online. " + }
public boolean readyToMigrate(int networkPlayerCount, HashSet<Server> synchronisedServers) {
if (networkPlayerCount > 0) {
logger.log(Level.WARNING, "Failed to start migration because there are players online. " +
"Your network has to be empty to migrate data for safety reasons."); "Your network has to be empty to migrate data for safety reasons.");
return; return false;
} }
int synchronisedServersWithMpdb = 0; int synchronisedServersWithMpdb = 0;
for (Server server : HuskSyncBungeeCord.synchronisedServers) { for (Server server : synchronisedServers) {
if (server.hasMySqlPlayerDataBridge()) { if (server.hasMySqlPlayerDataBridge()) {
synchronisedServersWithMpdb++; synchronisedServersWithMpdb++;
} }
} }
if (synchronisedServersWithMpdb < 1) { if (synchronisedServersWithMpdb < 1) {
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " + logger.log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " +
"Please start one Spigot server with HuskSync installed to begin migration."); "Please start one Spigot server with HuskSync installed to begin migration.");
return; return false;
} }
for (Settings.SynchronisationCluster cluster : Settings.clusters) { for (Settings.SynchronisationCluster cluster : Settings.clusters) {
@ -67,9 +70,9 @@ public class MPDBMigrator {
} }
} }
if (targetCluster == null) { if (targetCluster == null) {
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " + logger.log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " +
"Please ensure the target cluster is correct, configured in the proxy config file, then try again"); "Please ensure the target cluster is correct, configured in the proxy config file, then try again");
return; return false;
} }
migratedDataSent = 0; migratedDataSent = 0;
@ -79,32 +82,40 @@ public class MPDBMigrator {
final MigrationSettings settings = migrationSettings; final MigrationSettings settings = migrationSettings;
// Get connection to source database // Get connection to source database
sourceDatabase = new MigratorMySQL(plugin, settings.sourceHost, settings.sourcePort, sourceDatabase = new MigratorMySQL(logger, settings.sourceHost, settings.sourcePort,
settings.sourceDatabase, settings.sourceUsername, settings.sourcePassword, targetCluster); settings.sourceDatabase, settings.sourceUsername, settings.sourcePassword, targetCluster);
sourceDatabase.load(); sourceDatabase.load();
if (sourceDatabase.isInactive()) { if (sourceDatabase.isInactive()) {
plugin.getBungeeLogger().log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " + logger.log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " +
"Please check you have input the correct connection details and try again."); "Please check you have input the correct connection details and try again.");
return; return false;
} }
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { return true;
prepareTargetDatabase(); }
getInventoryData(); // Carry out the migration
public void executeMigrationOperations(DataManager dataManager, HashSet<Server> synchronisedServers) {
// Prepare the target database for insertion
prepareTargetDatabase(dataManager);
getEnderChestData(); // Fetch inventory data from MPDB
getInventoryData();
getExperienceData(); // Fetch ender chest data from MPDB
getEnderChestData();
sendEncodedData(); // Fetch experience data from MPDB
}); getExperienceData();
// Send the encoded data to the Bukkit servers for conversion
sendEncodedData(synchronisedServers);
} }
// Clear the new database out of current data // Clear the new database out of current data
private void prepareTargetDatabase() { private void prepareTargetDatabase(DataManager dataManager) {
plugin.getBungeeLogger().log(Level.INFO, "Preparing target database..."); logger.log(Level.INFO, "Preparing target database...");
try (Connection connection = HuskSyncBungeeCord.dataManager.getConnection(targetCluster.clusterId())) { try (Connection connection = dataManager.getConnection(targetCluster.clusterId())) {
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) { try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) {
statement.executeUpdate(); statement.executeUpdate();
} }
@ -112,14 +123,14 @@ public class MPDBMigrator {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred preparing the target database", e); logger.log(Level.SEVERE, "An exception occurred preparing the target database", e);
} finally { } finally {
plugin.getBungeeLogger().log(Level.INFO, "Finished preparing target database!"); logger.log(Level.INFO, "Finished preparing target database!");
} }
} }
private void getInventoryData() { private void getInventoryData() {
plugin.getBungeeLogger().log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge..."); logger.log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge...");
try (Connection connection = sourceDatabase.getConnection()) { try (Connection connection = sourceDatabase.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.inventoryDataTable + ";")) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.inventoryDataTable + ";")) {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
@ -135,14 +146,14 @@ public class MPDBMigrator {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting inventory data", e); logger.log(Level.SEVERE, "An exception occurred getting inventory data", e);
} finally { } finally {
plugin.getBungeeLogger().log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge"); logger.log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge");
} }
} }
private void getEnderChestData() { private void getEnderChestData() {
plugin.getBungeeLogger().log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge..."); logger.log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge...");
try (Connection connection = sourceDatabase.getConnection()) { try (Connection connection = sourceDatabase.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.enderChestDataTable + ";")) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.enderChestDataTable + ";")) {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
@ -158,14 +169,14 @@ public class MPDBMigrator {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e); logger.log(Level.SEVERE, "An exception occurred getting ender chest data", e);
} finally { } finally {
plugin.getBungeeLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge"); logger.log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge");
} }
} }
private void getExperienceData() { private void getExperienceData() {
plugin.getBungeeLogger().log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge..."); logger.log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge...");
try (Connection connection = sourceDatabase.getConnection()) { try (Connection connection = sourceDatabase.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.expDataTable + ";")) { try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.expDataTable + ";")) {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
@ -183,14 +194,14 @@ public class MPDBMigrator {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting experience data", e); logger.log(Level.SEVERE, "An exception occurred getting experience data", e);
} finally { } finally {
plugin.getBungeeLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge"); logger.log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge");
} }
} }
private void sendEncodedData() { private void sendEncodedData(HashSet<Server> synchronisedServers) {
for (Server processingServer : HuskSyncBungeeCord.synchronisedServers) { for (Server processingServer : synchronisedServers) {
if (processingServer.hasMySqlPlayerDataBridge()) { if (processingServer.hasMySqlPlayerDataBridge()) {
for (MPDBPlayerData data : mpdbPlayerData) { for (MPDBPlayerData data : mpdbPlayerData) {
try { try {
@ -201,10 +212,10 @@ public class MPDBMigrator {
.send(); .send();
migratedDataSent++; migratedDataSent++;
} catch (IOException e) { } catch (IOException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize encoded MPDB data", e); logger.log(Level.SEVERE, "Failed to serialize encoded MPDB data", e);
} }
} }
plugin.getBungeeLogger().log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish"); logger.log(Level.INFO, "Finished dispatching encoded data for " + migratedDataSent + " players; please wait for conversion to finish");
} }
return; return;
} }
@ -215,41 +226,39 @@ public class MPDBMigrator {
* *
* @param dataToLoad HashMap of the {@link PlayerData} to player Usernames that will be loaded * @param dataToLoad HashMap of the {@link PlayerData} to player Usernames that will be loaded
*/ */
public static void loadIncomingData(HashMap<PlayerData, String> dataToLoad) { public void loadIncomingData(HashMap<PlayerData, String> dataToLoad, DataManager dataManager) {
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { int playersSaved = 0;
int playersSaved = 0; logger.log(Level.INFO, "Saving data for " + playersMigrated + " players...");
plugin.getBungeeLogger().log(Level.INFO, "Saving data for " + playersMigrated + " players...");
for (PlayerData playerData : dataToLoad.keySet()) {
String playerName = dataToLoad.get(playerData);
// Add the player to the MySQL table for (PlayerData playerData : dataToLoad.keySet()) {
HuskSyncBungeeCord.dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName); String playerName = dataToLoad.get(playerData);
// Update the data in the cache and SQL // Add the player to the MySQL table
for (Settings.SynchronisationCluster cluster : Settings.clusters) { dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName);
HuskSyncBungeeCord.dataManager.updatePlayerData(playerData, cluster);
break;
}
playersSaved++; // Update the data in the cache and SQL
plugin.getBungeeLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players"); for (Settings.SynchronisationCluster cluster : Settings.clusters) {
dataManager.updatePlayerData(playerData, cluster);
break;
} }
// Mark as done when done playersSaved++;
plugin.getBungeeLogger().log(Level.INFO, """ logger.log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players");
=== MySQLPlayerDataBridge Migration Wizard ========== }
Migration complete! // Mark as done when done
logger.log(Level.INFO, """
Successfully migrated data for %1%/%2% players. === MySQLPlayerDataBridge Migration Wizard ==========
You should now uninstall MySQLPlayerDataBridge from Migration complete!
the rest of the Spigot servers, then restart them.
""".replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated)) Successfully migrated data for %1%/%2% players.
.replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent)));
sourceDatabase.close(); // Close source database You should now uninstall MySQLPlayerDataBridge from
}); the rest of the Spigot servers, then restart them.
""".replaceAll("%1%", Integer.toString(playersMigrated))
.replaceAll("%2%", Integer.toString(migratedDataSent)));
sourceDatabase.close(); // Close source database
} }
/** /**
@ -287,8 +296,8 @@ public class MPDBMigrator {
* MySQL class used for importing data from MPDB * MySQL class used for importing data from MPDB
*/ */
public static class MigratorMySQL extends MySQL { public static class MigratorMySQL extends MySQL {
public MigratorMySQL(HuskSyncBungeeCord instance, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) { public MigratorMySQL(Logger logger, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) {
super(cluster, instance.getBungeeLogger()); super(cluster, logger);
super.host = host; super.host = host;
super.port = port; super.port = port;
super.database = database; super.database = database;

@ -0,0 +1,17 @@
package me.william278.husksync.proxy.command;
public interface HuskSyncCommand {
SubCommand[] SUB_COMMANDS = {new SubCommand("about", null),
new SubCommand("status", "husksync.command.admin"),
new SubCommand("reload", "husksync.command.admin"),
new SubCommand("update", "husksync.command.admin"),
new SubCommand("invsee", "husksync.command.inventory"),
new SubCommand("echest", "husksync.command.ender_chest")};
/**
* A sub command, that may require a permission
*/
record SubCommand(String command, String permission) { }
}

@ -9,10 +9,11 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.data.DataManager; import me.william278.husksync.proxy.data.DataManager;
import me.william278.husksync.redis.RedisMessage; import me.william278.husksync.redis.RedisMessage;
import me.william278.husksync.velocity.VelocityUpdateChecker; import me.william278.husksync.velocity.VelocityUpdateChecker;
import me.william278.husksync.velocity.command.HuskSyncCommand; import me.william278.husksync.velocity.command.VelocityCommand;
import me.william278.husksync.velocity.config.ConfigLoader; import me.william278.husksync.velocity.config.ConfigLoader;
import me.william278.husksync.velocity.config.ConfigManager; import me.william278.husksync.velocity.config.ConfigManager;
import me.william278.husksync.velocity.listener.VelocityEventListener; import me.william278.husksync.velocity.listener.VelocityEventListener;
@ -65,7 +66,7 @@ public class HuskSyncVelocity {
public static DataManager dataManager; public static DataManager dataManager;
//public static MPDBMigrator mpdbMigrator; public static MPDBMigrator mpdbMigrator;
private final Logger logger; private final Logger logger;
private final ProxyServer server; private final ProxyServer server;
@ -108,13 +109,13 @@ public class HuskSyncVelocity {
ConfigManager.loadConfig(); ConfigManager.loadConfig();
// Load settings from config // Load settings from config
ConfigLoader.loadSettings(ConfigManager.getConfig()); ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig()));
// Load messages // Load messages
ConfigManager.loadMessages(); ConfigManager.loadMessages();
// Load locales from messages // Load locales from messages
ConfigLoader.loadMessageStrings(ConfigManager.getMessages()); ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages()));
// Do update checker // Do update checker
if (Settings.automaticUpdateChecks) { if (Settings.automaticUpdateChecks) {
@ -143,10 +144,10 @@ public class HuskSyncVelocity {
CommandMeta meta = commandManager.metaBuilder("husksync") CommandMeta meta = commandManager.metaBuilder("husksync")
.aliases("hs") .aliases("hs")
.build(); .build();
commandManager.register(meta, new HuskSyncCommand()); commandManager.register(meta, new VelocityCommand());
// Prepare the migrator for use if needed // Prepare the migrator for use if needed
//todo migrator mpdbMigrator = new MPDBMigrator(getVelocityLogger());
// Initialize bStats metrics // Initialize bStats metrics
try { try {

@ -1,34 +0,0 @@
package me.william278.husksync.velocity.command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class HuskSyncCommand implements SimpleCommand {
/**
* Executes the command for the specified invocation.
*
* @param invocation the invocation context
*/
@Override
public void execute(Invocation invocation) {
final String[] args = invocation.arguments();
final CommandSource source = invocation.source();
}
/**
* Provides tab complete suggestions for the specified invocation.
*
* @param invocation the invocation context
* @return the tab complete suggestions
*/
@Override
public List<String> suggest(Invocation invocation) {
return new ArrayList<>();
}
}

@ -0,0 +1,418 @@
package me.william278.husksync.velocity.command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.Player;
import de.themoep.minedown.adventure.MineDown;
import me.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.PlayerData;
import me.william278.husksync.Server;
import me.william278.husksync.Settings;
import me.william278.husksync.migrator.MPDBMigrator;
import me.william278.husksync.proxy.command.HuskSyncCommand;
import me.william278.husksync.redis.RedisMessage;
import me.william278.husksync.util.MessageManager;
import me.william278.husksync.velocity.VelocityUpdateChecker;
import me.william278.husksync.velocity.config.ConfigLoader;
import me.william278.husksync.velocity.config.ConfigManager;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class VelocityCommand implements SimpleCommand, HuskSyncCommand {
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
@Override
public void execute(Invocation invocation) {
final String[] args = invocation.arguments();
final CommandSource sender = invocation.source();
if (sender instanceof Player player) {
if (HuskSyncVelocity.synchronisedServers.size() == 0) {
player.sendMessage(new MineDown(MessageManager.getMessage("error_no_servers_proxied")).toComponent());
return;
}
if (args.length >= 1) {
switch (args[0].toLowerCase(Locale.ROOT)) {
case "about", "info" -> sendAboutInformation(player);
case "update" -> {
if (!player.hasPermission("husksync.command.inventory")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
sender.sendMessage(new MineDown("[Checking for HuskSync updates...](gray)").toComponent());
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
// Check Bukkit servers needing updates
int updatesNeeded = 0;
String bukkitBrand = "Spigot";
String bukkitVersion = "1.0";
for (Server server : HuskSyncVelocity.synchronisedServers) {
VelocityUpdateChecker updateChecker = new VelocityUpdateChecker(server.huskSyncVersion());
if (!updateChecker.isUpToDate()) {
updatesNeeded++;
bukkitBrand = server.serverBrand();
bukkitVersion = server.huskSyncVersion();
}
}
// Check Velocity servers needing updates and send message
VelocityUpdateChecker proxyUpdateChecker = new VelocityUpdateChecker(HuskSyncVelocity.VERSION);
if (proxyUpdateChecker.isUpToDate() && updatesNeeded == 0) {
sender.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running Version " + proxyUpdateChecker.getLatestVersion() + "](#00fb9a)").toComponent());
} else {
sender.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Your server(s) are not up-to-date:](#00fb9a)").toComponent());
if (!proxyUpdateChecker.isUpToDate()) {
sender.sendMessage(new MineDown("[•](white) [HuskSync on the Velocity proxy is outdated (Latest: " + proxyUpdateChecker.getLatestVersion() + ", Running: " + proxyUpdateChecker.getCurrentVersion() + ")](#00fb9a)").toComponent());
}
if (updatesNeeded > 0) {
sender.sendMessage(new MineDown("[•](white) [HuskSync on " + updatesNeeded + " connected " + bukkitBrand + " server(s) are outdated (Latest: " + proxyUpdateChecker.getLatestVersion() + ", Running: " + bukkitVersion + ")](#00fb9a)").toComponent());
}
sender.sendMessage(new MineDown("[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husktowns.92672/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husktowns.1056/updates)").toComponent());
}
});
}
case "invsee", "openinv", "inventory" -> {
if (!player.hasPermission("husksync.command.inventory")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
String clusterId;
if (Settings.clusters.size() > 1) {
if (args.length == 3) {
clusterId = args[2];
} else {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent());
return;
}
} else {
clusterId = "main";
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
clusterId = cluster.clusterId();
break;
}
}
if (args.length == 2 || args.length == 3) {
String playerName = args[1];
openInventory(player, playerName, clusterId);
} else {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax").replaceAll("%1%",
"/husksync invsee <player>")).toComponent());
}
}
case "echest", "enderchest" -> {
if (!player.hasPermission("husksync.command.ender_chest")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
String clusterId;
if (Settings.clusters.size() > 1) {
if (args.length == 3) {
clusterId = args[2];
} else {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent());
return;
}
} else {
clusterId = "main";
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
clusterId = cluster.clusterId();
break;
}
}
if (args.length == 2 || args.length == 3) {
String playerName = args[1];
openEnderChest(player, playerName, clusterId);
} else {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax")
.replaceAll("%1%", "/husksync echest <player>")).toComponent());
}
}
case "migrate" -> {
if (!player.hasPermission("husksync.command.admin")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
sender.sendMessage(new MineDown(MessageManager.getMessage("error_console_command_only")
.replaceAll("%1%", "Velocity")).toComponent());
}
case "status" -> {
if (!player.hasPermission("husksync.command.admin")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
int playerDataSize = 0;
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
playerDataSize += HuskSyncVelocity.dataManager.playerDataCache.get(cluster).playerData.size();
}
sender.sendMessage(new MineDown(MessageManager.PLUGIN_STATUS.toString()
.replaceAll("%1%", String.valueOf(HuskSyncVelocity.synchronisedServers.size()))
.replaceAll("%2%", String.valueOf(playerDataSize))).toComponent());
}
case "reload" -> {
if (!player.hasPermission("husksync.command.admin")) {
sender.sendMessage(new MineDown(MessageManager.getMessage("error_no_permission")).toComponent());
return;
}
ConfigManager.loadConfig();
ConfigLoader.loadSettings(Objects.requireNonNull(ConfigManager.getConfig()));
ConfigManager.loadMessages();
ConfigLoader.loadMessageStrings(Objects.requireNonNull(ConfigManager.getMessages()));
// Send reload request to all bukkit servers
try {
new RedisMessage(RedisMessage.MessageType.RELOAD_CONFIG,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null),
"reload")
.send();
} catch (IOException e) {
plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize reload notification message data");
}
sender.sendMessage(new MineDown(MessageManager.getMessage("reload_complete")).toComponent());
}
default -> sender.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_syntax").replaceAll("%1%",
"/husksync <about/status/invsee/echest>")).toComponent());
}
} else {
sendAboutInformation(player);
}
} else {
// Database migration wizard
if (args.length >= 1) {
if (args[0].equalsIgnoreCase("migrate")) {
MPDBMigrator migrator = HuskSyncVelocity.mpdbMigrator;
if (args.length == 1) {
sender.sendMessage(new MineDown(
"""
=== MySQLPlayerDataBridge Migration Wizard ==========
This will migrate data from the MySQLPlayerDataBridge
plugin to HuskSync.
Data that will be migrated:
- Inventories
- Ender Chests
- Experience points
Other non-vital data, such as current health, hunger
& potion effects will not be migrated to ensure that
migration does not take an excessive amount of time.
To do this, you need to have MySqlPlayerDataBridge
and HuskSync installed on one Spigot server as well
as HuskSync installed on the proxy (which you have)
>To proceed, type: husksync migrate setup""").toComponent());
} else {
switch (args[1].toLowerCase()) {
case "setup" -> sender.sendMessage(new MineDown(
"""
=== MySQLPlayerDataBridge Migration Wizard ==========
The following database settings will be used.
Please make sure they match the correct settings to
access your MySQLPlayerDataBridge Data
sourceHost: %1%
sourcePort: %2%
sourceDatabase: %3%
sourceUsername: %4%
sourcePassword: %5%
sourceInventoryTableName: %6%
sourceEnderChestTableName: %7%
sourceExperienceTableName: %8%
targetCluster: %9%
To change a setting, type:
husksync migrate setting <settingName> <value>
Please ensure no players are logged in to the network
and that at least one Spigot server is online with
both HuskSync AND MySqlPlayerDataBridge installed AND
that the server has been configured with the correct
Redis credentials.
Warning: Data will be saved to your configured data
source, which is currently a %10% database.
Please make sure you are happy with this, or stop
the proxy server and edit this in config.yml
Warning: Migration will overwrite any current data
saved by HuskSync. It will not, however, delete any
data from the source MySQLPlayerDataBridge database.
>When done, type: husksync migrate start"""
.replaceAll("%1%", migrator.migrationSettings.sourceHost)
.replaceAll("%2%", String.valueOf(migrator.migrationSettings.sourcePort))
.replaceAll("%3%", migrator.migrationSettings.sourceDatabase)
.replaceAll("%4%", migrator.migrationSettings.sourceUsername)
.replaceAll("%5%", migrator.migrationSettings.sourcePassword)
.replaceAll("%6%", migrator.migrationSettings.inventoryDataTable)
.replaceAll("%7%", migrator.migrationSettings.enderChestDataTable)
.replaceAll("%8%", migrator.migrationSettings.expDataTable)
.replaceAll("%9%", migrator.migrationSettings.targetCluster)
.replaceAll("%10%", Settings.dataStorageType.toString())
).toComponent());
case "setting" -> {
if (args.length == 4) {
String value = args[3];
switch (args[2]) {
case "sourceHost", "host" -> migrator.migrationSettings.sourceHost = value;
case "sourcePort", "port" -> {
try {
migrator.migrationSettings.sourcePort = Integer.parseInt(value);
} catch (NumberFormatException e) {
sender.sendMessage(new MineDown("Error: Invalid value; port must be a number").toComponent());
return;
}
}
case "sourceDatabase", "database" -> migrator.migrationSettings.sourceDatabase = value;
case "sourceUsername", "username" -> migrator.migrationSettings.sourceUsername = value;
case "sourcePassword", "password" -> migrator.migrationSettings.sourcePassword = value;
case "sourceInventoryTableName", "inventoryTableName", "inventoryTable" -> migrator.migrationSettings.inventoryDataTable = value;
case "sourceEnderChestTableName", "enderChestTableName", "enderChestTable" -> migrator.migrationSettings.enderChestDataTable = value;
case "sourceExperienceTableName", "experienceTableName", "experienceTable" -> migrator.migrationSettings.expDataTable = value;
case "targetCluster", "cluster" -> migrator.migrationSettings.targetCluster = value;
default -> {
sender.sendMessage(new MineDown("Error: Invalid setting; please use \"husksync migrate setup\" to view a list").toComponent());
return;
}
}
sender.sendMessage(new MineDown("Successfully updated setting: \"" + args[2] + "\" --> \"" + value + "\"").toComponent());
} else {
sender.sendMessage(new MineDown("Error: Invalid usage. Syntax: husksync migrate setting <settingName> <value>").toComponent());
}
}
case "start" -> {
sender.sendMessage(new MineDown("Starting MySQLPlayerDataBridge migration!...").toComponent());
// If the migrator is ready, execute the migration asynchronously
if (HuskSyncVelocity.mpdbMigrator.readyToMigrate(plugin.getProxyServer().getPlayerCount(),
HuskSyncVelocity.synchronisedServers)) {
plugin.getProxyServer().getScheduler().buildTask(plugin, () ->
HuskSyncVelocity.mpdbMigrator.executeMigrationOperations(HuskSyncVelocity.dataManager,
HuskSyncVelocity.synchronisedServers));
}
}
default -> sender.sendMessage(new MineDown("Error: Invalid argument for migration. Use \"husksync migrate\" to start the process").toComponent());
}
}
return;
}
}
sender.sendMessage(new MineDown("Error: Invalid syntax. Usage: husksync migrate <args>").toComponent());
}
}
// View the inventory of a player specified by their name
private void openInventory(Player viewer, String targetPlayerName, String clusterId) {
if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent());
return;
}
if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_inventory_online")).toComponent());
return;
}
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
if (!cluster.clusterId().equals(clusterId)) continue;
PlayerData playerData = HuskSyncVelocity.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId());
if (playerData == null) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent());
return;
}
try {
new RedisMessage(RedisMessage.MessageType.OPEN_INVENTORY,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId(), null),
targetPlayerName, RedisMessage.serialize(playerData))
.send();
viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_inventory_of").replaceAll("%1%",
targetPlayerName)).toComponent());
} catch (IOException e) {
plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e);
}
return;
}
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent());
});
}
// View the ender chest of a player specified by their name
public void openEnderChest(Player viewer, String targetPlayerName, String clusterId) {
if (viewer.getUsername().equalsIgnoreCase(targetPlayerName)) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_own_ender_chest")).toComponent());
return;
}
if (plugin.getProxyServer().getPlayer(targetPlayerName).isPresent()) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_cannot_view_ender_chest_online")).toComponent());
return;
}
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
if (!cluster.clusterId().equals(clusterId)) continue;
PlayerData playerData = HuskSyncVelocity.dataManager.getPlayerDataByName(targetPlayerName, cluster.clusterId());
if (playerData == null) {
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_player")).toComponent());
return;
}
try {
new RedisMessage(RedisMessage.MessageType.OPEN_ENDER_CHEST,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, viewer.getUniqueId(), null),
targetPlayerName, RedisMessage.serialize(playerData))
.send();
viewer.sendMessage(new MineDown(MessageManager.getMessage("viewing_ender_chest_of").replaceAll("%1%",
targetPlayerName)).toComponent());
} catch (IOException e) {
plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize inventory-see player data", e);
}
return;
}
viewer.sendMessage(new MineDown(MessageManager.getMessage("error_invalid_cluster")).toComponent());
});
}
/**
* Send information about the plugin
*
* @param player The player to send it to
*/
private void sendAboutInformation(Player player) {
try {
new RedisMessage(RedisMessage.MessageType.SEND_PLUGIN_INFORMATION,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, player.getUniqueId(), null),
"Velocity", HuskSyncVelocity.VERSION).send();
} catch (IOException e) {
plugin.getVelocityLogger().log(Level.WARNING, "Failed to serialize plugin information to send", e);
}
}
@Override
public List<String> suggest(Invocation invocation) {
final CommandSource sender = invocation.source();
final String[] args = invocation.arguments();
if (sender instanceof Player player) {
if (args.length == 1) {
final ArrayList<String> subCommands = new ArrayList<>();
for (SubCommand subCommand : SUB_COMMANDS) {
if (subCommand.permission() != null) {
if (!player.hasPermission(subCommand.permission())) {
continue;
}
}
subCommands.add(subCommand.command());
}
// Automatically filter the sub commands' order in tab completion by what the player has typed
return subCommands.stream().filter(val -> val.startsWith(args[0]))
.sorted().collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
return Collections.emptyList();
}
}

@ -3,7 +3,6 @@ package me.william278.husksync.velocity.config;
import me.william278.husksync.HuskSyncVelocity; import me.william278.husksync.HuskSyncVelocity;
import me.william278.husksync.Settings; import me.william278.husksync.Settings;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import java.io.File; import java.io.File;

Loading…
Cancel
Save