diff --git a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java
index 4dd79800..d95d0b20 100644
--- a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java
+++ b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java
@@ -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 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;
diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java
index f1603ac9..0419fe56 100644
--- a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java
+++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java
@@ -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);
}
});
}
diff --git a/common/src/main/java/net/william278/husksync/HuskSync.java b/common/src/main/java/net/william278/husksync/HuskSync.java
index 1c20308d..a689a749 100644
--- a/common/src/main/java/net/william278/husksync/HuskSync.java
+++ b/common/src/main/java/net/william278/husksync/HuskSync.java
@@ -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();
diff --git a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java
index f7a9c3a7..73c2db55 100644
--- a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java
+++ b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java
@@ -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 ");
+ plugin.getLoggingAdapter().log(Level.INFO, "Console usage: \"husksync \"");
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 ");
+ plugin.getLoggingAdapter().log(Level.INFO, "Invalid syntax. Console usage: \"husksync \"");
}
}
@Override
public List 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) {
diff --git a/common/src/main/java/net/william278/husksync/config/Settings.java b/common/src/main/java/net/william278/husksync/config/Settings.java
index 0ad81a83..2f8e6c7d 100644
--- a/common/src/main/java/net/william278/husksync/config/Settings.java
+++ b/common/src/main/java/net/william278/husksync/config/Settings.java
@@ -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),
diff --git a/common/src/main/java/net/william278/husksync/data/AdvancementData.java b/common/src/main/java/net/william278/husksync/data/AdvancementData.java
index 67860a04..c0abd2cd 100644
--- a/common/src/main/java/net/william278/husksync/data/AdvancementData.java
+++ b/common/src/main/java/net/william278/husksync/data/AdvancementData.java
@@ -23,11 +23,12 @@ public class AdvancementData {
@SerializedName("completed_criteria")
public Map completedCriteria;
- public AdvancementData() {
- }
-
public AdvancementData(@NotNull String key, @NotNull Map awardedCriteria) {
this.key = key;
this.completedCriteria = awardedCriteria;
}
+
+ @SuppressWarnings("unused")
+ protected AdvancementData() {
+ }
}
diff --git a/common/src/main/java/net/william278/husksync/data/CompressedDataAdapter.java b/common/src/main/java/net/william278/husksync/data/CompressedDataAdapter.java
new file mode 100644
index 00000000..3beccc79
--- /dev/null
+++ b/common/src/main/java/net/william278/husksync/data/CompressedDataAdapter.java
@@ -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);
+ }
+ }
+}
diff --git a/common/src/main/java/net/william278/husksync/data/DataAdapter.java b/common/src/main/java/net/william278/husksync/data/DataAdapter.java
new file mode 100644
index 00000000..1ba2fa33
--- /dev/null
+++ b/common/src/main/java/net/william278/husksync/data/DataAdapter.java
@@ -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;
+
+}
diff --git a/common/src/main/java/net/william278/husksync/data/DataAdaptionException.java b/common/src/main/java/net/william278/husksync/data/DataAdaptionException.java
new file mode 100644
index 00000000..b38364bc
--- /dev/null
+++ b/common/src/main/java/net/william278/husksync/data/DataAdaptionException.java
@@ -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);
+ }
+
+}
diff --git a/common/src/main/java/net/william278/husksync/data/DataDeserializationException.java b/common/src/main/java/net/william278/husksync/data/DataDeserializationException.java
new file mode 100644
index 00000000..6812b37f
--- /dev/null
+++ b/common/src/main/java/net/william278/husksync/data/DataDeserializationException.java
@@ -0,0 +1,13 @@
+package net.william278.husksync.data;
+
+/**
+ * Indicates an error occurred during base-64 serialization and deserialization of data.
+ *
+ * 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);
+ }
+
+}
diff --git a/common/src/main/java/net/william278/husksync/data/InventoryData.java b/common/src/main/java/net/william278/husksync/data/InventoryData.java
index 0487c42c..f0ac1b2e 100644
--- a/common/src/main/java/net/william278/husksync/data/InventoryData.java
+++ b/common/src/main/java/net/william278/husksync/data/InventoryData.java
@@ -18,7 +18,8 @@ public class InventoryData {
this.serializedInventory = serializedInventory;
}
- public InventoryData() {
+ @SuppressWarnings("unused")
+ protected InventoryData() {
}
}
diff --git a/common/src/main/java/net/william278/husksync/data/JsonDataAdapter.java b/common/src/main/java/net/william278/husksync/data/JsonDataAdapter.java
new file mode 100644
index 00000000..f3b1e6b2
--- /dev/null
+++ b/common/src/main/java/net/william278/husksync/data/JsonDataAdapter.java
@@ -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);
+ }
+ }
+}
diff --git a/common/src/main/java/net/william278/husksync/data/LocationData.java b/common/src/main/java/net/william278/husksync/data/LocationData.java
index ee8d2a5f..e567e49d 100644
--- a/common/src/main/java/net/william278/husksync/data/LocationData.java
+++ b/common/src/main/java/net/william278/husksync/data/LocationData.java
@@ -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() {
+ }
}
diff --git a/common/src/main/java/net/william278/husksync/data/PersistentDataContainerData.java b/common/src/main/java/net/william278/husksync/data/PersistentDataContainerData.java
index c1c9cc57..ae3e50cc 100644
--- a/common/src/main/java/net/william278/husksync/data/PersistentDataContainerData.java
+++ b/common/src/main/java/net/william278/husksync/data/PersistentDataContainerData.java
@@ -20,7 +20,8 @@ public class PersistentDataContainerData {
this.persistentDataMap = persistentDataMap;
}
- public PersistentDataContainerData() {
+ @SuppressWarnings("unused")
+ protected PersistentDataContainerData() {
}
}
diff --git a/common/src/main/java/net/william278/husksync/data/PotionEffectData.java b/common/src/main/java/net/william278/husksync/data/PotionEffectData.java
index a8ad390e..cf8662a6 100644
--- a/common/src/main/java/net/william278/husksync/data/PotionEffectData.java
+++ b/common/src/main/java/net/william278/husksync/data/PotionEffectData.java
@@ -15,7 +15,8 @@ public class PotionEffectData {
this.serializedPotionEffects = serializedInventory;
}
- public PotionEffectData() {
+ @SuppressWarnings("unused")
+ protected PotionEffectData() {
}
}
diff --git a/common/src/main/java/net/william278/husksync/data/StatisticsData.java b/common/src/main/java/net/william278/husksync/data/StatisticsData.java
index 62a2d409..c0d17d30 100644
--- a/common/src/main/java/net/william278/husksync/data/StatisticsData.java
+++ b/common/src/main/java/net/william278/husksync/data/StatisticsData.java
@@ -45,7 +45,8 @@ public class StatisticsData {
this.entityStatistics = entityStatistics;
}
- public StatisticsData() {
+ @SuppressWarnings("unused")
+ protected StatisticsData() {
}
}
diff --git a/common/src/main/java/net/william278/husksync/data/StatusData.java b/common/src/main/java/net/william278/husksync/data/StatusData.java
index 7606ca7e..d6b7f9c0 100644
--- a/common/src/main/java/net/william278/husksync/data/StatusData.java
+++ b/common/src/main/java/net/william278/husksync/data/StatusData.java
@@ -98,6 +98,7 @@ public class StatusData {
this.isFlying = isFlying;
}
- public StatusData() {
+ @SuppressWarnings("unused")
+ protected StatusData() {
}
}
diff --git a/common/src/main/java/net/william278/husksync/data/UserData.java b/common/src/main/java/net/william278/husksync/data/UserData.java
index 93fc1007..564dad04 100644
--- a/common/src/main/java/net/william278/husksync/data/UserData.java
+++ b/common/src/main/java/net/william278/husksync/data/UserData.java
@@ -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);
- }
-
}
diff --git a/common/src/main/java/net/william278/husksync/database/Database.java b/common/src/main/java/net/william278/husksync/database/Database.java
index e7bc4d2f..25b4b07d 100644
--- a/common/src/main/java/net/william278/husksync/database/Database.java
+++ b/common/src/main/java/net/william278/husksync/database/Database.java
@@ -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;
}
diff --git a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java
index 106671aa..1dd9c0d8 100644
--- a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java
+++ b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java
@@ -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());
diff --git a/common/src/main/java/net/william278/husksync/listener/EventListener.java b/common/src/main/java/net/william278/husksync/listener/EventListener.java
index 84ddea52..21e4268f 100644
--- a/common/src/main/java/net/william278/husksync/listener/EventListener.java
+++ b/common/src/main/java/net/william278/husksync/listener/EventListener.java
@@ -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 usersInWorld) {
- if (disabling) {
+ if (disabling || !huskSync.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) {
return;
}
CompletableFuture.runAsync(() -> usersInWorld.forEach(user ->
diff --git a/common/src/main/java/net/william278/husksync/redis/RedisManager.java b/common/src/main/java/net/william278/husksync/redis/RedisManager.java
index 50d4c116..02ce812e 100644
--- a/common/src/main/java/net/william278/husksync/redis/RedisManager.java
+++ b/common/src/main/java/net/william278/husksync/redis/RedisManager.java
@@ -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 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> 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));
}
});
}
diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml
index 0a851709..161736a4 100644
--- a/common/src/main/resources/config.yml
+++ b/common/src/main/resources/config.yml
@@ -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