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