mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-24 00:59:18 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131a364f53 | ||
|
|
19636d9447 | ||
|
|
f803a0b57b | ||
|
|
28afffe95e | ||
|
|
c7e100a78a | ||
|
|
12e223618d | ||
|
|
f6773f4e68 | ||
|
|
b9434a56e8 |
@@ -15,7 +15,7 @@ dependencies {
|
||||
compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||
compileOnly 'commons-io:commons-io:2.15.1'
|
||||
compileOnly 'org.json:json:20231013'
|
||||
compileOnly 'org.json:json:20240205'
|
||||
compileOnly 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||
compileOnly 'com.github.Exlll.ConfigLib:configlib-yaml:v4.4.0'
|
||||
compileOnly 'com.zaxxer:HikariCP:5.1.0'
|
||||
|
||||
@@ -60,7 +60,6 @@ import net.william278.husksync.util.BukkitMapPersister;
|
||||
import net.william278.husksync.util.BukkitTask;
|
||||
import net.william278.husksync.util.LegacyConverter;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.map.MapView;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
@@ -229,7 +228,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
@Override
|
||||
@NotNull
|
||||
public Set<OnlineUser> getOnlineUsers() {
|
||||
return Bukkit.getOnlinePlayers().stream()
|
||||
return getServer().getOnlinePlayers().stream()
|
||||
.map(player -> BukkitUser.adapt(player, this))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
@@ -237,7 +236,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
@Override
|
||||
@NotNull
|
||||
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||
final Player player = Bukkit.getPlayer(uuid);
|
||||
final Player player = getServer().getPlayer(uuid);
|
||||
if (player == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -253,12 +252,10 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<Identifier, Data> getPlayerCustomDataStore(@NotNull OnlineUser user) {
|
||||
if (playerCustomDataStore.containsKey(user.getUuid())) {
|
||||
return playerCustomDataStore.get(user.getUuid());
|
||||
}
|
||||
final Map<Identifier, Data> data = Maps.newHashMap();
|
||||
playerCustomDataStore.put(user.getUuid(), data);
|
||||
return data;
|
||||
return playerCustomDataStore.compute(
|
||||
user.getUuid(),
|
||||
(uuid, data) -> data == null ? Maps.newHashMap() : data
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,7 +266,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
|
||||
@Override
|
||||
public boolean isDependencyLoaded(@NotNull String name) {
|
||||
return Bukkit.getPluginManager().getPlugin(name) != null;
|
||||
return getServer().getPluginManager().getPlugin(name) != null;
|
||||
}
|
||||
|
||||
// Register bStats metrics
|
||||
@@ -303,7 +300,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
@NotNull
|
||||
@Override
|
||||
public Version getMinecraftVersion() {
|
||||
return Version.fromString(Bukkit.getBukkitVersion());
|
||||
return Version.fromString(getServer().getBukkitVersion());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -347,7 +344,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public HuskSync getPlugin() {
|
||||
public BukkitHuskSync getPlugin() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BukkitUserDataHolder extends UserDataHolder {
|
||||
@@ -140,9 +139,6 @@ public interface BukkitUserDataHolder extends UserDataHolder {
|
||||
@NotNull
|
||||
Player getBukkitPlayer();
|
||||
|
||||
@NotNull
|
||||
Map<Identifier, Data> getCustomDataStore();
|
||||
|
||||
@NotNull
|
||||
default BukkitMapPersister getMapPersister() {
|
||||
return (BukkitHuskSync) getPlugin();
|
||||
|
||||
@@ -25,7 +25,7 @@ import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
|
||||
import de.tr7zw.changeme.nbtapi.iface.ReadableNBT;
|
||||
import net.querz.nbt.io.NBTUtil;
|
||||
import net.querz.nbt.tag.CompoundTag;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.mapdataapi.MapBanner;
|
||||
import net.william278.mapdataapi.MapData;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -85,7 +85,7 @@ public interface BukkitMapPersister {
|
||||
|
||||
// Perform an operation on each map in an array of ItemStacks
|
||||
@NotNull
|
||||
private ItemStack[] forEachMap(@NotNull ItemStack[] items, @NotNull Function<ItemStack, ItemStack> function) {
|
||||
private ItemStack[] forEachMap(ItemStack[] items, @NotNull Function<ItemStack, ItemStack> function) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ItemStack item = items[i];
|
||||
if (item == null) {
|
||||
@@ -148,7 +148,7 @@ public interface BukkitMapPersister {
|
||||
// Search for an existing map view
|
||||
Optional<String> world = Optional.empty();
|
||||
for (String worldUid : mapIds.getKeys()) {
|
||||
world = Bukkit.getWorlds().stream()
|
||||
world = getPlugin().getServer().getWorlds().stream()
|
||||
.map(w -> w.getUID().toString()).filter(u -> u.equals(worldUid))
|
||||
.findFirst();
|
||||
if (world.isPresent()) {
|
||||
@@ -441,6 +441,6 @@ public interface BukkitMapPersister {
|
||||
|
||||
@ApiStatus.Internal
|
||||
@NotNull
|
||||
HuskSync getPlugin();
|
||||
BukkitHuskSync getPlugin();
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ dependencies {
|
||||
api 'commons-io:commons-io:2.15.1'
|
||||
api 'org.apache.commons:commons-text:1.11.0'
|
||||
api 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||
api 'org.json:json:20231013'
|
||||
api 'org.json:json:20240205'
|
||||
api 'com.google.code.gson:gson:2.10.1'
|
||||
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
||||
api 'com.github.Exlll.ConfigLib:configlib-yaml:v4.4.0'
|
||||
|
||||
@@ -31,11 +31,13 @@ import net.william278.husksync.user.OnlineUser;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* The common implementation of the HuskSync API, containing cross-platform API calls.
|
||||
@@ -262,13 +264,32 @@ public class HuskSyncAPI {
|
||||
*
|
||||
* @param user The user to save the data for
|
||||
* @param snapshot The snapshot to save
|
||||
* @param callback A callback to run after the data has been saved (if the DataSaveEvent was not cancelled)
|
||||
* @apiNote This will fire the {@link net.william278.husksync.event.DataSaveEvent} event, unless
|
||||
* the save cause is {@link DataSnapshot.SaveCause#SERVER_SHUTDOWN}
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public void addSnapshot(@NotNull User user, @NotNull DataSnapshot snapshot,
|
||||
@Nullable BiConsumer<User, DataSnapshot.Packed> callback) {
|
||||
plugin.runAsync(() -> plugin.getDataSyncer().saveData(
|
||||
user,
|
||||
snapshot instanceof DataSnapshot.Unpacked unpacked
|
||||
? unpacked.pack(plugin) : (DataSnapshot.Packed) snapshot,
|
||||
callback
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data snapshot to the database
|
||||
*
|
||||
* @param user The user to save the data for
|
||||
* @param snapshot The snapshot to save
|
||||
* @apiNote This will fire the {@link net.william278.husksync.event.DataSaveEvent} event, unless
|
||||
* * the save cause is {@link DataSnapshot.SaveCause#SERVER_SHUTDOWN}
|
||||
* @since 3.0
|
||||
*/
|
||||
public void addSnapshot(@NotNull User user, @NotNull DataSnapshot snapshot) {
|
||||
plugin.runAsync(() -> plugin.getDatabase().addSnapshot(
|
||||
user, snapshot instanceof DataSnapshot.Unpacked unpacked
|
||||
? unpacked.pack(plugin) : (DataSnapshot.Packed) snapshot
|
||||
));
|
||||
this.addSnapshot(user, snapshot, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,8 +72,8 @@ public class EnderChestCommand extends ItemsCommand {
|
||||
|
||||
// Creates a new snapshot with the updated enderChest
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User user) {
|
||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(user);
|
||||
private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User holder) {
|
||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
||||
if (latestData.isEmpty()) {
|
||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||
.ifPresent(viewer::sendMessage);
|
||||
@@ -90,10 +90,12 @@ public class EnderChestCommand extends ItemsCommand {
|
||||
);
|
||||
});
|
||||
|
||||
// Save data
|
||||
final RedisManager redis = plugin.getRedisManager();
|
||||
plugin.getDatabase().addSnapshot(user, snapshot);
|
||||
redis.sendUserDataUpdate(user, snapshot);
|
||||
redis.getUserData(user).ifPresent(data -> redis.setUserData(user, snapshot, RedisKeyType.TTL_1_YEAR));
|
||||
plugin.getDataSyncer().saveData(holder, snapshot, (user, data) -> {
|
||||
redis.getUserData(user).ifPresent(d -> redis.setUserData(user, snapshot, RedisKeyType.TTL_1_YEAR));
|
||||
redis.sendUserDataUpdate(user, data);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public class InventoryCommand extends ItemsCommand {
|
||||
|
||||
// Creates a new snapshot with the updated inventory
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User user) {
|
||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(user);
|
||||
private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User holder) {
|
||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
||||
if (latestData.isEmpty()) {
|
||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||
.ifPresent(viewer::sendMessage);
|
||||
@@ -90,10 +90,12 @@ public class InventoryCommand extends ItemsCommand {
|
||||
);
|
||||
});
|
||||
|
||||
// Save data
|
||||
final RedisManager redis = plugin.getRedisManager();
|
||||
plugin.getDatabase().addSnapshot(user, snapshot);
|
||||
redis.sendUserDataUpdate(user, snapshot);
|
||||
redis.getUserData(user).ifPresent(data -> redis.setUserData(user, snapshot, RedisKeyType.TTL_1_YEAR));
|
||||
plugin.getDataSyncer().saveData(holder, snapshot, (user, data) -> {
|
||||
redis.getUserData(user).ifPresent(d -> redis.setUserData(user, snapshot, RedisKeyType.TTL_1_YEAR));
|
||||
redis.sendUserDataUpdate(user, data);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ package net.william278.husksync.command;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.redis.RedisKeyType;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.user.CommandUser;
|
||||
import net.william278.husksync.user.User;
|
||||
import net.william278.husksync.util.DataDumper;
|
||||
@@ -110,13 +112,14 @@ public class UserDataCommand extends Command implements TabProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete user data by specified UUID
|
||||
// Delete user data by specified UUID and clear their data cache
|
||||
final UUID version = optionalUuid.get();
|
||||
if (!plugin.getDatabase().deleteSnapshot(user, version)) {
|
||||
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||
.ifPresent(executor::sendMessage);
|
||||
return;
|
||||
}
|
||||
plugin.getRedisManager().clearUserData(user);
|
||||
|
||||
plugin.getLocales().getLocale("data_deleted",
|
||||
version.toString().split("-")[0],
|
||||
@@ -152,11 +155,14 @@ public class UserDataCommand extends Command implements TabProvider {
|
||||
);
|
||||
}));
|
||||
|
||||
// Set the user's data and send a message
|
||||
plugin.getDatabase().addSnapshot(user, data);
|
||||
plugin.getRedisManager().sendUserDataUpdate(user, data);
|
||||
plugin.getLocales().getLocale("data_restored", user.getUsername(), user.getUuid().toString(),
|
||||
data.getShortId(), data.getId().toString()).ifPresent(executor::sendMessage);
|
||||
// Save data
|
||||
final RedisManager redis = plugin.getRedisManager();
|
||||
plugin.getDataSyncer().saveData(user, data, (u, s) -> {
|
||||
redis.getUserData(u).ifPresent(d -> redis.setUserData(u, s, RedisKeyType.TTL_1_YEAR));
|
||||
redis.sendUserDataUpdate(u, s);
|
||||
plugin.getLocales().getLocale("data_restored", u.getUsername(), u.getUuid().toString(),
|
||||
s.getShortId(), s.getId().toString()).ifPresent(executor::sendMessage);
|
||||
});
|
||||
}
|
||||
|
||||
case "pin" -> {
|
||||
|
||||
@@ -23,8 +23,6 @@ import lombok.Getter;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.data.DataSnapshot.SaveCause;
|
||||
import net.william278.husksync.data.UserDataHolder;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -33,6 +31,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* An abstract representation of the plugin database, storing player data.
|
||||
@@ -156,42 +155,23 @@ public abstract class Database {
|
||||
@Blocking
|
||||
public abstract boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid);
|
||||
|
||||
/**
|
||||
* Save user data to the database
|
||||
* </p>
|
||||
* This will remove the oldest data for the user if the amount of data exceeds the limit as configured
|
||||
*
|
||||
* @param user The user to add data for
|
||||
* @param snapshot The {@link DataSnapshot} to set.
|
||||
* The implementation should version it with a random UUID and the current timestamp during insertion.
|
||||
* @see UserDataHolder#createSnapshot(SaveCause)
|
||||
*/
|
||||
@Blocking
|
||||
public void addSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed snapshot) {
|
||||
if (snapshot.getSaveCause() != SaveCause.SERVER_SHUTDOWN) {
|
||||
plugin.fireEvent(
|
||||
plugin.getDataSaveEvent(user, snapshot),
|
||||
(event) -> this.addAndRotateSnapshot(user, snapshot)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addAndRotateSnapshot(user, snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>Internal</b> - Save user data to the database. This will:
|
||||
* Save user data to the database, doing the following (in order):
|
||||
* <ol>
|
||||
* <li>Delete their most recent snapshot, if it was created before the backup frequency time</li>
|
||||
* <li>Create the snapshot</li>
|
||||
* <li>Rotate snapshot backups</li>
|
||||
* </ol>
|
||||
* This is an expensive blocking method and should be run off the main thread.
|
||||
*
|
||||
* @param user The user to add data for
|
||||
* @param snapshot The {@link DataSnapshot} to set.
|
||||
* @apiNote Prefer {@link net.william278.husksync.sync.DataSyncer#saveData(User, DataSnapshot.Packed, BiConsumer)}.
|
||||
* </p>This method will not fire the {@link net.william278.husksync.event.DataSaveEvent}
|
||||
*/
|
||||
@Blocking
|
||||
private void addAndRotateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed snapshot) {
|
||||
public void addSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed snapshot) {
|
||||
final int backupFrequency = plugin.getSettings().getSynchronization().getSnapshotBackupFrequency();
|
||||
if (!snapshot.isPinned() && backupFrequency > 0) {
|
||||
this.rotateLatestSnapshot(user, snapshot.getTimestamp().minusHours(backupFrequency));
|
||||
|
||||
@@ -81,8 +81,8 @@ public abstract class EventListener {
|
||||
}
|
||||
usersInWorld.stream()
|
||||
.filter(user -> !plugin.isLocked(user.getUuid()) && !user.isNpc())
|
||||
.forEach(user -> plugin.getDatabase().addSnapshot(
|
||||
user, user.createSnapshot(DataSnapshot.SaveCause.WORLD_SAVE)
|
||||
.forEach(user -> plugin.getDataSyncer().saveData(
|
||||
user, user.createSnapshot(DataSnapshot.SaveCause.WORLD_SAVE), null
|
||||
));
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public abstract class EventListener {
|
||||
|
||||
final DataSnapshot.Packed snapshot = user.createSnapshot(DataSnapshot.SaveCause.DEATH);
|
||||
snapshot.edit(plugin, (data -> data.getInventory().ifPresent(inventory -> inventory.setContents(items))));
|
||||
plugin.getDatabase().addSnapshot(user, snapshot);
|
||||
plugin.getDataSyncer().saveData(user, snapshot, (u, d) -> plugin.getRedisManager().sendUserDataUpdate(u, d));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +123,9 @@ public abstract class EventListener {
|
||||
.filter(user -> !plugin.isLocked(user.getUuid()) && !user.isNpc())
|
||||
.forEach(user -> {
|
||||
plugin.lockPlayer(user.getUuid());
|
||||
plugin.getDatabase().addSnapshot(user, user.createSnapshot(DataSnapshot.SaveCause.SERVER_SHUTDOWN));
|
||||
plugin.getDataSyncer().saveData(
|
||||
user, user.createSnapshot(DataSnapshot.SaveCause.SERVER_SHUTDOWN), null
|
||||
);
|
||||
});
|
||||
|
||||
// Close outstanding connections
|
||||
|
||||
@@ -255,6 +255,18 @@ public class RedisManager extends JedisPubSub {
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void clearUserData(@NotNull User user) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.del(
|
||||
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId)
|
||||
);
|
||||
plugin.debug(String.format("[%s] Cleared %s on Redis", user.getUsername(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred clearing user data on Redis", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
|
||||
@@ -22,14 +22,20 @@ package net.william278.husksync.sync;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.api.HuskSyncAPI;
|
||||
import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.user.OnlineUser;
|
||||
import net.william278.husksync.user.User;
|
||||
import net.william278.husksync.util.Task;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -87,6 +93,42 @@ public abstract class DataSyncer {
|
||||
*/
|
||||
public abstract void saveUserData(@NotNull OnlineUser user);
|
||||
|
||||
/**
|
||||
* Save a {@link DataSnapshot.Packed user's data snapshot} to the database,
|
||||
* first firing the {@link net.william278.husksync.event.DataSaveEvent}. This will not update data on Redis.
|
||||
*
|
||||
* @param user the user to save the data for
|
||||
* @param data the data to save
|
||||
* @param after a consumer to run after data has been saved. Will be run async (off the main thread).
|
||||
* @apiNote Data will not be saved if the {@link net.william278.husksync.event.DataSaveEvent} is cancelled.
|
||||
* Note that this method can also edit the data before saving it.
|
||||
* @implNote Note that the {@link net.william278.husksync.event.DataSaveEvent} will <b>not</b> be fired if the
|
||||
* save cause is {@link DataSnapshot.SaveCause#SERVER_SHUTDOWN}.
|
||||
* @since 3.3.2
|
||||
*/
|
||||
@Blocking
|
||||
public void saveData(@NotNull User user, @NotNull DataSnapshot.Packed data,
|
||||
@Nullable BiConsumer<User, DataSnapshot.Packed> after) {
|
||||
if (data.getSaveCause() == DataSnapshot.SaveCause.SERVER_SHUTDOWN) {
|
||||
addSnapshotToDatabase(user, data, after);
|
||||
return;
|
||||
}
|
||||
plugin.fireEvent(
|
||||
plugin.getDataSaveEvent(user, data),
|
||||
(event) -> addSnapshotToDatabase(user, data, after)
|
||||
);
|
||||
}
|
||||
|
||||
// Adds a snapshot to the database and runs the after consumer
|
||||
@Blocking
|
||||
private void addSnapshotToDatabase(@NotNull User user, @NotNull DataSnapshot.Packed data,
|
||||
@Nullable BiConsumer<User, DataSnapshot.Packed> after) {
|
||||
getDatabase().addSnapshot(user, data);
|
||||
if (after != null) {
|
||||
after.accept(user, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates the max attempts the system should listen for user data for based on the latency value
|
||||
private long getMaxListenAttempts() {
|
||||
return BASE_LISTEN_ATTEMPTS + (
|
||||
@@ -98,7 +140,7 @@ public abstract class DataSyncer {
|
||||
// Set a user's data from the database, or set them as a new user
|
||||
@ApiStatus.Internal
|
||||
protected void setUserFromDatabase(@NotNull OnlineUser user) {
|
||||
plugin.getDatabase().getLatestSnapshot(user).ifPresentOrElse(
|
||||
getDatabase().getLatestSnapshot(user).ifPresentOrElse(
|
||||
snapshot -> user.applySnapshot(snapshot, DataSnapshot.UpdateCause.SYNCHRONIZED),
|
||||
() -> user.completeSync(true, DataSnapshot.UpdateCause.NEW_USER, plugin)
|
||||
);
|
||||
@@ -139,6 +181,16 @@ public abstract class DataSyncer {
|
||||
task.get().run();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected RedisManager getRedis() {
|
||||
return plugin.getRedisManager();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Database getDatabase() {
|
||||
return plugin.getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the different available default modes of {@link DataSyncer}
|
||||
*
|
||||
|
||||
@@ -39,7 +39,7 @@ public class DelayDataSyncer extends DataSyncer {
|
||||
plugin.runAsyncDelayed(
|
||||
() -> {
|
||||
// Fetch from the database if the user isn't changing servers
|
||||
if (!plugin.getRedisManager().getUserServerSwitch(user)) {
|
||||
if (!getRedis().getUserServerSwitch(user)) {
|
||||
this.setUserFromDatabase(user);
|
||||
return;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class DelayDataSyncer extends DataSyncer {
|
||||
// Listen for the data to be updated
|
||||
this.listenForRedisData(
|
||||
user,
|
||||
() -> plugin.getRedisManager().getUserData(user).map(data -> {
|
||||
() -> getRedis().getUserData(user).map(data -> {
|
||||
user.applySnapshot(data, DataSnapshot.UpdateCause.SYNCHRONIZED);
|
||||
return true;
|
||||
}).orElse(false)
|
||||
@@ -58,12 +58,13 @@ public class DelayDataSyncer extends DataSyncer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUserData(@NotNull OnlineUser user) {
|
||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
||||
plugin.runAsync(() -> {
|
||||
plugin.getRedisManager().setUserServerSwitch(user);
|
||||
final DataSnapshot.Packed data = user.createSnapshot(DataSnapshot.SaveCause.DISCONNECT);
|
||||
plugin.getRedisManager().setUserData(user, data, RedisKeyType.TTL_10_SECONDS);
|
||||
plugin.getDatabase().addSnapshot(user, data);
|
||||
getRedis().setUserServerSwitch(onlineUser);
|
||||
saveData(
|
||||
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
||||
(user, data) -> getRedis().setUserData(user, data, RedisKeyType.TTL_10_SECONDS)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,21 +33,21 @@ public class LockstepDataSyncer extends DataSyncer {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
plugin.getRedisManager().clearUsersCheckedOutOnServer();
|
||||
getRedis().clearUsersCheckedOutOnServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
plugin.getRedisManager().clearUsersCheckedOutOnServer();
|
||||
getRedis().clearUsersCheckedOutOnServer();
|
||||
}
|
||||
|
||||
// Consume their data when they are checked in
|
||||
@Override
|
||||
public void setUserData(@NotNull OnlineUser user) {
|
||||
this.listenForRedisData(user, () -> {
|
||||
if (plugin.getRedisManager().getUserCheckedOut(user).isEmpty()) {
|
||||
plugin.getRedisManager().setUserCheckedOut(user, true);
|
||||
plugin.getRedisManager().getUserData(user).ifPresentOrElse(
|
||||
if (getRedis().getUserCheckedOut(user).isEmpty()) {
|
||||
getRedis().setUserCheckedOut(user, true);
|
||||
getRedis().getUserData(user).ifPresentOrElse(
|
||||
data -> user.applySnapshot(data, DataSnapshot.UpdateCause.SYNCHRONIZED),
|
||||
() -> this.setUserFromDatabase(user)
|
||||
);
|
||||
@@ -58,12 +58,16 @@ public class LockstepDataSyncer extends DataSyncer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUserData(@NotNull OnlineUser user) {
|
||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
||||
plugin.runAsync(() -> {
|
||||
final DataSnapshot.Packed data = user.createSnapshot(DataSnapshot.SaveCause.DISCONNECT);
|
||||
plugin.getRedisManager().setUserData(user, data, RedisKeyType.TTL_1_YEAR);
|
||||
plugin.getRedisManager().setUserCheckedOut(user, false);
|
||||
plugin.getDatabase().addSnapshot(user, data);
|
||||
getRedis().setUserServerSwitch(onlineUser);
|
||||
saveData(
|
||||
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
||||
(user, data) -> {
|
||||
getRedis().setUserData(user, data, RedisKeyType.TTL_1_YEAR);
|
||||
getRedis().setUserCheckedOut(user, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.daemon=true
|
||||
javaVersion=17
|
||||
|
||||
plugin_version=3.3.1
|
||||
plugin_version=3.3.2
|
||||
plugin_archive=husksync
|
||||
plugin_description=A modern, cross-server player data synchronization system
|
||||
|
||||
|
||||
Reference in New Issue
Block a user