Fix data sync when changing servers, consume keys when retrieved

feat/data-edit-commands
William 3 years ago
parent 38c261871a
commit d78dd42b72

@ -3,10 +3,8 @@ package net.william278.husksync.listener;
import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.BukkitHuskSync;
import net.william278.husksync.player.BukkitPlayer; import net.william278.husksync.player.BukkitPlayer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldSaveEvent;
@ -32,7 +30,7 @@ public class BukkitEventListener extends EventListener implements Listener {
BukkitPlayer.remove(event.getPlayer()); BukkitPlayer.remove(event.getPlayer());
} }
@EventHandler @EventHandler(ignoreCancelled = true)
public void onWorldSave(@NotNull WorldSaveEvent event) { public void onWorldSave(@NotNull WorldSaveEvent event) {
super.handleWorldSave(event.getWorld().getPlayers().stream().map(BukkitPlayer::adapt) super.handleWorldSave(event.getWorld().getPlayers().stream().map(BukkitPlayer::adapt)
.collect(Collectors.toList())); .collect(Collectors.toList()));

@ -70,9 +70,10 @@ public class BukkitPlayer extends OnlineUser {
@Override @Override
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, public CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
boolean setHealth, boolean setMaxHealth, final boolean setHealth, final boolean setMaxHealth,
boolean setHunger, boolean setExperience, final boolean setHunger, final boolean setExperience,
boolean setGameMode, boolean setFlying) { final boolean setGameMode, final boolean setFlying,
final boolean setSelectedItemSlot) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)) double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
.getBaseValue(); .getBaseValue();
@ -101,20 +102,26 @@ public class BukkitPlayer extends OnlineUser {
player.setSaturation(statusData.saturation); player.setSaturation(statusData.saturation);
player.setExhaustion(statusData.saturationExhaustion); player.setExhaustion(statusData.saturationExhaustion);
} }
if (setSelectedItemSlot) {
player.getInventory().setHeldItemSlot(statusData.selectedItemSlot);
}
if (setExperience) { if (setExperience) {
player.setTotalExperience(statusData.totalExperience); player.setTotalExperience(statusData.totalExperience);
player.setLevel(statusData.expLevel); player.setLevel(statusData.expLevel);
player.setExp(statusData.expProgress); player.setExp(statusData.expProgress);
} }
if (setGameMode) { if (setGameMode) {
player.setGameMode(GameMode.valueOf(statusData.gameMode)); Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () ->
player.setGameMode(GameMode.valueOf(statusData.gameMode)));
} }
if (setFlying) { if (setFlying) {
if (statusData.isFlying) { Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
player.setAllowFlight(true); if (statusData.isFlying) {
player.setFlying(true); player.setAllowFlight(true);
} player.setFlying(true);
player.setFlying(false); }
player.setFlying(false);
});
} }
}); });
} }
@ -189,7 +196,8 @@ public class BukkitPlayer extends OnlineUser {
@Override @Override
public CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData) { public CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
// Temporarily disable advancement announcing if needed // Temporarily disable advancement announcing if needed
boolean announceAdvancementUpdate = false; boolean announceAdvancementUpdate = false;
if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) { if (Boolean.TRUE.equals(player.getWorld().getGameRuleValue(GameRule.ANNOUNCE_ADVANCEMENTS))) {
@ -205,50 +213,53 @@ public class BukkitPlayer extends OnlineUser {
// Determines whether the experience might have changed warranting an update // Determines whether the experience might have changed warranting an update
final AtomicBoolean correctExperience = new AtomicBoolean(false); final AtomicBoolean correctExperience = new AtomicBoolean(false);
// Apply the advancements to the player // Run asynchronously as advancement setting is expensive
final Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator(); CompletableFuture.runAsync(() -> {
while (serverAdvancements.hasNext()) { // Apply the advancements to the player
// Iterate through all advancements final Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
final Advancement advancement = serverAdvancements.next(); while (serverAdvancements.hasNext()) {
final AdvancementProgress playerProgress = player.getAdvancementProgress(advancement); // Iterate through all advancements
final Advancement advancement = serverAdvancements.next();
advancementData.stream().filter(record -> record.key.equals(advancement.getKey().toString())).findFirst().ifPresentOrElse( final AdvancementProgress playerProgress = player.getAdvancementProgress(advancement);
// Award all criteria that the player does not have that they do on the cache
record -> { advancementData.stream().filter(record -> record.key.equals(advancement.getKey().toString())).findFirst().ifPresentOrElse(
record.completedCriteria.keySet().stream() // Award all criteria that the player does not have that they do on the cache
.filter(criterion -> !playerProgress.getAwardedCriteria().contains(criterion)) record -> {
.forEach(criterion -> { record.completedCriteria.keySet().stream()
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), .filter(criterion -> !playerProgress.getAwardedCriteria().contains(criterion))
() -> player.getAdvancementProgress(advancement).awardCriteria(criterion)); .forEach(criterion -> {
correctExperience.set(true); Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
}); () -> player.getAdvancementProgress(advancement).awardCriteria(criterion));
correctExperience.set(true);
// Revoke all criteria that the player does have but should not });
new ArrayList<>(playerProgress.getAwardedCriteria()).stream().filter(criterion -> !record.completedCriteria.containsKey(criterion))
.forEach(criterion -> Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), // Revoke all criteria that the player does have but should not
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion))); new ArrayList<>(playerProgress.getAwardedCriteria()).stream().filter(criterion -> !record.completedCriteria.containsKey(criterion))
.forEach(criterion -> Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
}, () -> player.getAdvancementProgress(advancement).revokeCriteria(criterion)));
// Revoke the criteria as the player shouldn't have any
() -> new ArrayList<>(playerProgress.getAwardedCriteria()).forEach(criterion -> },
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), // Revoke the criteria as the player shouldn't have any
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion)))); () -> new ArrayList<>(playerProgress.getAwardedCriteria()).forEach(criterion ->
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
// Update the player's experience in case the advancement changed that () -> player.getAdvancementProgress(advancement).revokeCriteria(criterion))));
if (correctExperience.get()) {
player.setLevel(experienceLevel); // Update the player's experience in case the advancement changed that
player.setExp(expProgress); if (correctExperience.get()) {
correctExperience.set(false); player.setLevel(experienceLevel);
player.setExp(expProgress);
correctExperience.set(false);
}
} }
}
// Re-enable announcing advancements (back on main thread again) // Re-enable announcing advancements (back on main thread again)
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> { Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
if (finalAnnounceAdvancementUpdate) { if (finalAnnounceAdvancementUpdate) {
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true); player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true);
} }
});
}); });
}); }));
} }
@Override @Override
@ -397,6 +408,16 @@ public class BukkitPlayer extends OnlineUser {
}); });
} }
@Override
public boolean isDead() {
return player.isDead() || player.getHealth() <= 0;
}
@Override
public boolean isOffline() {
return player == null;
}
@Override @Override
public boolean hasPermission(@NotNull String node) { public boolean hasPermission(@NotNull String node) {
return player.hasPermission(node); return player.hasPermission(node);

@ -35,7 +35,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
"[•](white) [Currently running:](#00fb9a) [Version " + updateChecker.getCurrentVersion() + "](gray)" + "[•](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) [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 { } else {
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running version " + latestVersion)); player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running version " + latestVersion + "](#00fb9a)"));
} }
}); });
} }
@ -46,17 +46,17 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
return; return;
} }
plugin.reload(); plugin.reload();
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) &#00fb9a&| Reloaded config & message files.")); player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Reloaded config & message files.]((#00fb9a)"));
} }
default -> default ->
plugin.getLocales().getLocale("error_invalid_syntax", "/husksync <update|info|reload>").ifPresent(player::sendMessage); plugin.getLocales().getLocale("error_invalid_syntax", "/husksync <update/info/reload>").ifPresent(player::sendMessage);
} }
} }
@Override @Override
public void onConsoleExecute(@NotNull String[] args) { public void onConsoleExecute(@NotNull String[] args) {
if (args.length < 1) { 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; return;
} }
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
@ -71,7 +71,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
//todo - MPDB migrator //todo - MPDB migrator
} }
default -> 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>");
} }
} }

@ -15,11 +15,11 @@ import java.util.regex.Pattern;
public class Locales { public class Locales {
public static final String PLUGIN_INFORMATION = """ public static final String PLUGIN_INFORMATION = """
[HuskSync](#00fb9a bold) [| Version %version%(#00fb9a) [HuskSync](#00fb9a bold) [| Version %version%](#00fb9a)
[A modern, cross-server player data synchronization system](gray) [A modern, cross-server player data synchronization system](gray)
[ Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net) [ Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)
[ Contributors:](white) [HarvelsX](gray show_text=&7Code) [ Contributors:](white) [HarvelsX](gray show_text=&7Code)
[ Translators:](white) [Namiu/](gray show_text=&7Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es), [Ceddix](gray show_text=&7German, de-de), [](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua) [ Translators:](white) [Namiu](gray show_text=&7\\(\\) - Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es), [Ceddix](gray show_text=&7German, de-de), [](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua)
[ Plugin Info:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/) [ Plugin Info:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/)
[ Report Issues:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues) [ Report Issues:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)
[ Support Discord:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)"""; [ Support Discord:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)""";

@ -271,19 +271,19 @@ public class MySqlDatabase extends Database {
protected CompletableFuture<Void> pruneUserDataRecords(@NotNull User user) { protected CompletableFuture<Void> pruneUserDataRecords(@NotNull User user) {
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(data -> { return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(data -> {
if (data.size() > maxUserDataRecords) { if (data.size() > maxUserDataRecords) {
Collections.reverse(data); try (Connection connection = getConnection()) {
data.subList(0, data.size() - maxUserDataRecords).forEach(dataToDelete -> { try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
try (Connection connection = getConnection()) { DELETE FROM `%data_table%`
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" WHERE `player_uuid`=?
DELETE FROM `%data_table%` ORDER BY `timestamp` ASC
WHERE `version_uuid`=?"""))) { LIMIT %entry_count%;""".replace("%entry_count%",
statement.setString(1, dataToDelete.versionUUID().toString()); Integer.toString(data.size() - maxUserDataRecords))))) {
statement.executeUpdate(); statement.setString(1, user.uuid.toString());
} statement.executeUpdate();
} catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
} }
}); } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
}
} }
})); }));
} }
@ -306,7 +306,7 @@ public class MySqlDatabase extends Database {
} catch (SQLException | IOException e) { } catch (SQLException | IOException e) {
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e); getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
} }
})/*.thenRunAsync(() -> pruneUserDataRecords(user).join())*/; }).thenRun(() -> pruneUserDataRecords(user).join());
} }
@Override @Override

@ -2,6 +2,7 @@ package net.william278.husksync.listener;
import net.william278.husksync.HuskSync; import net.william278.husksync.HuskSync;
import net.william278.husksync.player.OnlineUser; import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.player.User;
import net.william278.husksync.redis.RedisManager; import net.william278.husksync.redis.RedisManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -9,8 +10,12 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class EventListener { public abstract class EventListener {
/** /**
* The plugin instance * The plugin instance
@ -34,30 +39,58 @@ public class EventListener {
} }
public final void handlePlayerJoin(@NotNull OnlineUser user) { public final void handlePlayerJoin(@NotNull OnlineUser user) {
if (user.isDead()) {
return;
}
usersAwaitingSync.add(user.uuid); usersAwaitingSync.add(user.uuid);
huskSync.getRedisManager().getUserData(user, RedisManager.RedisKeyType.SERVER_CHANGE).thenAccept( CompletableFuture.runAsync(() -> huskSync.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
cachedUserData -> cachedUserData.ifPresentOrElse( if (!changingServers) {
userData -> user.setData(userData, huskSync.getSettings()).join(), // Fetch from the database if the user isn't changing servers
() -> huskSync.getDatabase().getCurrentUserData(user).thenAccept( setUserFromDatabase(user).thenRun(() -> handleSynchronisationCompletion(user));
databaseUserData -> databaseUserData.ifPresent( } else {
data -> user.setData(data.userData(), huskSync.getSettings()).join())).join())).thenRunAsync( final int TIME_OUT_MILLISECONDS = 3200;
() -> { CompletableFuture.runAsync(() -> {
huskSync.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar); final AtomicInteger currentMilliseconds = new AtomicInteger(0);
usersAwaitingSync.remove(user.uuid); final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
huskSync.getDatabase().ensureUser(user).join();
// Set the user as soon as the source server has set the data to redis
executor.scheduleAtFixedRate(() -> {
if (disabling || currentMilliseconds.get() > TIME_OUT_MILLISECONDS) {
executor.shutdown();
setUserFromDatabase(user).thenRun(() -> handleSynchronisationCompletion(user));
return;
}
huskSync.getRedisManager().getUserData(user).thenAccept(redisUserData ->
redisUserData.ifPresent(redisData -> {
user.setData(redisData, huskSync.getSettings()).join();
executor.shutdown();
})).join();
currentMilliseconds.addAndGet(200);
}, 0, 200L, TimeUnit.MILLISECONDS);
}); });
}
}));
}
private CompletableFuture<Void> setUserFromDatabase(@NotNull OnlineUser user) {
return huskSync.getDatabase().getCurrentUserData(user)
.thenAccept(databaseUserData -> databaseUserData.ifPresent(databaseData -> user
.setData(databaseData.userData(), huskSync.getSettings()).join()));
}
private void handleSynchronisationCompletion(@NotNull OnlineUser user) {
huskSync.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar);
usersAwaitingSync.remove(user.uuid);
huskSync.getDatabase().ensureUser(user).join();
} }
public final void handlePlayerQuit(@NotNull OnlineUser user) { public final void handlePlayerQuit(@NotNull OnlineUser user) {
if (disabling) { if (disabling) {
return; return;
} }
user.getUserData().thenAccept(userData -> { huskSync.getRedisManager().setUserServerSwitch(user).thenRun(() -> user.getUserData().thenAccept(
System.out.println(userData.userData().toJson()); userData -> huskSync.getRedisManager().setUserData(user, userData.userData()).thenRun(
huskSync.getRedisManager() () -> huskSync.getDatabase().setUserData(user, userData).join())));
.setUserData(user, userData.userData(), RedisManager.RedisKeyType.SERVER_CHANGE).thenRun(
() -> huskSync.getDatabase().setUserData(user, userData).join());
});
} }
public final void handleWorldSave(@NotNull List<OnlineUser> usersInWorld) { public final void handleWorldSave(@NotNull List<OnlineUser> usersInWorld) {

@ -39,7 +39,8 @@ public abstract class OnlineUser extends User {
public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData, public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
final boolean setHealth, final boolean setMaxHealth, final boolean setHealth, final boolean setMaxHealth,
final boolean setHunger, final boolean setExperience, final boolean setHunger, final boolean setExperience,
final boolean setGameMode, boolean setFlying); final boolean setGameMode, final boolean setFlying,
final boolean setSelectedItemSlot);
/** /**
* Get the player's inventory {@link InventoryData} contents * Get the player's inventory {@link InventoryData} contents
@ -147,6 +148,20 @@ public abstract class OnlineUser extends User {
*/ */
public abstract CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData); public abstract CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData);
/**
* Indicates if the player is currently dead
*
* @return {@code true} if the player is dead (health <= 0); {@code false} otherwise
*/
public abstract boolean isDead();
/**
* Indicates if the player has gone offline
*
* @return {@code true} if the player has left the server; {@code false} otherwise
*/
public abstract boolean isOffline();
/** /**
* Set {@link UserData} to a player * Set {@link UserData} to a player
* *
@ -156,32 +171,45 @@ public abstract class OnlineUser extends User {
*/ */
public final CompletableFuture<Void> setData(@NotNull UserData data, @NotNull Settings settings) { public final CompletableFuture<Void> setData(@NotNull UserData data, @NotNull Settings settings) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) { try {
setInventory(data.getInventoryData()).join(); // Don't set offline players
} if (isOffline()) {
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) { return;
setEnderChest(data.getEnderChestData()).join(); }
} // Don't set dead players
setStatus(data.getStatusData(), settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HEALTH), if (isDead()) {
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_MAX_HEALTH), return;
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HUNGER), }
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_EXPERIENCE), if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_GAME_MODE), setInventory(data.getInventoryData()).join();
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)).join(); }
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) { if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
setPotionEffects(data.getPotionEffectData()).join(); setEnderChest(data.getEnderChestData()).join();
} }
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) { setStatus(data.getStatusData(), settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HEALTH),
setAdvancements(data.getAdvancementData()).join(); settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_MAX_HEALTH),
} settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HUNGER),
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) { settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_EXPERIENCE),
setStatistics(data.getStatisticData()).join(); settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_GAME_MODE),
} settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION),
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) { settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)).join();
setPersistentDataContainer(data.getPersistentDataContainerData()).join(); if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
} setPotionEffects(data.getPotionEffectData()).join();
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) { }
setLocation(data.getLocationData()).join(); if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
setAdvancements(data.getAdvancementData()).join();
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
setStatistics(data.getStatisticData()).join();
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
setPersistentDataContainer(data.getPersistentDataContainerData()).join();
}
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
setLocation(data.getLocationData()).join();
}
} catch (Exception e) {
e.printStackTrace();
} }
}); });
} }

@ -12,6 +12,7 @@ import redis.clients.jedis.exceptions.JedisException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -76,14 +77,14 @@ public class RedisManager {
* @param redisKeyType the type of key to set the data with. This determines the time to live for the data. * @param redisKeyType the type of key to set the data with. This determines the time to live for the data.
* @return a future returning void when complete * @return a future returning void when complete
*/ */
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData, public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData) {
@NotNull RedisKeyType redisKeyType) {
try { try {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) { try (Jedis jedis = jedisPool.getResource()) {
// Set the user's data as a compressed byte array of the json using Snappy // Set the user's data as a compressed byte array of the json using Snappy
jedis.setex(getKey(redisKeyType, user.uuid), redisKeyType.timeToLive, jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid), RedisKeyType.DATA_UPDATE.timeToLive,
Snappy.compress(userData.toJson().getBytes(StandardCharsets.UTF_8))); Snappy.compress(userData.toJson().getBytes(StandardCharsets.UTF_8)));
System.out.println("Set key at " + new Date().getTime());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -94,21 +95,34 @@ public class RedisManager {
return null; return null;
} }
public CompletableFuture<Void> setUserServerSwitch(@NotNull User user) {
return CompletableFuture.runAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
}
});
}
/** /**
* Fetch a user's data from the Redis server * Fetch a user's data from the Redis server and consume the key if found
* *
* @param user The user to fetch data for * @param user The user to fetch data for
* @param redisKeyType The type of key to fetch * @param redisKeyType The type of key to fetch
* @return The user's data, if it's present on the database. Otherwise, an empty optional. * @return The user's data, if it's present on the database. Otherwise, an empty optional.
*/ */
public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user, public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
@NotNull RedisKeyType redisKeyType) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) { try (Jedis jedis = jedisPool.getResource()) {
final byte[] compressedJson = jedis.get(getKey(redisKeyType, user.uuid)); 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) { if (compressedJson == null) {
return Optional.empty(); return Optional.empty();
} }
// Consume the key (delete from redis)
jedis.del(key);
// Use Snappy to decompress the json // Use Snappy to decompress the json
return Optional.of(UserData.fromJson(new String(Snappy.uncompress(compressedJson), return Optional.of(UserData.fromJson(new String(Snappy.uncompress(compressedJson),
StandardCharsets.UTF_8))); StandardCharsets.UTF_8)));
@ -118,6 +132,21 @@ public class RedisManager {
}); });
} }
public CompletableFuture<Boolean> getUserServerSwitch(@NotNull User user) {
return CompletableFuture.supplyAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) {
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
final byte[] compressedJson = jedis.get(key);
if (compressedJson == null) {
return false;
}
// Consume the key (delete from redis)
jedis.del(key);
return true;
}
});
}
public void close() { public void close() {
if (jedisPool != null) { if (jedisPool != null) {
if (!jedisPool.isClosed()) { if (!jedisPool.isClosed()) {
@ -132,7 +161,8 @@ public class RedisManager {
public enum RedisKeyType { public enum RedisKeyType {
CACHE(60 * 60 * 24), CACHE(60 * 60 * 24),
SERVER_CHANGE(2); DATA_UPDATE(10),
SERVER_SWITCH(10);
public final int timeToLive; public final int timeToLive;

Loading…
Cancel
Save