mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 00:59:18 +00:00
Implement compression via DataAdapter, add option to disable compression, better exception handling
This commit is contained in:
@@ -12,6 +12,9 @@ import net.william278.husksync.command.HuskSyncCommand;
|
||||
import net.william278.husksync.command.Permission;
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.CompressedDataAdapter;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.data.JsonDataAdapter;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.database.MySqlDatabase;
|
||||
import net.william278.husksync.listener.BukkitEventListener;
|
||||
@@ -46,6 +49,8 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
|
||||
private EventListener eventListener;
|
||||
|
||||
private DataAdapter dataAdapter;
|
||||
|
||||
private Settings settings;
|
||||
|
||||
private Locales locales;
|
||||
@@ -91,9 +96,18 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
}
|
||||
return loadedSettings;
|
||||
}).join();
|
||||
}).thenApply(succeeded -> {
|
||||
if (succeeded) {
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_COMPRESS_DATA)) {
|
||||
dataAdapter = new CompressedDataAdapter();
|
||||
} else {
|
||||
dataAdapter = new JsonDataAdapter();
|
||||
}
|
||||
}
|
||||
return succeeded;
|
||||
}).thenApply(succeeded -> {
|
||||
// Establish connection to the database
|
||||
this.database = new MySqlDatabase(settings, resourceReader, logger);
|
||||
this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter);
|
||||
if (succeeded) {
|
||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database...");
|
||||
final CompletableFuture<Boolean> databaseConnectFuture = new CompletableFuture<>();
|
||||
@@ -101,7 +115,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
final boolean initialized = this.database.initialize();
|
||||
if (!initialized) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "Failed to establish a connection to the database. "
|
||||
+ "Please check the supplied database credentials in the config file");
|
||||
+ "Please check the supplied database credentials in the config file");
|
||||
databaseConnectFuture.completeAsync(() -> false);
|
||||
return;
|
||||
}
|
||||
@@ -113,13 +127,13 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
return false;
|
||||
}).thenApply(succeeded -> {
|
||||
// Establish connection to the Redis server
|
||||
this.redisManager = new RedisManager(settings);
|
||||
this.redisManager = new RedisManager(settings, dataAdapter);
|
||||
if (succeeded) {
|
||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||
return this.redisManager.initialize().thenApply(initialized -> {
|
||||
if (!initialized) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "Failed to establish a connection to the Redis server. "
|
||||
+ "Please check the supplied Redis credentials in the config file");
|
||||
+ "Please check the supplied Redis credentials in the config file");
|
||||
return false;
|
||||
}
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the Redis server");
|
||||
@@ -167,7 +181,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
// Handle failed initialization
|
||||
if (!succeeded) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "Failed to initialize HuskSync. " +
|
||||
"The plugin will now be disabled");
|
||||
"The plugin will now be disabled");
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
} else {
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getVersion());
|
||||
@@ -207,6 +221,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
return redisManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DataAdapter getDataAdapter() {
|
||||
return dataAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Settings getSettings() {
|
||||
return settings;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class BukkitSerializer {
|
||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to serialize item stack data");
|
||||
throw new DataDeserializationException("Failed to serialize item stack data", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class BukkitSerializer {
|
||||
return inventoryContents;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Failed to deserialize item stack data");
|
||||
throw new DataDeserializationException("Failed to deserialize item stack data", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class BukkitSerializer {
|
||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to serialize potion effect data");
|
||||
throw new DataDeserializationException("Failed to serialize potion effect data", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -167,7 +167,7 @@ public class BukkitSerializer {
|
||||
return potionEffects;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Failed to deserialize potion effects", e);
|
||||
throw new DataDeserializationException("Failed to deserialize potion effects", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.william278.husksync;
|
||||
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
@@ -23,6 +24,8 @@ public interface HuskSync {
|
||||
|
||||
@NotNull RedisManager getRedisManager();
|
||||
|
||||
@NotNull DataAdapter getDataAdapter();
|
||||
|
||||
@NotNull Settings getSettings();
|
||||
|
||||
@NotNull Locales getLocales();
|
||||
|
||||
@@ -7,11 +7,15 @@ import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.util.UpdateChecker;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HuskSyncCommand extends CommandBase implements TabCompletable, ConsoleExecutable {
|
||||
|
||||
private final String[] COMMAND_ARGUMENTS = {"update", "about", "reload"};
|
||||
|
||||
public HuskSyncCommand(@NotNull HuskSync implementor) {
|
||||
super("husksync", Permission.COMMAND_HUSKSYNC, implementor);
|
||||
}
|
||||
@@ -32,8 +36,8 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
updateChecker.fetchLatestVersion().thenAccept(latestVersion -> {
|
||||
if (updateChecker.isUpdateAvailable(latestVersion)) {
|
||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| A new update is available:](#00fb9a) [HuskSync " + updateChecker.fetchLatestVersion() + "](#00fb9a bold)" +
|
||||
"[•](white) [Currently running:](#00fb9a) [Version " + updateChecker.getCurrentVersion() + "](gray)" +
|
||||
"[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husksync.97144/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husksync.1634/updates) [•](#262626) [[⏩ Songoda]](gray open_url=https://songoda.com/marketplace/product/husksync-a-modern-cross-server-player-data-synchronization-system.758)"));
|
||||
"[•](white) [Currently running:](#00fb9a) [Version " + updateChecker.getCurrentVersion() + "](gray)" +
|
||||
"[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husksync.97144/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husksync.1634/updates) [•](#262626) [[⏩ Songoda]](gray open_url=https://songoda.com/marketplace/product/husksync-a-modern-cross-server-player-data-synchronization-system.758)"));
|
||||
} else {
|
||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running version " + latestVersion + "](#00fb9a)"));
|
||||
}
|
||||
@@ -56,11 +60,12 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
@Override
|
||||
public void onConsoleExecute(@NotNull String[] args) {
|
||||
if (args.length < 1) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: /husksync <update/info/reload/migrate>");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: \"husksync <update/info/reload/migrate>\"");
|
||||
return;
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "update", "version" -> new UpdateChecker(plugin.getVersion(), plugin.getLoggingAdapter()).logToConsole();
|
||||
case "update", "version" ->
|
||||
new UpdateChecker(plugin.getVersion(), plugin.getLoggingAdapter()).logToConsole();
|
||||
case "info", "about" -> plugin.getLoggingAdapter().log(Level.INFO, plugin.getLocales().stripMineDown(
|
||||
Locales.PLUGIN_INFORMATION.replace("%version%", plugin.getVersion())));
|
||||
case "reload" -> {
|
||||
@@ -71,13 +76,15 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
//todo - MPDB migrator
|
||||
}
|
||||
default ->
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid syntax. Console usage: /husksync <update/info/reload/migrate>");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid syntax. Console usage: \"husksync <update/info/reload/migrate>\"");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||
return null;
|
||||
return Arrays.stream(COMMAND_ARGUMENTS)
|
||||
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
||||
.sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void displayPluginInformation(@NotNull OnlineUser player) {
|
||||
|
||||
@@ -141,6 +141,7 @@ public class Settings {
|
||||
|
||||
SYNCHRONIZATION_MAX_USER_DATA_RECORDS("synchronization.max_user_data_records", OptionType.INTEGER, 5),
|
||||
SYNCHRONIZATION_SAVE_ON_WORLD_SAVE("synchronization.save_on_world_save", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_COMPRESS_DATA("synchronization.compress_data", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_INVENTORIES("synchronization.features.inventories", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_ENDER_CHESTS("synchronization.features.ender_chests", OptionType.BOOLEAN, true),
|
||||
SYNCHRONIZATION_SYNC_HEALTH("synchronization.features.health", OptionType.BOOLEAN, true),
|
||||
|
||||
@@ -23,11 +23,12 @@ public class AdvancementData {
|
||||
@SerializedName("completed_criteria")
|
||||
public Map<String, Date> completedCriteria;
|
||||
|
||||
public AdvancementData() {
|
||||
}
|
||||
|
||||
public AdvancementData(@NotNull String key, @NotNull Map<String, Date> awardedCriteria) {
|
||||
this.key = key;
|
||||
this.completedCriteria = awardedCriteria;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected AdvancementData() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.xerial.snappy.Snappy;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CompressedDataAdapter extends JsonDataAdapter {
|
||||
|
||||
@Override
|
||||
public byte[] toBytes(@NotNull UserData data) throws DataAdaptionException {
|
||||
try {
|
||||
return Snappy.compress(super.toBytes(data));
|
||||
} catch (IOException e) {
|
||||
throw new DataAdaptionException("Failed to compress data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UserData fromBytes(byte[] data) throws DataAdaptionException {
|
||||
try {
|
||||
return super.fromBytes(Snappy.uncompress(data));
|
||||
} catch (IOException e) {
|
||||
throw new DataAdaptionException("Failed to decompress data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An adapter that adapts {@link UserData} to and from a portable byte array.
|
||||
*/
|
||||
public interface DataAdapter {
|
||||
|
||||
/**
|
||||
* Converts {@link UserData} to a byte array.
|
||||
*
|
||||
* @param data The {@link UserData} to adapt.
|
||||
* @return The byte array.
|
||||
* @throws DataAdaptionException If an error occurred during adaptation.
|
||||
*/
|
||||
byte[] toBytes(@NotNull UserData data) throws DataAdaptionException;
|
||||
|
||||
/**
|
||||
* Converts a byte array to {@link UserData}.
|
||||
*
|
||||
* @param data The byte array to adapt.
|
||||
* @return The {@link UserData}.
|
||||
* @throws DataAdaptionException If an error occurred during adaptation, such as if the byte array is invalid.
|
||||
*/
|
||||
@NotNull
|
||||
UserData fromBytes(final byte[] data) throws DataAdaptionException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
/**
|
||||
* Indicates an error occurred during {@link UserData} adaptation to and from (compressed) json.
|
||||
*/
|
||||
public class DataAdaptionException extends RuntimeException {
|
||||
protected DataAdaptionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
/**
|
||||
* Indicates an error occurred during base-64 serialization and deserialization of data.
|
||||
* </p>
|
||||
* For example, an exception deserializing {@link InventoryData} item stack or {@link PotionEffectData} potion effect arrays
|
||||
*/
|
||||
public class DataDeserializationException extends RuntimeException {
|
||||
protected DataDeserializationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,8 @@ public class InventoryData {
|
||||
this.serializedInventory = serializedInventory;
|
||||
}
|
||||
|
||||
public InventoryData() {
|
||||
@SuppressWarnings("unused")
|
||||
protected InventoryData() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class JsonDataAdapter implements DataAdapter {
|
||||
|
||||
@Override
|
||||
public byte[] toBytes(@NotNull UserData data) throws DataAdaptionException {
|
||||
return new GsonBuilder().create().toJson(data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull UserData fromBytes(byte[] data) throws DataAdaptionException {
|
||||
try {
|
||||
return new GsonBuilder().create().fromJson(new String(data, StandardCharsets.UTF_8), UserData.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new DataAdaptionException("Failed to parse JSON data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,9 +53,6 @@ public class LocationData {
|
||||
@SerializedName("pitch")
|
||||
public float pitch;
|
||||
|
||||
public LocationData() {
|
||||
}
|
||||
|
||||
public LocationData(@NotNull String worldName, @NotNull UUID worldUuid,
|
||||
@NotNull String worldEnvironment,
|
||||
double x, double y, double z,
|
||||
@@ -69,4 +66,8 @@ public class LocationData {
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected LocationData() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ public class PersistentDataContainerData {
|
||||
this.persistentDataMap = persistentDataMap;
|
||||
}
|
||||
|
||||
public PersistentDataContainerData() {
|
||||
@SuppressWarnings("unused")
|
||||
protected PersistentDataContainerData() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ public class PotionEffectData {
|
||||
this.serializedPotionEffects = serializedInventory;
|
||||
}
|
||||
|
||||
public PotionEffectData() {
|
||||
@SuppressWarnings("unused")
|
||||
protected PotionEffectData() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ public class StatisticsData {
|
||||
this.entityStatistics = entityStatistics;
|
||||
}
|
||||
|
||||
public StatisticsData() {
|
||||
@SuppressWarnings("unused")
|
||||
protected StatisticsData() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public class StatusData {
|
||||
this.isFlying = isFlying;
|
||||
}
|
||||
|
||||
public StatusData() {
|
||||
@SuppressWarnings("unused")
|
||||
protected StatusData() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -74,6 +72,8 @@ public class UserData {
|
||||
this.persistentDataContainerData = persistentDataContainerData;
|
||||
}
|
||||
|
||||
// Empty constructor to facilitate json serialization
|
||||
@SuppressWarnings("unused")
|
||||
protected UserData() {
|
||||
}
|
||||
|
||||
@@ -109,14 +109,4 @@ public class UserData {
|
||||
return persistentDataContainerData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static UserData fromJson(String json) throws JsonSyntaxException {
|
||||
return new GsonBuilder().create().fromJson(json, UserData.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toJson() {
|
||||
return new GsonBuilder().create().toJson(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.william278.husksync.database;
|
||||
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.VersionedUserData;
|
||||
import net.william278.husksync.player.User;
|
||||
@@ -36,6 +37,20 @@ public abstract class Database {
|
||||
*/
|
||||
protected final int maxUserDataRecords;
|
||||
|
||||
/**
|
||||
* {@link DataAdapter} implementation used for adapting {@link UserData} to and from JSON
|
||||
*/
|
||||
private final DataAdapter dataAdapter;
|
||||
|
||||
/**
|
||||
* Returns the {@link DataAdapter} used to adapt {@link UserData} to and from JSON
|
||||
*
|
||||
* @return instance of the {@link DataAdapter} implementation
|
||||
*/
|
||||
protected DataAdapter getDataAdapter() {
|
||||
return dataAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger instance used for database error logging
|
||||
*/
|
||||
@@ -56,11 +71,12 @@ public abstract class Database {
|
||||
private final ResourceReader resourceReader;
|
||||
|
||||
protected Database(@NotNull String playerTableName, @NotNull String dataTableName, final int maxUserDataRecords,
|
||||
@NotNull ResourceReader resourceReader, @NotNull Logger logger) {
|
||||
@NotNull ResourceReader resourceReader, @NotNull DataAdapter dataAdapter, @NotNull Logger logger) {
|
||||
this.playerTableName = playerTableName;
|
||||
this.dataTableName = dataTableName;
|
||||
this.maxUserDataRecords = maxUserDataRecords;
|
||||
this.resourceReader = resourceReader;
|
||||
this.dataAdapter = dataAdapter;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.william278.husksync.database;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.data.DataAdaptionException;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.VersionedUserData;
|
||||
import net.william278.husksync.player.User;
|
||||
@@ -52,22 +54,23 @@ public class MySqlDatabase extends Database {
|
||||
*/
|
||||
private HikariDataSource connectionPool;
|
||||
|
||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger) {
|
||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger,
|
||||
@NotNull DataAdapter dataAdapter) {
|
||||
super(settings.getStringValue(Settings.ConfigOption.DATABASE_PLAYERS_TABLE_NAME),
|
||||
settings.getStringValue(Settings.ConfigOption.DATABASE_DATA_TABLE_NAME),
|
||||
settings.getIntegerValue(Settings.ConfigOption.SYNCHRONIZATION_MAX_USER_DATA_RECORDS),
|
||||
resourceReader, logger);
|
||||
mySqlHost = settings.getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
||||
mySqlPort = settings.getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
||||
mySqlDatabaseName = settings.getStringValue(Settings.ConfigOption.DATABASE_NAME);
|
||||
mySqlUsername = settings.getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
|
||||
mySqlPassword = settings.getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
|
||||
mySqlConnectionParameters = settings.getStringValue(Settings.ConfigOption.DATABASE_CONNECTION_PARAMS);
|
||||
hikariMaximumPoolSize = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_SIZE);
|
||||
hikariMinimumIdle = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MIN_IDLE);
|
||||
hikariMaximumLifetime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_LIFETIME);
|
||||
hikariKeepAliveTime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_KEEPALIVE);
|
||||
hikariConnectionTimeOut = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_TIMEOUT);
|
||||
resourceReader, dataAdapter, logger);
|
||||
this.mySqlHost = settings.getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
||||
this.mySqlPort = settings.getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
||||
this.mySqlDatabaseName = settings.getStringValue(Settings.ConfigOption.DATABASE_NAME);
|
||||
this.mySqlUsername = settings.getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
|
||||
this.mySqlPassword = settings.getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
|
||||
this.mySqlConnectionParameters = settings.getStringValue(Settings.ConfigOption.DATABASE_CONNECTION_PARAMS);
|
||||
this.hikariMaximumPoolSize = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_SIZE);
|
||||
this.hikariMinimumIdle = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MIN_IDLE);
|
||||
this.hikariMaximumLifetime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_LIFETIME);
|
||||
this.hikariKeepAliveTime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_KEEPALIVE);
|
||||
this.hikariConnectionTimeOut = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,16 +222,15 @@ public class MySqlDatabase extends Database {
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] compressedDataJson = blob.getBytes(1, (int) blob.length());
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
return Optional.of(new VersionedUserData(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
UserData.fromJson(new String(Snappy.uncompress(compressedDataJson),
|
||||
StandardCharsets.UTF_8))));
|
||||
getDataAdapter().fromBytes(dataByteArray)));
|
||||
}
|
||||
}
|
||||
} catch (SQLException | IOException e) {
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
@@ -249,18 +251,17 @@ public class MySqlDatabase extends Database {
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] compressedDataJson = blob.getBytes(1, (int) blob.length());
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
final VersionedUserData data = new VersionedUserData(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
UserData.fromJson(new String(Snappy.uncompress(compressedDataJson),
|
||||
StandardCharsets.UTF_8)));
|
||||
getDataAdapter().fromBytes(dataByteArray));
|
||||
retrievedData.add(data);
|
||||
}
|
||||
return retrievedData;
|
||||
}
|
||||
} catch (SQLException | IOException e) {
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return retrievedData;
|
||||
@@ -297,11 +298,11 @@ public class MySqlDatabase extends Database {
|
||||
(`player_uuid`,`version_uuid`,`timestamp`,`data`)
|
||||
VALUES (?,UUID(),NOW(),?);"""))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.setBlob(2, new ByteArrayInputStream(Snappy
|
||||
.compress(userData.toJson().getBytes(StandardCharsets.UTF_8))));
|
||||
statement.setBlob(2, new ByteArrayInputStream(
|
||||
getDataAdapter().toBytes(userData)));
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | IOException e) {
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||
}
|
||||
}).thenRun(() -> pruneUserDataRecords(user).join());
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.player.User;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -94,7 +93,7 @@ public abstract class EventListener {
|
||||
}
|
||||
|
||||
public final void handleWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
||||
if (disabling) {
|
||||
if (disabling || !huskSync.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> usersInWorld.forEach(user ->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -26,6 +27,7 @@ public class RedisManager {
|
||||
private static String clusterId = "";
|
||||
|
||||
private final JedisPoolConfig jedisPoolConfig;
|
||||
private final DataAdapter dataAdapter;
|
||||
|
||||
private final String redisHost;
|
||||
private final int redisPort;
|
||||
@@ -34,8 +36,9 @@ public class RedisManager {
|
||||
|
||||
private JedisPool jedisPool;
|
||||
|
||||
public RedisManager(@NotNull Settings settings) {
|
||||
public RedisManager(@NotNull Settings settings, @NotNull DataAdapter dataAdapter) {
|
||||
clusterId = settings.getStringValue(Settings.ConfigOption.CLUSTER_ID);
|
||||
this.dataAdapter = dataAdapter;
|
||||
this.redisHost = settings.getStringValue(Settings.ConfigOption.REDIS_HOST);
|
||||
this.redisPort = settings.getIntegerValue(Settings.ConfigOption.REDIS_PORT);
|
||||
this.redisPassword = settings.getStringValue(Settings.ConfigOption.REDIS_PASSWORD);
|
||||
@@ -72,9 +75,8 @@ public class RedisManager {
|
||||
/**
|
||||
* Set a user's data to the Redis server
|
||||
*
|
||||
* @param user the user to set data for
|
||||
* @param userData the user's data to set
|
||||
* @param redisKeyType the type of key to set the data with. This determines the time to live for the data.
|
||||
* @param user the user to set data for
|
||||
* @param userData the user's data to set
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData) {
|
||||
@@ -82,11 +84,9 @@ public class RedisManager {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
// Set the user's data as a compressed byte array of the json using Snappy
|
||||
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid), RedisKeyType.DATA_UPDATE.timeToLive,
|
||||
Snappy.compress(userData.toJson().getBytes(StandardCharsets.UTF_8)));
|
||||
System.out.println("Set key at " + new Date().getTime());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid),
|
||||
RedisKeyType.DATA_UPDATE.timeToLive,
|
||||
dataAdapter.toBytes(userData));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -107,8 +107,7 @@ public class RedisManager {
|
||||
/**
|
||||
* Fetch a user's data from the Redis server and consume the key if found
|
||||
*
|
||||
* @param user The user to fetch data for
|
||||
* @param redisKeyType The type of key to fetch
|
||||
* @param user The user to fetch data for
|
||||
* @return The user's data, if it's present on the database. Otherwise, an empty optional.
|
||||
*/
|
||||
public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||
@@ -116,18 +115,15 @@ public class RedisManager {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
||||
System.out.println("Reading key at " + new Date().getTime());
|
||||
final byte[] compressedJson = jedis.get(key);
|
||||
if (compressedJson == null) {
|
||||
final byte[] dataByteArray = jedis.get(key);
|
||||
if (dataByteArray == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// Consume the key (delete from redis)
|
||||
jedis.del(key);
|
||||
|
||||
// Use Snappy to decompress the json
|
||||
return Optional.of(UserData.fromJson(new String(Snappy.uncompress(compressedJson),
|
||||
StandardCharsets.UTF_8)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
return Optional.of(dataAdapter.fromBytes(dataByteArray));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ redis:
|
||||
synchronization:
|
||||
max_user_data_records: 5
|
||||
save_on_world_save: true
|
||||
compress_data: true
|
||||
features:
|
||||
inventories: true
|
||||
ender_chests: true
|
||||
|
||||
Reference in New Issue
Block a user