forked from public-mirrors/HuskSync
Start work on Velocity support
parent
48441cf9fd
commit
c6451a6ac6
@ -0,0 +1,302 @@
|
|||||||
|
package me.william278.husksync.bungeecord.migrator;
|
||||||
|
|
||||||
|
import me.william278.husksync.HuskSyncBungeeCord;
|
||||||
|
import me.william278.husksync.PlayerData;
|
||||||
|
import me.william278.husksync.Server;
|
||||||
|
import me.william278.husksync.Settings;
|
||||||
|
import me.william278.husksync.migrator.MPDBPlayerData;
|
||||||
|
import me.william278.husksync.proxy.data.sql.Database;
|
||||||
|
import me.william278.husksync.proxy.data.sql.MySQL;
|
||||||
|
import me.william278.husksync.redis.RedisMessage;
|
||||||
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle migration of data from MySQLPlayerDataBridge
|
||||||
|
* <p>
|
||||||
|
* The migrator accesses and decodes MPDB's format directly,
|
||||||
|
* by communicating with a Spigot server
|
||||||
|
*/
|
||||||
|
public class MPDBMigrator {
|
||||||
|
|
||||||
|
public static int migratedDataSent = 0;
|
||||||
|
public static int playersMigrated = 0;
|
||||||
|
|
||||||
|
private static final HuskSyncBungeeCord plugin = HuskSyncBungeeCord.getInstance();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (ProxyServer.getInstance().getPlayers().size() > 0) {
|
||||||
|
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because there are players online. " +
|
||||||
|
"Your network has to be empty to migrate data for safety reasons.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int synchronisedServersWithMpdb = 0;
|
||||||
|
for (Server server : HuskSyncBungeeCord.synchronisedServers) {
|
||||||
|
if (server.hasMySqlPlayerDataBridge()) {
|
||||||
|
synchronisedServersWithMpdb++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (synchronisedServersWithMpdb < 1) {
|
||||||
|
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because at least one Spigot server with both HuskSync and MySqlPlayerDataBridge installed is not online. " +
|
||||||
|
"Please start one Spigot server with HuskSync installed to begin migration.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
|
||||||
|
if (migrationSettings.targetCluster.equals(cluster.clusterId())) {
|
||||||
|
targetCluster = cluster;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetCluster == null) {
|
||||||
|
plugin.getBungeeLogger().log(Level.WARNING, "Failed to start migration because the target cluster could not be found. " +
|
||||||
|
"Please ensure the target cluster is correct, configured in the proxy config file, then try again");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
migratedDataSent = 0;
|
||||||
|
playersMigrated = 0;
|
||||||
|
mpdbPlayerData = new HashSet<>();
|
||||||
|
incomingPlayerData = new HashMap<>();
|
||||||
|
final MigrationSettings settings = migrationSettings;
|
||||||
|
|
||||||
|
// Get connection to source database
|
||||||
|
sourceDatabase = new MigratorMySQL(plugin, settings.sourceHost, settings.sourcePort,
|
||||||
|
settings.sourceDatabase, settings.sourceUsername, settings.sourcePassword, targetCluster);
|
||||||
|
sourceDatabase.load();
|
||||||
|
if (sourceDatabase.isInactive()) {
|
||||||
|
plugin.getBungeeLogger().log(Level.WARNING, "Failed to establish connection to the origin MySQL database. " +
|
||||||
|
"Please check you have input the correct connection details and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||||
|
prepareTargetDatabase();
|
||||||
|
|
||||||
|
getInventoryData();
|
||||||
|
|
||||||
|
getEnderChestData();
|
||||||
|
|
||||||
|
getExperienceData();
|
||||||
|
|
||||||
|
sendEncodedData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the new database out of current data
|
||||||
|
private void prepareTargetDatabase() {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Preparing target database...");
|
||||||
|
try (Connection connection = HuskSyncBungeeCord.dataManager.getConnection(targetCluster.clusterId())) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.playerTableName() + ";")) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM " + targetCluster.dataTableName() + ";")) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred preparing the target database", e);
|
||||||
|
} finally {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Finished preparing target database!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getInventoryData() {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Getting inventory data from MySQLPlayerDataBridge...");
|
||||||
|
try (Connection connection = sourceDatabase.getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.inventoryDataTable + ";")) {
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
final UUID playerUUID = UUID.fromString(resultSet.getString("player_uuid"));
|
||||||
|
final String playerName = resultSet.getString("player_name");
|
||||||
|
|
||||||
|
MPDBPlayerData data = new MPDBPlayerData(playerUUID, playerName);
|
||||||
|
data.inventoryData = resultSet.getString("inventory");
|
||||||
|
data.armorData = resultSet.getString("armor");
|
||||||
|
|
||||||
|
mpdbPlayerData.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting inventory data", e);
|
||||||
|
} finally {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Finished getting inventory data from MySQLPlayerDataBridge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getEnderChestData() {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Getting ender chest data from MySQLPlayerDataBridge...");
|
||||||
|
try (Connection connection = sourceDatabase.getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.enderChestDataTable + ";")) {
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
final UUID playerUUID = UUID.fromString(resultSet.getString("player_uuid"));
|
||||||
|
|
||||||
|
for (MPDBPlayerData data : mpdbPlayerData) {
|
||||||
|
if (data.playerUUID.equals(playerUUID)) {
|
||||||
|
data.enderChestData = resultSet.getString("enderchest");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting ender chest data", e);
|
||||||
|
} finally {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Finished getting ender chest data from MySQLPlayerDataBridge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getExperienceData() {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Getting experience data from MySQLPlayerDataBridge...");
|
||||||
|
try (Connection connection = sourceDatabase.getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + migrationSettings.expDataTable + ";")) {
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
final UUID playerUUID = UUID.fromString(resultSet.getString("player_uuid"));
|
||||||
|
|
||||||
|
for (MPDBPlayerData data : mpdbPlayerData) {
|
||||||
|
if (data.playerUUID.equals(playerUUID)) {
|
||||||
|
data.expLevel = resultSet.getInt("exp_lvl");
|
||||||
|
data.expProgress = resultSet.getFloat("exp");
|
||||||
|
data.totalExperience = resultSet.getInt("total_exp");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getBungeeLogger().log(Level.SEVERE, "An exception occurred getting experience data", e);
|
||||||
|
} finally {
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Finished getting experience data from MySQLPlayerDataBridge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEncodedData() {
|
||||||
|
for (Server processingServer : HuskSyncBungeeCord.synchronisedServers) {
|
||||||
|
if (processingServer.hasMySqlPlayerDataBridge()) {
|
||||||
|
for (MPDBPlayerData data : mpdbPlayerData) {
|
||||||
|
try {
|
||||||
|
new RedisMessage(RedisMessage.MessageType.DECODE_MPDB_DATA,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null),
|
||||||
|
processingServer.serverUUID().toString(),
|
||||||
|
RedisMessage.serialize(data))
|
||||||
|
.send();
|
||||||
|
migratedDataSent++;
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getBungeeLogger().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");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all incoming decoded MPDB data to the cache and database
|
||||||
|
*
|
||||||
|
* @param dataToLoad HashMap of the {@link PlayerData} to player Usernames that will be loaded
|
||||||
|
*/
|
||||||
|
public static void loadIncomingData(HashMap<PlayerData, String> dataToLoad) {
|
||||||
|
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||||
|
int playersSaved = 0;
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Saving data for " + playersMigrated + " players...");
|
||||||
|
|
||||||
|
for (PlayerData playerData : dataToLoad.keySet()) {
|
||||||
|
String playerName = dataToLoad.get(playerData);
|
||||||
|
|
||||||
|
// Add the player to the MySQL table
|
||||||
|
HuskSyncBungeeCord.dataManager.ensurePlayerExists(playerData.getPlayerUUID(), playerName);
|
||||||
|
|
||||||
|
// Update the data in the cache and SQL
|
||||||
|
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
|
||||||
|
HuskSyncBungeeCord.dataManager.updatePlayerData(playerData, cluster);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
playersSaved++;
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Saved data for " + playersSaved + "/" + playersMigrated + " players");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as done when done
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, """
|
||||||
|
=== MySQLPlayerDataBridge Migration Wizard ==========
|
||||||
|
|
||||||
|
Migration complete!
|
||||||
|
|
||||||
|
Successfully migrated data for %1%/%2% players.
|
||||||
|
|
||||||
|
You should now uninstall MySQLPlayerDataBridge from
|
||||||
|
the rest of the Spigot servers, then restart them.
|
||||||
|
""".replaceAll("%1%", Integer.toString(MPDBMigrator.playersMigrated))
|
||||||
|
.replaceAll("%2%", Integer.toString(MPDBMigrator.migratedDataSent)));
|
||||||
|
sourceDatabase.close(); // Close source database
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to hold settings for the MPDB migration
|
||||||
|
*/
|
||||||
|
public static class MigrationSettings {
|
||||||
|
public String sourceHost;
|
||||||
|
public int sourcePort;
|
||||||
|
public String sourceDatabase;
|
||||||
|
public String sourceUsername;
|
||||||
|
public String sourcePassword;
|
||||||
|
|
||||||
|
public String inventoryDataTable;
|
||||||
|
public String enderChestDataTable;
|
||||||
|
public String expDataTable;
|
||||||
|
|
||||||
|
public String targetCluster;
|
||||||
|
|
||||||
|
public MigrationSettings() {
|
||||||
|
sourceHost = "localhost";
|
||||||
|
sourcePort = 3306;
|
||||||
|
sourceDatabase = "mpdb";
|
||||||
|
sourceUsername = "root";
|
||||||
|
sourcePassword = "pa55w0rd";
|
||||||
|
|
||||||
|
targetCluster = "main";
|
||||||
|
|
||||||
|
inventoryDataTable = "mpdb_inventory";
|
||||||
|
enderChestDataTable = "mpdb_enderchest";
|
||||||
|
expDataTable = "mpdb_experience";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MySQL class used for importing data from MPDB
|
||||||
|
*/
|
||||||
|
public static class MigratorMySQL extends MySQL {
|
||||||
|
public MigratorMySQL(HuskSyncBungeeCord instance, String host, int port, String database, String username, String password, Settings.SynchronisationCluster cluster) {
|
||||||
|
super(cluster, instance.getBungeeLogger());
|
||||||
|
super.host = host;
|
||||||
|
super.port = port;
|
||||||
|
super.database = database;
|
||||||
|
super.username = username;
|
||||||
|
super.password = password;
|
||||||
|
super.params = "?useSSL=false";
|
||||||
|
super.dataPoolName = super.dataPoolName + "Migrator";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package me.william278.husksync.bungeecord.util;
|
||||||
|
|
||||||
|
import me.william278.husksync.util.Logger;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public record BungeeLogger(java.util.logging.Logger parent) implements Logger {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message, Exception e) {
|
||||||
|
parent.log(level, message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message) {
|
||||||
|
parent.log(level, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String message) {
|
||||||
|
parent.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void severe(String message) {
|
||||||
|
parent.severe(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(String message) {
|
||||||
|
parent.config(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package me.william278.husksync;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A record representing a server synchronised on the network and whether it has MySqlPlayerDataBridge installed
|
||||||
|
*/
|
||||||
|
public record Server(UUID serverUUID, boolean hasMySqlPlayerDataBridge, String huskSyncVersion, String serverBrand,
|
||||||
|
String clusterId) {
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
package me.william278.husksync.bungeecord.data.sql;
|
package me.william278.husksync.proxy.data.sql;
|
||||||
|
|
||||||
import me.william278.husksync.HuskSyncBungeeCord;
|
|
||||||
import me.william278.husksync.Settings;
|
import me.william278.husksync.Settings;
|
||||||
|
import me.william278.husksync.util.Logger;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public abstract class Database {
|
public abstract class Database {
|
||||||
protected HuskSyncBungeeCord plugin;
|
|
||||||
|
|
||||||
public String dataPoolName;
|
public String dataPoolName;
|
||||||
public Settings.SynchronisationCluster cluster;
|
public Settings.SynchronisationCluster cluster;
|
||||||
|
public final Logger logger;
|
||||||
|
|
||||||
public Database(HuskSyncBungeeCord instance, Settings.SynchronisationCluster cluster) {
|
public Database(Settings.SynchronisationCluster cluster, Logger logger) {
|
||||||
this.plugin = instance;
|
|
||||||
this.cluster = cluster;
|
this.cluster = cluster;
|
||||||
this.dataPoolName = cluster != null ? "HuskSyncHikariPool-" + cluster.clusterId() : "HuskSyncMigratorPool";
|
this.dataPoolName = cluster != null ? "HuskSyncHikariPool-" + cluster.clusterId() : "HuskSyncMigratorPool";
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Connection getConnection() throws SQLException;
|
public abstract Connection getConnection() throws SQLException;
|
@ -0,0 +1,19 @@
|
|||||||
|
package me.william278.husksync.util;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger interface to allow for implementation of different logger platforms used by Bungee and Velocity
|
||||||
|
*/
|
||||||
|
public interface Logger {
|
||||||
|
|
||||||
|
void log(Level level, String message, Exception e);
|
||||||
|
|
||||||
|
void log(Level level, String message);
|
||||||
|
|
||||||
|
void info(String message);
|
||||||
|
|
||||||
|
void severe(String message);
|
||||||
|
|
||||||
|
void config(String message);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly project(':common')
|
||||||
|
implementation project(path: ':common', configuration: 'shadow')
|
||||||
|
|
||||||
|
compileOnly 'redis.clients:jedis:3.7.0'
|
||||||
|
implementation 'org.bstats:bstats-velocity:2.2.1'
|
||||||
|
implementation 'com.zaxxer:HikariCP:5.0.0'
|
||||||
|
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||||
|
|
||||||
|
compileOnly 'com.velocitypowered:velocity-api:3.1.0'
|
||||||
|
annotationProcessor 'com.velocitypowered:velocity-api:3.1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
relocate 'com.zaxxer', 'me.William278.husksync.libraries.hikari'
|
||||||
|
relocate 'org.bstats', 'me.William278.husksync.libraries.plan'
|
||||||
|
relocate 'de.themoep', 'me.William278.husksync.libraries.minedown'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('prepareKotlinBuildScriptModel'){}
|
@ -0,0 +1,187 @@
|
|||||||
|
package me.william278.husksync;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.velocitypowered.api.command.CommandManager;
|
||||||
|
import com.velocitypowered.api.command.CommandMeta;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import me.william278.husksync.proxy.data.DataManager;
|
||||||
|
import me.william278.husksync.redis.RedisMessage;
|
||||||
|
import me.william278.husksync.velocity.VelocityUpdateChecker;
|
||||||
|
import me.william278.husksync.velocity.command.HuskSyncCommand;
|
||||||
|
import me.william278.husksync.velocity.config.ConfigLoader;
|
||||||
|
import me.william278.husksync.velocity.config.ConfigManager;
|
||||||
|
import me.william278.husksync.velocity.listener.VelocityEventListener;
|
||||||
|
import me.william278.husksync.velocity.listener.VelocityRedisListener;
|
||||||
|
import me.william278.husksync.velocity.util.VelocityLogger;
|
||||||
|
import org.bstats.velocity.Metrics;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static me.william278.husksync.HuskSyncVelocity.VERSION;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
id = "velocity",
|
||||||
|
name = "HuskSync",
|
||||||
|
version = VERSION,
|
||||||
|
description = "HuskSync for velocity",
|
||||||
|
authors = {"William278"}
|
||||||
|
)
|
||||||
|
public class HuskSyncVelocity {
|
||||||
|
|
||||||
|
// Plugin version
|
||||||
|
public static final String VERSION = "1.2-dev";
|
||||||
|
|
||||||
|
// Velocity bStats ID (different from Bukkit and BungeeCord)
|
||||||
|
private static final int METRICS_ID = 13489;
|
||||||
|
private final Metrics.Factory metricsFactory;
|
||||||
|
|
||||||
|
private static HuskSyncVelocity instance;
|
||||||
|
|
||||||
|
public static HuskSyncVelocity getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the plugin is ready to accept redis messages
|
||||||
|
public static boolean readyForRedis = false;
|
||||||
|
|
||||||
|
// Whether the plugin is in the process of disabling and should skip responding to handshake confirmations
|
||||||
|
public static boolean isDisabling = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of all the {@link Server}s that have completed the synchronisation handshake with HuskSync on the proxy
|
||||||
|
*/
|
||||||
|
public static HashSet<Server> synchronisedServers;
|
||||||
|
|
||||||
|
public static DataManager dataManager;
|
||||||
|
|
||||||
|
//public static MPDBMigrator mpdbMigrator;
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
private final ProxyServer server;
|
||||||
|
private final Path dataDirectory;
|
||||||
|
|
||||||
|
// Get the data folder
|
||||||
|
public File getDataFolder() {
|
||||||
|
return dataDirectory.toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the proxy server
|
||||||
|
public ProxyServer getProxyServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Velocity logger handling
|
||||||
|
private VelocityLogger velocityLogger;
|
||||||
|
|
||||||
|
public VelocityLogger getVelocityLogger() {
|
||||||
|
return velocityLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HuskSyncVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) {
|
||||||
|
this.server = server;
|
||||||
|
this.logger = logger;
|
||||||
|
this.dataDirectory = dataDirectory;
|
||||||
|
this.metricsFactory = metricsFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||||
|
// Set instance
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
// Setup logger
|
||||||
|
velocityLogger = new VelocityLogger(logger);
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
ConfigManager.loadConfig();
|
||||||
|
|
||||||
|
// Load settings from config
|
||||||
|
ConfigLoader.loadSettings(ConfigManager.getConfig());
|
||||||
|
|
||||||
|
// Load messages
|
||||||
|
ConfigManager.loadMessages();
|
||||||
|
|
||||||
|
// Load locales from messages
|
||||||
|
ConfigLoader.loadMessageStrings(ConfigManager.getMessages());
|
||||||
|
|
||||||
|
// Do update checker
|
||||||
|
if (Settings.automaticUpdateChecks) {
|
||||||
|
new VelocityUpdateChecker(VERSION).logToConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup data manager
|
||||||
|
dataManager = new DataManager(getVelocityLogger(), getDataFolder());
|
||||||
|
|
||||||
|
// Setup player data cache
|
||||||
|
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
|
||||||
|
dataManager.playerDataCache.put(cluster, new DataManager.PlayerDataCache());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the redis listener
|
||||||
|
if (!new VelocityRedisListener().isActiveAndEnabled) {
|
||||||
|
getVelocityLogger().severe("Failed to initialize Redis; HuskSync will now abort loading itself (Velocity) v" + VERSION);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register listener
|
||||||
|
server.getEventManager().register(this, new VelocityEventListener());
|
||||||
|
|
||||||
|
// Register command
|
||||||
|
CommandManager commandManager = getProxyServer().getCommandManager();
|
||||||
|
CommandMeta meta = commandManager.metaBuilder("husksync")
|
||||||
|
.aliases("hs")
|
||||||
|
.build();
|
||||||
|
commandManager.register(meta, new HuskSyncCommand());
|
||||||
|
|
||||||
|
// Prepare the migrator for use if needed
|
||||||
|
//todo migrator
|
||||||
|
|
||||||
|
// Initialize bStats metrics
|
||||||
|
try {
|
||||||
|
metricsFactory.make(this, METRICS_ID);
|
||||||
|
} catch (Exception e) {
|
||||||
|
getVelocityLogger().info("Skipped metrics initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to console
|
||||||
|
getVelocityLogger().info("Enabled HuskSync (Velocity) v" + VERSION);
|
||||||
|
|
||||||
|
// Mark as ready for redis message processing
|
||||||
|
readyForRedis = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyShutdown(ProxyShutdownEvent event) {
|
||||||
|
// Plugin shutdown logic
|
||||||
|
isDisabling = true;
|
||||||
|
|
||||||
|
// Send terminating handshake message
|
||||||
|
for (Server server : synchronisedServers) {
|
||||||
|
try {
|
||||||
|
new RedisMessage(RedisMessage.MessageType.TERMINATE_HANDSHAKE,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, server.clusterId()),
|
||||||
|
server.serverUUID().toString(),
|
||||||
|
"Velocity").send();
|
||||||
|
} catch (IOException e) {
|
||||||
|
getVelocityLogger().log(Level.SEVERE, "Failed to serialize Redis message for handshake termination", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataManager.closeDatabases();
|
||||||
|
|
||||||
|
// Log to console
|
||||||
|
getVelocityLogger().info("Disabled HuskSync (Velocity) v" + VERSION);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package me.william278.husksync.velocity;
|
||||||
|
|
||||||
|
import me.william278.husksync.HuskSyncVelocity;
|
||||||
|
import me.william278.husksync.util.UpdateChecker;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class VelocityUpdateChecker extends UpdateChecker {
|
||||||
|
|
||||||
|
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
|
||||||
|
|
||||||
|
public VelocityUpdateChecker(String versionToCheck) {
|
||||||
|
super(versionToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message) {
|
||||||
|
plugin.getVelocityLogger().log(level, message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package me.william278.husksync.velocity.command;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class HuskSyncCommand implements SimpleCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the command for the specified invocation.
|
||||||
|
*
|
||||||
|
* @param invocation the invocation context
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
final String[] args = invocation.arguments();
|
||||||
|
final CommandSource source = invocation.source();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides tab complete suggestions for the specified invocation.
|
||||||
|
*
|
||||||
|
* @param invocation the invocation context
|
||||||
|
* @return the tab complete suggestions
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<String> suggest(Invocation invocation) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package me.william278.husksync.velocity.config;
|
||||||
|
|
||||||
|
import me.william278.husksync.HuskSyncVelocity;
|
||||||
|
import me.william278.husksync.Settings;
|
||||||
|
import me.william278.husksync.util.MessageManager;
|
||||||
|
import ninja.leaping.configurate.ConfigurationNode;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class ConfigLoader {
|
||||||
|
|
||||||
|
private static ConfigurationNode copyDefaults(ConfigurationNode configRoot) {
|
||||||
|
// Get the config version and update if needed
|
||||||
|
String configVersion = getConfigString(configRoot, "1.0", "config_file_version");
|
||||||
|
if (configVersion.contains("-dev")) {
|
||||||
|
configVersion = configVersion.replaceAll("-dev", "");
|
||||||
|
}
|
||||||
|
if (!configVersion.equals(HuskSyncVelocity.VERSION)) {
|
||||||
|
if (configVersion.equalsIgnoreCase("1.0")) {
|
||||||
|
configRoot.getNode("check_for_updates").setValue(true);
|
||||||
|
}
|
||||||
|
if (configVersion.equalsIgnoreCase("1.0") || configVersion.equalsIgnoreCase("1.0.1") || configVersion.equalsIgnoreCase("1.0.2") || configVersion.equalsIgnoreCase("1.0.3")) {
|
||||||
|
configRoot.getNode("clusters.main.player_table").setValue("husksync_players");
|
||||||
|
configRoot.getNode("clusters.main.data_table").setValue("husksync_data");
|
||||||
|
}
|
||||||
|
configRoot.getNode("config_file_version").setValue(HuskSyncVelocity.VERSION);
|
||||||
|
}
|
||||||
|
// Save the config back
|
||||||
|
ConfigManager.saveConfig(configRoot);
|
||||||
|
return configRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getConfigString(ConfigurationNode rootNode, String defaultValue, String... nodePath) {
|
||||||
|
return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getString() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getConfigBoolean(ConfigurationNode rootNode, boolean defaultValue, String... nodePath) {
|
||||||
|
return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getBoolean() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getConfigInt(ConfigurationNode rootNode, int defaultValue, String... nodePath) {
|
||||||
|
return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getInt() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getConfigLong(ConfigurationNode rootNode, long defaultValue, String... nodePath) {
|
||||||
|
return !rootNode.getNode(nodePath).isVirtual() ? rootNode.getNode(nodePath).getLong() : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadSettings(ConfigurationNode loadedConfig) throws IllegalArgumentException {
|
||||||
|
ConfigurationNode config = copyDefaults(loadedConfig);
|
||||||
|
|
||||||
|
Settings.language = getConfigString(config, "en-gb", "language");
|
||||||
|
|
||||||
|
Settings.serverType = Settings.ServerType.PROXY;
|
||||||
|
Settings.automaticUpdateChecks = getConfigBoolean(config, true, "check_for_updates");
|
||||||
|
Settings.redisHost = getConfigString(config, "localhost", "redis_settings", "host");
|
||||||
|
Settings.redisPort = getConfigInt(config, 6379, "redis_settings", "port");
|
||||||
|
Settings.redisPassword = getConfigString(config, "", "redis_settings", "password");
|
||||||
|
|
||||||
|
Settings.dataStorageType = Settings.DataStorageType.valueOf(getConfigString(config, "sqlite", "data_storage_settings", "database_type").toUpperCase());
|
||||||
|
if (Settings.dataStorageType == Settings.DataStorageType.MYSQL) {
|
||||||
|
Settings.mySQLHost = getConfigString(config, "localhost", "data_storage_settings", "mysql_settings", "host");
|
||||||
|
Settings.mySQLPort = getConfigInt(config, 3306, "data_storage_settings", "mysql_settings", "port");
|
||||||
|
Settings.mySQLDatabase = getConfigString(config, "HuskSync", "data_storage_settings", "mysql_settings", "database");
|
||||||
|
Settings.mySQLUsername = getConfigString(config, "root", "data_storage_settings", "mysql_settings", "username");
|
||||||
|
Settings.mySQLPassword = getConfigString(config, "pa55w0rd", "data_storage_settings", "mysql_settings", "password");
|
||||||
|
Settings.mySQLParams = getConfigString(config, "?autoReconnect=true&useSSL=false", "data_storage_settings", "mysql_settings", "params");
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.hikariMaximumPoolSize = getConfigInt(config, 10, "data_storage_settings", "hikari_pool_settings", "maximum_pool_size");
|
||||||
|
Settings.hikariMinimumIdle = getConfigInt(config, 10, "data_storage_settings", "hikari_pool_settings", "minimum_idle");
|
||||||
|
Settings.hikariMaximumLifetime = getConfigLong(config, 1800000, "data_storage_settings", "hikari_pool_settings", "maximum_lifetime");
|
||||||
|
Settings.hikariKeepAliveTime = getConfigLong(config, 0, "data_storage_settings", "hikari_pool_settings", "keepalive_time");
|
||||||
|
Settings.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout");
|
||||||
|
|
||||||
|
// Read cluster data
|
||||||
|
ConfigurationNode clusterSection = config.getNode("clusters");
|
||||||
|
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync";
|
||||||
|
for (ConfigurationNode cluster : clusterSection.getChildrenList()) {
|
||||||
|
final String clusterId = (String) cluster.getKey();
|
||||||
|
final String playerTableName = getConfigString(config, "husksync_players", "clusters", clusterId, "player_table");
|
||||||
|
final String dataTableName = getConfigString(config, "husksync_data", "clusters", clusterId, "data_table");
|
||||||
|
final String databaseName = getConfigString(config, settingDatabaseName, "clusters", clusterId, "database");
|
||||||
|
Settings.clusters.add(new Settings.SynchronisationCluster(clusterId, databaseName, playerTableName, dataTableName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadMessageStrings(ConfigurationNode config) {
|
||||||
|
final HashMap<String, String> messages = new HashMap<>();
|
||||||
|
for (ConfigurationNode message : config.getChildrenList()) {
|
||||||
|
final String messageId = (String) message.getKey();
|
||||||
|
messages.put(messageId, getConfigString(config, "", messageId));
|
||||||
|
}
|
||||||
|
MessageManager.setMessages(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package me.william278.husksync.velocity.config;
|
||||||
|
|
||||||
|
import me.william278.husksync.HuskSyncVelocity;
|
||||||
|
import me.william278.husksync.Settings;
|
||||||
|
import ninja.leaping.configurate.ConfigurationNode;
|
||||||
|
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
|
||||||
|
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
|
||||||
|
|
||||||
|
public static void loadConfig() {
|
||||||
|
try {
|
||||||
|
if (!plugin.getDataFolder().exists()) {
|
||||||
|
if (plugin.getDataFolder().mkdir()) {
|
||||||
|
plugin.getVelocityLogger().info("Created HuskSync data folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("proxy-config.yml")), configFile.toPath());
|
||||||
|
plugin.getVelocityLogger().info("Created HuskSync config file");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveConfig(ConfigurationNode rootNode) {
|
||||||
|
try {
|
||||||
|
getConfigLoader().save(rootNode);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the configuration file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadMessages() {
|
||||||
|
try {
|
||||||
|
if (!plugin.getDataFolder().exists()) {
|
||||||
|
if (plugin.getDataFolder().mkdir()) {
|
||||||
|
plugin.getVelocityLogger().info("Created HuskSync data folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File messagesFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml");
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
Files.copy(Objects.requireNonNull(plugin.getClass().getResourceAsStream("languages/" + Settings.language + ".yml")),
|
||||||
|
messagesFile.toPath());
|
||||||
|
plugin.getVelocityLogger().info("Created HuskSync messages file");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.CONFIG, "An exception occurred loading the messages file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static YAMLConfigurationLoader getConfigLoader() {
|
||||||
|
File configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||||
|
return YAMLConfigurationLoader.builder()
|
||||||
|
.setPath(configFile.toPath())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigurationNode getConfig() {
|
||||||
|
try {
|
||||||
|
return getConfigLoader().load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.CONFIG, "An IOException has occurred loading the plugin config.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigurationNode getMessages() {
|
||||||
|
try {
|
||||||
|
File configFile = new File(plugin.getDataFolder(), "messages_" + Settings.language + ".yml");
|
||||||
|
return YAMLConfigurationLoader.builder()
|
||||||
|
.setPath(configFile.toPath())
|
||||||
|
.build()
|
||||||
|
.load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.CONFIG, "An IOException has occurred loading the plugin messages.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package me.william278.husksync.velocity.listener;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import me.william278.husksync.HuskSyncVelocity;
|
||||||
|
import me.william278.husksync.PlayerData;
|
||||||
|
import me.william278.husksync.Settings;
|
||||||
|
import me.william278.husksync.redis.RedisMessage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class VelocityEventListener {
|
||||||
|
|
||||||
|
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPostLogin(PostLoginEvent event) {
|
||||||
|
final Player player = event.getPlayer();
|
||||||
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
// Ensure the player has data on SQL and that it is up-to-date
|
||||||
|
HuskSyncVelocity.dataManager.ensurePlayerExists(player.getUniqueId(), player.getUsername());
|
||||||
|
|
||||||
|
// Get the player's data from SQL
|
||||||
|
final Map<Settings.SynchronisationCluster, PlayerData> data = HuskSyncVelocity.dataManager.getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
// Update the player's data from SQL onto the cache
|
||||||
|
assert data != null;
|
||||||
|
for (Settings.SynchronisationCluster cluster : data.keySet()) {
|
||||||
|
HuskSyncVelocity.dataManager.playerDataCache.get(cluster).updatePlayer(data.get(cluster));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message asking the bukkit to request data on join
|
||||||
|
try {
|
||||||
|
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, null),
|
||||||
|
RedisMessage.RequestOnJoinUpdateType.ADD_REQUESTER.toString(), player.getUniqueId().toString()).send();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize request data on join message data");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
package me.william278.husksync.velocity.listener;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
|
import me.william278.husksync.HuskSyncVelocity;
|
||||||
|
import me.william278.husksync.PlayerData;
|
||||||
|
import me.william278.husksync.Server;
|
||||||
|
import me.william278.husksync.Settings;
|
||||||
|
import me.william278.husksync.redis.RedisListener;
|
||||||
|
import me.william278.husksync.redis.RedisMessage;
|
||||||
|
import me.william278.husksync.util.MessageManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class VelocityRedisListener extends RedisListener {
|
||||||
|
|
||||||
|
private static final HuskSyncVelocity plugin = HuskSyncVelocity.getInstance();
|
||||||
|
|
||||||
|
// Initialize the listener on the bungee
|
||||||
|
public VelocityRedisListener() {
|
||||||
|
listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerData getPlayerCachedData(UUID uuid, String clusterId) {
|
||||||
|
PlayerData data = null;
|
||||||
|
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
|
||||||
|
if (cluster.clusterId().equals(clusterId)) {
|
||||||
|
// Get the player data from the cache
|
||||||
|
PlayerData cachedData = HuskSyncVelocity.dataManager.playerDataCache.get(cluster).getPlayer(uuid);
|
||||||
|
if (cachedData != null) {
|
||||||
|
return cachedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = Objects.requireNonNull(HuskSyncVelocity.dataManager.getPlayerData(uuid)).get(cluster); // Get their player data from MySQL
|
||||||
|
HuskSyncVelocity.dataManager.playerDataCache.get(cluster).updatePlayer(data); // Update the cache
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data; // Return the data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming {@link RedisMessage}
|
||||||
|
*
|
||||||
|
* @param message The {@link RedisMessage} to handle
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleMessage(RedisMessage message) {
|
||||||
|
// Ignore messages destined for Bukkit servers
|
||||||
|
if (message.getMessageTarget().targetServerType() != Settings.ServerType.PROXY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only process redis messages when ready
|
||||||
|
if (!HuskSyncVelocity.readyForRedis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.getMessageType()) {
|
||||||
|
case PLAYER_DATA_REQUEST -> {
|
||||||
|
// Get the UUID of the requesting player
|
||||||
|
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
|
||||||
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
try {
|
||||||
|
// Send the reply, serializing the message data
|
||||||
|
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
|
||||||
|
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Send an update to all bukkit servers removing the player from the requester cache
|
||||||
|
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||||
|
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Send synchronisation complete message
|
||||||
|
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
|
||||||
|
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case PLAYER_DATA_UPDATE -> {
|
||||||
|
// Deserialize the PlayerData received
|
||||||
|
PlayerData playerData;
|
||||||
|
final String serializedPlayerData = message.getMessageData();
|
||||||
|
try {
|
||||||
|
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
|
||||||
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
log(Level.SEVERE, "Failed to deserialize PlayerData when handling a player update request");
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data in the cache and SQL
|
||||||
|
for (Settings.SynchronisationCluster cluster : Settings.clusters) {
|
||||||
|
if (cluster.clusterId().equals(message.getMessageTarget().targetClusterId())) {
|
||||||
|
HuskSyncVelocity.dataManager.updatePlayerData(playerData, cluster);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply with the player data if they are still online (switching server)
|
||||||
|
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
|
||||||
|
updatingPlayer.ifPresent(player -> {
|
||||||
|
try {
|
||||||
|
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
|
||||||
|
RedisMessage.serialize(playerData))
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Send synchronisation complete message
|
||||||
|
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
case CONNECTION_HANDSHAKE -> {
|
||||||
|
// Reply to a Bukkit server's connection handshake to complete the process
|
||||||
|
if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling
|
||||||
|
final UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||||
|
final boolean hasMySqlPlayerDataBridge = Boolean.parseBoolean(message.getMessageDataElements()[1]);
|
||||||
|
final String bukkitBrand = message.getMessageDataElements()[2];
|
||||||
|
final String huskSyncVersion = message.getMessageDataElements()[3];
|
||||||
|
try {
|
||||||
|
new RedisMessage(RedisMessage.MessageType.CONNECTION_HANDSHAKE,
|
||||||
|
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
|
||||||
|
serverUUID.toString(), "Velocity")
|
||||||
|
.send();
|
||||||
|
HuskSyncVelocity.synchronisedServers.add(
|
||||||
|
new Server(serverUUID, hasMySqlPlayerDataBridge,
|
||||||
|
huskSyncVersion, bukkitBrand, message.getMessageTarget().targetClusterId()));
|
||||||
|
log(Level.INFO, "Completed handshake with " + bukkitBrand + " server (" + serverUUID + ")");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log(Level.SEVERE, "Failed to serialize handshake message data");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case TERMINATE_HANDSHAKE -> {
|
||||||
|
// Terminate the handshake with a Bukkit server
|
||||||
|
final UUID serverUUID = UUID.fromString(message.getMessageDataElements()[0]);
|
||||||
|
final String bukkitBrand = message.getMessageDataElements()[1];
|
||||||
|
|
||||||
|
// Remove a server from the synchronised server list
|
||||||
|
Server serverToRemove = null;
|
||||||
|
for (Server server : HuskSyncVelocity.synchronisedServers) {
|
||||||
|
if (server.serverUUID().equals(serverUUID)) {
|
||||||
|
serverToRemove = server;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HuskSyncVelocity.synchronisedServers.remove(serverToRemove);
|
||||||
|
log(Level.INFO, "Terminated the handshake with " + bukkitBrand + " server (" + serverUUID + ")");
|
||||||
|
}
|
||||||
|
case DECODED_MPDB_DATA_SET -> {
|
||||||
|
// Deserialize the PlayerData received
|
||||||
|
PlayerData playerData;
|
||||||
|
final String serializedPlayerData = message.getMessageDataElements()[0];
|
||||||
|
final String playerName = message.getMessageDataElements()[1];
|
||||||
|
try {
|
||||||
|
playerData = (PlayerData) RedisMessage.deserialize(serializedPlayerData);
|
||||||
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
log(Level.SEVERE, "Failed to deserialize PlayerData when handling incoming decoded MPDB data");
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo Migrator
|
||||||
|
/*// Add the incoming data to the data to be saved
|
||||||
|
MPDBMigrator.incomingPlayerData.put(playerData, playerName);
|
||||||
|
|
||||||
|
// Increment players migrated
|
||||||
|
MPDBMigrator.playersMigrated++;
|
||||||
|
plugin.getBungeeLogger().log(Level.INFO, "Migrated " + MPDBMigrator.playersMigrated + "/" + MPDBMigrator.migratedDataSent + " players.");
|
||||||
|
|
||||||
|
// When all the data has been received, save it
|
||||||
|
if (MPDBMigrator.migratedDataSent == MPDBMigrator.playersMigrated) {
|
||||||
|
MPDBMigrator.loadIncomingData(MPDBMigrator.incomingPlayerData);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log to console
|
||||||
|
*
|
||||||
|
* @param level The {@link Level} to log
|
||||||
|
* @param message Message to log
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message) {
|
||||||
|
plugin.getVelocityLogger().log(level, message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package me.william278.husksync.velocity.util;
|
||||||
|
|
||||||
|
import me.william278.husksync.util.Logger;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public record VelocityLogger(org.slf4j.Logger parent) implements Logger {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message, Exception e) {
|
||||||
|
logMessage(level, message);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, String message) {
|
||||||
|
logMessage(level, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String message) {
|
||||||
|
logMessage(Level.INFO, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void severe(String message) {
|
||||||
|
logMessage(Level.SEVERE, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(String message) {
|
||||||
|
logMessage(Level.CONFIG, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs the message using SLF4J
|
||||||
|
private void logMessage(Level level, String message) {
|
||||||
|
switch (level.intValue()) {
|
||||||
|
case 1000 -> parent.error(message); // Severe
|
||||||
|
case 900 -> parent.warn(message); // Warning
|
||||||
|
case 70 -> parent.warn("[Config] " + message);
|
||||||
|
default -> parent.info(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue