Merge branch 'master' into hotfix/death-sync-event

feat/data-edit-commands
William 3 years ago
commit 33904d82d0

@ -72,7 +72,7 @@ public abstract class BaseHuskSyncAPI {
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) { public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
if (user instanceof OnlineUser) { if (user instanceof OnlineUser) {
return Optional.of(((OnlineUser) user).getUserData().join()); return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter()).join();
} else { } else {
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData); return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
} }
@ -103,8 +103,8 @@ public abstract class BaseHuskSyncAPI {
* @since 2.0 * @since 2.0
*/ */
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) { public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
return CompletableFuture.runAsync(() -> user.getUserData().thenAccept(userData -> return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter()).thenAccept(optionalUserData -> optionalUserData.ifPresent(
plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())); userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
} }
/** /**

@ -76,8 +76,8 @@ public abstract class EventListener {
} }
if (disabling || currentMilliseconds.get() > TIME_OUT_MILLISECONDS) { if (disabling || currentMilliseconds.get() > TIME_OUT_MILLISECONDS) {
executor.shutdown(); executor.shutdown();
setUserFromDatabase(user) setUserFromDatabase(user).thenAccept(
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)); succeeded -> handleSynchronisationCompletion(user, succeeded));
return; return;
} }
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData -> plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
@ -145,9 +145,10 @@ public abstract class EventListener {
if (usersAwaitingSync.contains(user.uuid)) { if (usersAwaitingSync.contains(user.uuid)) {
return; return;
} }
plugin.getRedisManager().setUserServerSwitch(user).thenRun(() -> user.getUserData().thenAccept( plugin.getRedisManager().setUserServerSwitch(user).thenRun(() -> user.getUserData(plugin.getLoggingAdapter()).thenAccept(
optionalUserData -> optionalUserData.ifPresent(
userData -> plugin.getRedisManager().setUserData(user, userData).thenRun( userData -> plugin.getRedisManager().setUserData(user, userData).thenRun(
() -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.DISCONNECT).join()))); () -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.DISCONNECT).join()))));
usersAwaitingSync.remove(user.uuid); usersAwaitingSync.remove(user.uuid);
} }
@ -160,8 +161,8 @@ public abstract class EventListener {
if (disabling || !plugin.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) { if (disabling || !plugin.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) {
return; return;
} }
usersInWorld.forEach(user -> plugin.getDatabase().setUserData(user, user.getUserData().join(), usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter()).join().ifPresent(
DataSaveCause.WORLD_SAVE).join()); userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.WORLD_SAVE).join()));
} }
/** /**
@ -204,8 +205,9 @@ public abstract class EventListener {
public final void handlePluginDisable() { public final void handlePluginDisable() {
disabling = true; disabling = true;
plugin.getOnlineUsers().stream().filter(user -> !usersAwaitingSync.contains(user.uuid)).forEach(user -> plugin.getOnlineUsers().stream().filter(user -> !usersAwaitingSync.contains(user.uuid)).forEach(
plugin.getDatabase().setUserData(user, user.getUserData().join(), DataSaveCause.SERVER_SHUTDOWN).join()); user -> user.getUserData(plugin.getLoggingAdapter()).join().ifPresent(
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()));
plugin.getDatabase().close(); plugin.getDatabase().close();
plugin.getRedisManager().close(); plugin.getRedisManager().close();

@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.logging.Level; import java.util.logging.Level;
@ -251,16 +252,23 @@ public abstract class OnlineUser extends User {
public abstract void showMenu(@NotNull ItemEditorMenu menu); public abstract void showMenu(@NotNull ItemEditorMenu menu);
/** /**
* Get the player's current {@link UserData} * Get the player's current {@link UserData} in an {@link Optional}
* </p>
* If the user data could not be returned due to an exception, the optional will return empty
* *
* @return the player's current {@link UserData} * @param logger The logger to use for handling exceptions
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
*/ */
public final CompletableFuture<UserData> getUserData() { public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger) {
return CompletableFuture.supplyAsync( return CompletableFuture.supplyAsync(() -> Optional.of(new UserData(getStatus().join(), getInventory().join(),
() -> new UserData(getStatus().join(), getInventory().join(),
getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(), getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(),
getStatistics().join(), getLocation().join(), getPersistentDataContainer().join(), getStatistics().join(), getLocation().join(), getPersistentDataContainer().join(),
getMinecraftVersion().toString())); getMinecraftVersion().toString())))
.exceptionally(exception -> {
logger.log(Level.SEVERE, "Failed to fetch user data for online player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace();
return Optional.empty();
});
} }
} }

@ -165,13 +165,17 @@ public class RedisManager {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) { try (Jedis jedis = jedisPool.getResource()) {
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid); final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
plugin.getLoggingAdapter().debug("[" + user.username + "] Read " + RedisKeyType.DATA_UPDATE.name()
+ " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
final byte[] dataByteArray = jedis.get(key); final byte[] dataByteArray = jedis.get(key);
if (dataByteArray == null) { if (dataByteArray == null) {
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
return Optional.empty(); return Optional.empty();
} }
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
// Consume the key (delete from redis) // Consume the key (delete from redis)
jedis.del(key); jedis.del(key);
@ -188,13 +192,17 @@ public class RedisManager {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) { try (Jedis jedis = jedisPool.getResource()) {
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid); final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
plugin.getLoggingAdapter().debug("[" + user.username + "] Read " + RedisKeyType.SERVER_SWITCH.name()
+ " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
final byte[] readData = jedis.get(key); final byte[] readData = jedis.get(key);
if (readData == null) { if (readData == null) {
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
return false; return false;
} }
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
// Consume the key (delete from redis) // Consume the key (delete from redis)
jedis.del(key); jedis.del(key);
return true; return true;

@ -1,11 +1,14 @@
package net.william278.husksync.data; package net.william278.husksync.data;
import net.william278.husksync.logger.DummyLogger;
import net.william278.husksync.player.DummyPlayer; import net.william278.husksync.player.DummyPlayer;
import net.william278.husksync.player.OnlineUser; import net.william278.husksync.player.OnlineUser;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Tests for the data system {@link DataAdapter} * Tests for the data system {@link DataAdapter}
@ -15,12 +18,13 @@ public class DataAdaptionTests {
@Test @Test
public void testJsonDataAdapter() { public void testJsonDataAdapter() {
final OnlineUser dummyUser = DummyPlayer.create(); final OnlineUser dummyUser = DummyPlayer.create();
final UserData dummyUserData = dummyUser.getUserData().join(); final AtomicBoolean isEquals = new AtomicBoolean(false);
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
final DataAdapter dataAdapter = new JsonDataAdapter(); final DataAdapter dataAdapter = new JsonDataAdapter();
final byte[] data = dataAdapter.toBytes(dummyUserData); final byte[] data = dataAdapter.toBytes(dummyUserData);
final UserData deserializedUserData = dataAdapter.fromBytes(data); final UserData deserializedUserData = dataAdapter.fromBytes(data);
boolean isEquals = deserializedUserData.getInventoryData().serializedItems isEquals.set(deserializedUserData.getInventoryData().serializedItems
.equals(dummyUserData.getInventoryData().serializedItems) .equals(dummyUserData.getInventoryData().serializedItems)
&& deserializedUserData.getEnderChestData().serializedItems && deserializedUserData.getEnderChestData().serializedItems
.equals(dummyUserData.getEnderChestData().serializedItems) .equals(dummyUserData.getEnderChestData().serializedItems)
@ -33,31 +37,34 @@ public class DataAdaptionTests {
&& deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot && deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot
&& deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience && deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience
&& deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth && deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth
&& deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale; && deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale);
});
Assertions.assertTrue(isEquals); Assertions.assertTrue(isEquals.get());
} }
@Test @Test
public void testJsonFormat() { public void testJsonFormat() {
final OnlineUser dummyUser = DummyPlayer.create(); final OnlineUser dummyUser = DummyPlayer.create();
final UserData dummyUserData = dummyUser.getUserData().join(); final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"minecraft_version\":\"1.19-beta123456\",\"format_version\":1}";
AtomicReference<String> json = new AtomicReference<>();
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
final DataAdapter dataAdapter = new JsonDataAdapter(); final DataAdapter dataAdapter = new JsonDataAdapter();
final byte[] data = dataAdapter.toBytes(dummyUserData); final byte[] data = dataAdapter.toBytes(dummyUserData);
final String json = new String(data, StandardCharsets.UTF_8); json.set(new String(data, StandardCharsets.UTF_8));
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"minecraft_version\":\"1.19-beta123456\",\"format_version\":1}"; });
Assertions.assertEquals(expectedJson, json); Assertions.assertEquals(expectedJson, json.get());
} }
@Test @Test
public void testCompressedDataAdapter() { public void testCompressedDataAdapter() {
final OnlineUser dummyUser = DummyPlayer.create(); final OnlineUser dummyUser = DummyPlayer.create();
final UserData dummyUserData = dummyUser.getUserData().join(); AtomicBoolean isEquals = new AtomicBoolean(false);
dummyUser.getUserData(new DummyLogger()).join().ifPresent(dummyUserData -> {
final DataAdapter dataAdapter = new CompressedDataAdapter(); final DataAdapter dataAdapter = new CompressedDataAdapter();
final byte[] data = dataAdapter.toBytes(dummyUserData); final byte[] data = dataAdapter.toBytes(dummyUserData);
final UserData deserializedUserData = dataAdapter.fromBytes(data); final UserData deserializedUserData = dataAdapter.fromBytes(data);
boolean isEquals = deserializedUserData.getInventoryData().serializedItems isEquals.set(deserializedUserData.getInventoryData().serializedItems
.equals(dummyUserData.getInventoryData().serializedItems) .equals(dummyUserData.getInventoryData().serializedItems)
&& deserializedUserData.getEnderChestData().serializedItems && deserializedUserData.getEnderChestData().serializedItems
.equals(dummyUserData.getEnderChestData().serializedItems) .equals(dummyUserData.getEnderChestData().serializedItems)
@ -70,9 +77,9 @@ public class DataAdaptionTests {
&& deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot && deserializedUserData.getStatusData().selectedItemSlot == dummyUserData.getStatusData().selectedItemSlot
&& deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience && deserializedUserData.getStatusData().totalExperience == dummyUserData.getStatusData().totalExperience
&& deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth && deserializedUserData.getStatusData().maxHealth == dummyUserData.getStatusData().maxHealth
&& deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale; && deserializedUserData.getStatusData().healthScale == dummyUserData.getStatusData().healthScale);
});
Assertions.assertTrue(isEquals); Assertions.assertTrue(isEquals.get());
} }
} }

@ -0,0 +1,44 @@
package net.william278.husksync.logger;
import de.themoep.minedown.MineDown;
import net.william278.husksync.util.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Level;
public class DummyLogger extends Logger {
public DummyLogger() {
}
@Override
public void log(@NotNull Level level, @NotNull String message, @NotNull Exception e) {
System.out.println(level.getName() + ": " + message);
e.printStackTrace();
}
@Override
public void log(@NotNull Level level, @NotNull String message) {
System.out.println(level.getName() + ": " + message);
}
@Override
public void log(@NotNull Level level, @NotNull MineDown mineDown) {
System.out.println(level.getName() + ": " + mineDown.message());
}
@Override
public void info(@NotNull String message) {
System.out.println(Level.INFO.getName() + ": " + message);
}
@Override
public void severe(@NotNull String message) {
System.out.println(Level.SEVERE.getName() + ": " + message);
}
@Override
public void config(@NotNull String message) {
System.out.println(Level.CONFIG.getName() + ": " + message);
}
}

@ -3,5 +3,5 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.daemon=true org.gradle.daemon=true
javaVersion=16 javaVersion=16
plugin_version=2.0 plugin_version=2.1
plugin_archive=husksync plugin_archive=husksync
Loading…
Cancel
Save