mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-26 18:19:10 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67ef4888da | ||
|
|
a5d3015c6e | ||
|
|
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,10 +23,16 @@ import com.google.common.collect.Maps;
|
|||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import de.themoep.minedown.adventure.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
import net.william278.husksync.adapter.DataAdapter;
|
import net.william278.husksync.adapter.DataAdapter;
|
||||||
|
import org.apache.commons.text.WordUtils;
|
||||||
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 org.jetbrains.annotations.Nullable;
|
||||||
@@ -41,6 +47,7 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class DataSnapshot {
|
public class DataSnapshot {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -59,7 +66,7 @@ public class DataSnapshot {
|
|||||||
protected OffsetDateTime timestamp;
|
protected OffsetDateTime timestamp;
|
||||||
|
|
||||||
@SerializedName("save_cause")
|
@SerializedName("save_cause")
|
||||||
protected SaveCause saveCause;
|
protected String saveCause;
|
||||||
|
|
||||||
@SerializedName("server_name")
|
@SerializedName("server_name")
|
||||||
protected String serverName;
|
protected String serverName;
|
||||||
@@ -77,7 +84,7 @@ public class DataSnapshot {
|
|||||||
protected Map<String, String> data;
|
protected Map<String, String> data;
|
||||||
|
|
||||||
private DataSnapshot(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private DataSnapshot(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.pinned = pinned;
|
this.pinned = pinned;
|
||||||
@@ -90,10 +97,6 @@ public class DataSnapshot {
|
|||||||
this.formatVersion = formatVersion;
|
this.formatVersion = formatVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private DataSnapshot() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public static DataSnapshot.Builder builder(@NotNull HuskSync plugin) {
|
public static DataSnapshot.Builder builder(@NotNull HuskSync plugin) {
|
||||||
@@ -196,7 +199,7 @@ public class DataSnapshot {
|
|||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public SaveCause getSaveCause() {
|
public SaveCause getSaveCause() {
|
||||||
return saveCause;
|
return SaveCause.of(saveCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +222,7 @@ public class DataSnapshot {
|
|||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public void setSaveCause(@NotNull SaveCause saveCause) {
|
public void setSaveCause(@NotNull SaveCause saveCause) {
|
||||||
this.saveCause = saveCause;
|
this.saveCause = saveCause.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,24 +273,21 @@ public class DataSnapshot {
|
|||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class Packed extends DataSnapshot implements Adaptable {
|
public static class Packed extends DataSnapshot implements Adaptable {
|
||||||
|
|
||||||
protected Packed(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
protected Packed(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private Packed() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public void edit(@NotNull HuskSync plugin, @NotNull Consumer<Unpacked> editor) {
|
public void edit(@NotNull HuskSync plugin, @NotNull Consumer<Unpacked> editor) {
|
||||||
final Unpacked data = unpack(plugin);
|
final Unpacked data = unpack(plugin);
|
||||||
editor.accept(data);
|
editor.accept(data);
|
||||||
this.pinned = data.isPinned();
|
this.pinned = data.isPinned();
|
||||||
this.saveCause = data.getSaveCause();
|
this.saveCause = data.getSaveCause().name();
|
||||||
this.data = data.serializeData(plugin);
|
this.data = data.serializeData(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +341,7 @@ public class DataSnapshot {
|
|||||||
private final Map<Identifier, Data> deserialized;
|
private final Map<Identifier, Data> deserialized;
|
||||||
|
|
||||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion,
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion,
|
||||||
@NotNull HuskSync plugin) {
|
@NotNull HuskSync plugin) {
|
||||||
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
||||||
@@ -349,7 +349,7 @@ public class DataSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<Identifier, Data> data,
|
@NotNull String saveCause, @NotNull String serverName, @NotNull Map<Identifier, Data> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
super(id, pinned, timestamp, saveCause, serverName, Map.of(), minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, Map.of(), minecraftVersion, platformType, formatVersion);
|
||||||
this.deserialized = data;
|
this.deserialized = data;
|
||||||
@@ -719,7 +719,7 @@ public class DataSnapshot {
|
|||||||
id,
|
id,
|
||||||
pinned || plugin.getSettings().getSynchronization().doAutoPin(saveCause),
|
pinned || plugin.getSettings().getSynchronization().doAutoPin(saveCause),
|
||||||
timestamp,
|
timestamp,
|
||||||
saveCause,
|
saveCause.name(),
|
||||||
serverName,
|
serverName,
|
||||||
data,
|
data,
|
||||||
plugin.getMinecraftVersion(),
|
plugin.getMinecraftVersion(),
|
||||||
@@ -742,138 +742,253 @@ public class DataSnapshot {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface Cause {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the capitalized display name of the cause.
|
||||||
|
*
|
||||||
|
* @return the cause display name
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
default String getDisplayName() {
|
||||||
|
return WordUtils.capitalizeFully(name().replaceAll("_", " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the cause of a player data save.
|
* A string wrapper, for identifying the cause of a player data save.
|
||||||
*
|
|
||||||
* @implNote This enum is saved in the database.
|
|
||||||
* </p>
|
* </p>
|
||||||
* Cause names have a max length of 32 characters.
|
* Cause names have a max length of 32 characters.
|
||||||
*/
|
*/
|
||||||
public enum SaveCause {
|
@Accessors(fluent = true)
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class SaveCause implements Cause {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when a player disconnected from the server (either to change servers, or to log off)
|
* Indicates data saved when a player disconnected from the server (either to change servers, or to log off)
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
DISCONNECT,
|
public static final SaveCause DISCONNECT = of("DISCONNECT");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the world saved
|
* Indicates data saved when the world saved
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
WORLD_SAVE,
|
public static final SaveCause WORLD_SAVE = of("WORLD_SAVE");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the user died
|
* Indicates data saved when the user died
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
DEATH,
|
public static final SaveCause DEATH = of("DEATH");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the server shut down
|
* Indicates data saved when the server shut down
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
SERVER_SHUTDOWN,
|
public static final SaveCause SERVER_SHUTDOWN = of("SERVER_SHUTDOWN");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by editing inventory contents via the {@code /inventory} command
|
* Indicates data was saved by editing inventory contents via the {@code /inventory} command
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
INVENTORY_COMMAND,
|
public static final SaveCause INVENTORY_COMMAND = of("INVENTORY_COMMAND");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by editing Ender Chest contents via the {@code /enderchest} command
|
* Indicates data was saved by editing Ender Chest contents via the {@code /enderchest} command
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
ENDERCHEST_COMMAND,
|
public static final SaveCause ENDERCHEST_COMMAND = of("ENDERCHEST_COMMAND");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by restoring it from a previous version
|
* Indicates data was saved by restoring it from a previous version
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
BACKUP_RESTORE,
|
public static final SaveCause BACKUP_RESTORE = of("BACKUP_RESTORE");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved by an API call
|
* Indicates data was saved by an API call
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
API,
|
public static final SaveCause API = of("API");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved from being imported from MySQLPlayerDataBridge
|
* Indicates data was saved from being imported from MySQLPlayerDataBridge
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
MPDB_MIGRATION,
|
public static final SaveCause MPDB_MIGRATION = of("MPDB_MIGRATION");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved from being imported from a legacy version (v1.x -> v2.x)
|
* Indicates data was saved from being imported from a legacy version (v1.x -> v2.x)
|
||||||
*
|
*
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
LEGACY_MIGRATION,
|
public static final SaveCause LEGACY_MIGRATION = of("LEGACY_MIGRATION");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates data was saved from being imported from a legacy version (v2.x -> v3.x)
|
* Indicates data was saved from being imported from a legacy version (v2.x -> v3.x)
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
CONVERTED_FROM_V2;
|
public static final SaveCause CONVERTED_FROM_V2 = of("CONVERTED_FROM_V2");
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getDisplayName() {
|
private final String name;
|
||||||
return name().toLowerCase(Locale.ENGLISH);
|
|
||||||
|
/**
|
||||||
|
* Get or create a {@link SaveCause} from a name
|
||||||
|
*
|
||||||
|
* @param name the name to be displayed
|
||||||
|
* @return the cause
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static SaveCause of(@NotNull String name) {
|
||||||
|
return new SaveCause(name.length() > 32 ? name.substring(0, 31) : name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getLocale(@NotNull HuskSync plugin) {
|
public String getLocale(@NotNull HuskSync plugin) {
|
||||||
return plugin.getLocales().getRawLocale("save_cause_" + name().toLowerCase())
|
return plugin.getLocales()
|
||||||
|
.getRawLocale("save_cause_" + name().toLowerCase(Locale.ENGLISH))
|
||||||
.orElse(getDisplayName());
|
.orElse(getDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static SaveCause[] values() {
|
||||||
|
return new SaveCause[]{
|
||||||
|
DISCONNECT, WORLD_SAVE, DEATH, SERVER_SHUTDOWN, INVENTORY_COMMAND, ENDERCHEST_COMMAND,
|
||||||
|
BACKUP_RESTORE, API, MPDB_MIGRATION, LEGACY_MIGRATION, CONVERTED_FROM_V2
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the cause of a player having their data updated.
|
* A string wrapper, for identifying the cause of a player data update.
|
||||||
|
* </p>
|
||||||
|
* Cause names have a max length of 32 characters.
|
||||||
*/
|
*/
|
||||||
public enum UpdateCause {
|
@Accessors(fluent = true)
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class UpdateCause implements Cause {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the data was updated by a synchronization process
|
* Indicates the data was updated by a synchronization process
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
SYNCHRONIZED("synchronization_complete", "synchronization_failed"),
|
public static final UpdateCause SYNCHRONIZED = of("SYNCHRONIZED",
|
||||||
|
"synchronization_complete", "synchronization_failed"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the data was updated by a user joining the server
|
* Indicates the data was updated by a user joining the server
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
NEW_USER("user_registration_complete", null),
|
public static final UpdateCause NEW_USER = of("NEW_USER",
|
||||||
|
"user_registration_complete", null
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the data was updated by a data update process (management command, API, etc.)
|
* Indicates the data was updated by a data update process (management command, API, etc.)
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
UPDATED("data_update_complete", "data_update_failed");
|
public static final UpdateCause UPDATED = of("UPDATED",
|
||||||
|
"data_update_complete", "data_update_failed"
|
||||||
|
);
|
||||||
|
|
||||||
private final String completedLocale;
|
/**
|
||||||
private final String failureLocale;
|
* Indicates data was saved by an API call
|
||||||
|
*
|
||||||
|
* @since 3.3.3
|
||||||
|
*/
|
||||||
|
public static final UpdateCause API = of("API");
|
||||||
|
|
||||||
UpdateCause(@Nullable String completedLocale, @Nullable String failureLocale) {
|
@NotNull
|
||||||
this.completedLocale = completedLocale;
|
private final String name;
|
||||||
this.failureLocale = failureLocale;
|
@Nullable
|
||||||
|
private String completedLocale;
|
||||||
|
@Nullable
|
||||||
|
private String failureLocale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a {@link UpdateCause} from a name and completed/failure locales
|
||||||
|
*
|
||||||
|
* @param name the name to be displayed
|
||||||
|
* @param completedLocale the locale to be displayed on successful update,
|
||||||
|
* or {@code null} if none is to be shown
|
||||||
|
* @param failureLocale the locale to be displayed on a failed update,
|
||||||
|
* or {@code null} if none is to be shown
|
||||||
|
* @return the cause
|
||||||
|
*/
|
||||||
|
public static UpdateCause of(@NotNull String name, @Nullable String completedLocale,
|
||||||
|
@Nullable String failureLocale) {
|
||||||
|
return new UpdateCause(
|
||||||
|
name.length() > 32 ? name.substring(0, 31) : name,
|
||||||
|
completedLocale, failureLocale
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a {@link UpdateCause} from a name
|
||||||
|
*
|
||||||
|
* @param name the name to be displayed
|
||||||
|
* @return the cause
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static UpdateCause of(@NotNull String name) {
|
||||||
|
return of(name, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message to be displayed when a user's data is successfully updated.
|
||||||
|
*
|
||||||
|
* @param plugin plugin instance
|
||||||
|
* @return the message
|
||||||
|
*/
|
||||||
public Optional<MineDown> getCompletedLocale(@NotNull HuskSync plugin) {
|
public Optional<MineDown> getCompletedLocale(@NotNull HuskSync plugin) {
|
||||||
if (completedLocale != null) {
|
if (completedLocale() != null) {
|
||||||
return plugin.getLocales().getLocale(completedLocale);
|
return Optional.of(plugin.getLocales().getLocale(completedLocale())
|
||||||
|
.orElse(plugin.getLocales().format(getDisplayName())));
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message to be displayed when a user's data fails to update.
|
||||||
|
*
|
||||||
|
* @param plugin plugin instance
|
||||||
|
* @return the message
|
||||||
|
*/
|
||||||
public Optional<MineDown> getFailedLocale(@NotNull HuskSync plugin) {
|
public Optional<MineDown> getFailedLocale(@NotNull HuskSync plugin) {
|
||||||
if (failureLocale != null) {
|
if (failureLocale() != null) {
|
||||||
return plugin.getLocales().getLocale(failureLocale);
|
return Optional.of(plugin.getLocales().getLocale(failureLocale())
|
||||||
|
.orElse(plugin.getLocales().format(failureLocale())));
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static UpdateCause[] values() {
|
||||||
|
return new UpdateCause[]{
|
||||||
|
SYNCHRONIZED, NEW_USER, UPDATED
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +81,7 @@ 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)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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
|
||||||
@@ -168,7 +170,6 @@ public abstract class EventListener {
|
|||||||
return Map.entry(name().toLowerCase(), defaultPriority.name());
|
return Map.entry(name().toLowerCase(), defaultPriority.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@NotNull
|
@NotNull
|
||||||
public static Map<String, String> getDefaults() {
|
public static Map<String, String> getDefaults() {
|
||||||
|
|||||||
@@ -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,58 @@ 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @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.3
|
||||||
|
*/
|
||||||
|
public void saveData(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
|
saveData(user, data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +156,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 +197,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.3
|
||||||
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