mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-23 16:49:19 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39767c5cd0 | ||
|
|
48f7037898 | ||
|
|
67dddf0cfa | ||
|
|
eeb5e57c1e | ||
|
|
5a6ea2cffe | ||
|
|
07ddd34f8e | ||
|
|
a0b86c298f | ||
|
|
6fbef032bc | ||
|
|
318aacd432 | ||
|
|
ba1b2ff62e | ||
|
|
67ef4888da | ||
|
|
a5d3015c6e | ||
|
|
131a364f53 | ||
|
|
19636d9447 | ||
|
|
f803a0b57b | ||
|
|
28afffe95e | ||
|
|
c7e100a78a | ||
|
|
12e223618d | ||
|
|
f6773f4e68 | ||
|
|
b9434a56e8 |
@@ -3,7 +3,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.2.1'
|
id 'org.ajoberstar.grgit' version '5.2.2'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ ext {
|
|||||||
set 'jedis_version', jedis_version.toString()
|
set 'jedis_version', jedis_version.toString()
|
||||||
set 'mysql_driver_version', mysql_driver_version.toString()
|
set 'mysql_driver_version', mysql_driver_version.toString()
|
||||||
set 'mariadb_driver_version', mariadb_driver_version.toString()
|
set 'mariadb_driver_version', mariadb_driver_version.toString()
|
||||||
|
set 'mongodb_driver_version', mongodb_driver_version.toString()
|
||||||
set 'snappy_version', snappy_version.toString()
|
set 'snappy_version', snappy_version.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ 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.5.0'
|
||||||
compileOnly 'com.zaxxer:HikariCP:5.1.0'
|
compileOnly 'com.zaxxer:HikariCP:5.1.0'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
@@ -39,7 +39,7 @@ shadowJar {
|
|||||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'de.exlll', 'net.william278.huskclaims.libraries'
|
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import net.william278.husksync.data.Data;
|
|||||||
import net.william278.husksync.data.Identifier;
|
import net.william278.husksync.data.Identifier;
|
||||||
import net.william278.husksync.data.Serializer;
|
import net.william278.husksync.data.Serializer;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
|
import net.william278.husksync.database.MongoDbDatabase;
|
||||||
import net.william278.husksync.database.MySqlDatabase;
|
import net.william278.husksync.database.MySqlDatabase;
|
||||||
import net.william278.husksync.event.BukkitEventDispatcher;
|
import net.william278.husksync.event.BukkitEventDispatcher;
|
||||||
import net.william278.husksync.hook.PlanHook;
|
import net.william278.husksync.hook.PlanHook;
|
||||||
@@ -60,7 +61,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;
|
||||||
@@ -163,7 +163,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
|
|
||||||
// Initialize the database
|
// Initialize the database
|
||||||
initialize(getSettings().getDatabase().getType().getDisplayName() + " database connection", (plugin) -> {
|
initialize(getSettings().getDatabase().getType().getDisplayName() + " database connection", (plugin) -> {
|
||||||
this.database = new MySqlDatabase(this);
|
this.database = switch (settings.getDatabase().getType()) {
|
||||||
|
case MYSQL, MARIADB -> new MySqlDatabase(this);
|
||||||
|
case MONGO -> new MongoDbDatabase(this);
|
||||||
|
default -> throw new IllegalStateException("Invalid database type");
|
||||||
|
};
|
||||||
this.database.initialize();
|
this.database.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,7 +233,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 +241,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 +257,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 +271,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 +305,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 +349,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();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import net.william278.husksync.user.OnlineUser;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.entity.Projectile;
|
import org.bukkit.entity.Projectile;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -49,6 +50,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BukkitEventListener extends EventListener implements BukkitJoinEventListener, BukkitQuitEventListener,
|
public class BukkitEventListener extends EventListener implements BukkitJoinEventListener, BukkitQuitEventListener,
|
||||||
@@ -132,52 +134,52 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
|||||||
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
|
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
|
||||||
final Projectile projectile = event.getEntity();
|
final Projectile projectile = event.getEntity();
|
||||||
if (projectile.getShooter() instanceof Player player) {
|
if (projectile.getShooter() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
cancelPlayerEvent(player.getUniqueId(), event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
cancelPlayerEvent(player.getUniqueId(), event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerInteractEntity(@NotNull PlayerInteractEntityEvent event) {
|
public void onPlayerInteractEntity(@NotNull PlayerInteractEntityEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
||||||
if (event.getPlayer() instanceof Player player) {
|
if (event.getPlayer() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
cancelPlayerEvent(player.getUniqueId(), event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onInventoryClick(@NotNull InventoryClickEvent event) {
|
public void onInventoryClick(@NotNull InventoryClickEvent event) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getWhoClicked().getUniqueId()));
|
cancelPlayerEvent(event.getWhoClicked().getUniqueId(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
@@ -187,7 +189,7 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
|||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
cancelPlayerEvent(player.getUniqueId(), event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +199,13 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
|||||||
final String commandLabel = commandArgs[0].toLowerCase(Locale.ENGLISH);
|
final String commandLabel = commandArgs[0].toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
if (blacklistedCommands.contains("*") || blacklistedCommands.contains(commandLabel)) {
|
if (blacklistedCommands.contains("*") || blacklistedCommands.contains(commandLabel)) {
|
||||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelPlayerEvent(@NotNull UUID uuid, @NotNull Cancellable event) {
|
||||||
|
if (cancelPlayerEvent(uuid)) {
|
||||||
|
event.setCancelled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ libraries:
|
|||||||
- 'redis.clients:jedis:${jedis_version}'
|
- 'redis.clients:jedis:${jedis_version}'
|
||||||
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
||||||
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
||||||
|
- 'org.mongodb:mongodb-driver:${mongodb_driver_version}'
|
||||||
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||||
@@ -6,10 +6,10 @@ 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.5.0'
|
||||||
api 'net.william278:DesertWell:2.0.4'
|
api 'net.william278:DesertWell:2.0.4'
|
||||||
api 'net.william278:PagineDown:1.1'
|
api 'net.william278:PagineDown:1.1'
|
||||||
api('com.zaxxer:HikariCP:5.1.0') {
|
api('com.zaxxer:HikariCP:5.1.0') {
|
||||||
@@ -18,20 +18,21 @@ dependencies {
|
|||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'net.kyori:adventure-api:4.15.0'
|
compileOnly 'net.kyori:adventure-api:4.16.0'
|
||||||
compileOnly 'net.kyori:adventure-platform-api:4.3.2'
|
compileOnly 'net.kyori:adventure-platform-api:4.3.2'
|
||||||
compileOnly 'com.google.guava:guava:33.0.0-jre'
|
compileOnly 'com.google.guava:guava:33.0.0-jre'
|
||||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
||||||
compileOnly "org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version"
|
compileOnly "org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version"
|
||||||
|
compileOnly "org.mongodb:mongodb-driver:$mongodb_driver_version"
|
||||||
compileOnly "org.xerial.snappy:snappy-java:$snappy_version"
|
compileOnly "org.xerial.snappy:snappy-java:$snappy_version"
|
||||||
|
|
||||||
testImplementation "redis.clients:jedis:$jedis_version"
|
testImplementation "redis.clients:jedis:$jedis_version"
|
||||||
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
||||||
testImplementation 'com.google.guava:guava:33.0.0-jre'
|
testImplementation 'com.google.guava:guava:33.0.0-jre'
|
||||||
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
testCompileOnly 'com.github.Exlll.ConfigLib:configlib-yaml:v4.4.0'
|
testCompileOnly 'com.github.Exlll.ConfigLib:configlib-yaml:v4.5.0'
|
||||||
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized.
|
HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized.
|
||||||
Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup):
|
Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup):
|
||||||
|
|
||||||
1) Make sure you've entered your MySQL or MariaDB database details correctly in config.yml
|
1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml
|
||||||
2) Make sure your Redis server details are also correct in config.yml
|
2) Make sure your Redis server details are also correct in config.yml
|
||||||
3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)
|
3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)
|
||||||
4) Check the error below for more details
|
4) Check the error below for more details
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
|||||||
AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net"))
|
AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net"))
|
||||||
.credits("Contributors",
|
.credits("Contributors",
|
||||||
AboutMenu.Credit.of("HarvelsX").description("Code"),
|
AboutMenu.Credit.of("HarvelsX").description("Code"),
|
||||||
AboutMenu.Credit.of("HookWoods").description("Code"))
|
AboutMenu.Credit.of("HookWoods").description("Code"),
|
||||||
|
AboutMenu.Credit.of("Preva1l").description("Code"))
|
||||||
.credits("Translators",
|
.credits("Translators",
|
||||||
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
||||||
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
||||||
|
|||||||
@@ -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" -> {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import net.william278.husksync.data.Identifier;
|
|||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.listener.EventListener;
|
import net.william278.husksync.listener.EventListener;
|
||||||
import net.william278.husksync.sync.DataSyncer;
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
|
import org.checkerframework.checker.units.qual.C;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -85,10 +86,10 @@ public class Settings {
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class DatabaseSettings {
|
public static class DatabaseSettings {
|
||||||
|
|
||||||
@Comment("Type of database to use (MYSQL, MARIADB)")
|
@Comment("Type of database to use (MYSQL, MARIADB, MONGO)")
|
||||||
private Database.Type type = Database.Type.MYSQL;
|
private Database.Type type = Database.Type.MYSQL;
|
||||||
|
|
||||||
@Comment("Specify credentials here for your MYSQL or MARIADB database")
|
@Comment("Specify credentials here for your MYSQL, MARIADB OR MONGO database")
|
||||||
private DatabaseCredentials credentials = new DatabaseCredentials();
|
private DatabaseCredentials credentials = new DatabaseCredentials();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -100,9 +101,12 @@ public class Settings {
|
|||||||
private String database = "HuskSync";
|
private String database = "HuskSync";
|
||||||
private String username = "root";
|
private String username = "root";
|
||||||
private String password = "pa55w0rd";
|
private String password = "pa55w0rd";
|
||||||
|
@Comment("Only change this if you have select MYSQL or MARIADB")
|
||||||
private String parameters = String.join("&",
|
private String parameters = String.join("&",
|
||||||
"?autoReconnect=true", "useSSL=false",
|
"?autoReconnect=true", "useSSL=false",
|
||||||
"useUnicode=true", "characterEncoding=UTF-8");
|
"useUnicode=true", "characterEncoding=UTF-8");
|
||||||
|
@Comment("Only change this if you have selected MONGO")
|
||||||
|
private String mongoAuthDb = "admin";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Comment("MYSQL / MARIADB database Hikari connection pool properties. Don't modify this unless you know what you're doing!")
|
@Comment("MYSQL / MARIADB database Hikari connection pool properties. Don't modify this unless you know what you're doing!")
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,11 @@
|
|||||||
|
|
||||||
package net.william278.husksync.database;
|
package net.william278.husksync.database;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
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 +32,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 +156,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));
|
||||||
@@ -273,9 +254,11 @@ public abstract class Database {
|
|||||||
/**
|
/**
|
||||||
* Identifies types of databases
|
* Identifies types of databases
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
public enum Type {
|
public enum Type {
|
||||||
MYSQL("MySQL", "mysql"),
|
MYSQL("MySQL", "mysql"),
|
||||||
MARIADB("MariaDB", "mariadb");
|
MARIADB("MariaDB", "mariadb"),
|
||||||
|
MONGO("MongoDB", "mongo");
|
||||||
|
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
private final String protocol;
|
private final String protocol;
|
||||||
@@ -284,16 +267,6 @@ public abstract class Database {
|
|||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getProtocol() {
|
|
||||||
return protocol;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.database;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.mongodb.MongoException;
|
||||||
|
import com.mongodb.client.FindIterable;
|
||||||
|
import com.mongodb.client.model.Updates;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.database.mongo.MongoCollectionHelper;
|
||||||
|
import net.william278.husksync.database.mongo.MongoConnectionHandler;
|
||||||
|
import net.william278.husksync.user.User;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.jetbrains.annotations.Blocking;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class MongoDbDatabase extends Database {
|
||||||
|
private MongoConnectionHandler mongoConnectionHandler;
|
||||||
|
private MongoCollectionHelper mongoCollectionHelper;
|
||||||
|
|
||||||
|
private final String usersTable;
|
||||||
|
private final String userDataTable;
|
||||||
|
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
||||||
|
this.userDataTable = plugin.getSettings().getDatabase().getTableName(TableName.USER_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the database and ensure tables are present; create tables if they do not exist.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the database could not be initialized
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize() throws IllegalStateException {
|
||||||
|
final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials();
|
||||||
|
try {
|
||||||
|
mongoConnectionHandler = new MongoConnectionHandler(
|
||||||
|
credentials.getHost(),
|
||||||
|
credentials.getPort(),
|
||||||
|
credentials.getUsername(),
|
||||||
|
credentials.getPassword(),
|
||||||
|
credentials.getDatabase(),
|
||||||
|
credentials.getMongoAuthDb()
|
||||||
|
);
|
||||||
|
mongoCollectionHelper = new MongoCollectionHelper(mongoConnectionHandler);
|
||||||
|
if (mongoCollectionHelper.getCollection(usersTable) == null) {
|
||||||
|
mongoCollectionHelper.createCollection(usersTable);
|
||||||
|
}
|
||||||
|
if (mongoCollectionHelper.getCollection(userDataTable) == null) {
|
||||||
|
mongoCollectionHelper.createCollection(userDataTable);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||||
|
"Please check the supplied database credentials in the config file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a {@link User} has an entry in the database and that their username is up-to-date
|
||||||
|
*
|
||||||
|
* @param user The {@link User} to ensure
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public void ensureUser(@NotNull User user) {
|
||||||
|
getUser(user.getUuid()).ifPresentOrElse(
|
||||||
|
existingUser -> {
|
||||||
|
if (!existingUser.getUsername().equals(user.getUsername())) {
|
||||||
|
// Update a user's name if it has changed in the database
|
||||||
|
try {
|
||||||
|
Document filter = new Document("uuid", existingUser.getUuid().toString());
|
||||||
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
|
|
||||||
|
Bson updates = Updates.set("uuid", user.getUuid().toString());
|
||||||
|
mongoCollectionHelper.updateDocument(usersTable, doc, updates);
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
// Insert new player data into the database
|
||||||
|
try {
|
||||||
|
Document doc = new Document("uuid", user.getUuid().toString()).append("username", user.getUsername());
|
||||||
|
mongoCollectionHelper.insertDocument(usersTable, doc);
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a player by their Minecraft account {@link UUID}
|
||||||
|
*
|
||||||
|
* @param uuid Minecraft account {@link UUID} of the {@link User} to get
|
||||||
|
* @return An optional with the {@link User} present if they exist
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public Optional<User> getUser(@NotNull UUID uuid) {
|
||||||
|
Document filter = new Document("uuid", uuid);
|
||||||
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
|
if (doc != null) {
|
||||||
|
return Optional.of(new User(UUID.fromString(doc.getString("uuid")),
|
||||||
|
doc.getString("username")));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user by their username (<i>case-insensitive</i>)
|
||||||
|
*
|
||||||
|
* @param username Username of the {@link User} to get (<i>case-insensitive</i>)
|
||||||
|
* @return An optional with the {@link User} present if they exist
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public Optional<User> getUserByName(@NotNull String username) {
|
||||||
|
Document filter = new Document("username", username);
|
||||||
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
|
if (doc != null) {
|
||||||
|
return Optional.of(new User(UUID.fromString(doc.getString("uuid")),
|
||||||
|
doc.getString("username")));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest data snapshot for a user.
|
||||||
|
*
|
||||||
|
* @param user The user to get data for
|
||||||
|
* @return an optional containing the {@link DataSnapshot}, if it exists, or an empty optional if it does not
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString());
|
||||||
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
|
Document doc = iterable.first();
|
||||||
|
if (doc != null) {
|
||||||
|
final UUID versionUuid = UUID.fromString(doc.getString("version_uuid"));
|
||||||
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
||||||
|
final Binary bin = doc.get("data", Binary.class);
|
||||||
|
final byte[] dataByteArray = bin.getData();
|
||||||
|
return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all {@link DataSnapshot} entries for a user from the database.
|
||||||
|
*
|
||||||
|
* @param user The user to get data for
|
||||||
|
* @return The list of a user's {@link DataSnapshot} entries
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<DataSnapshot.Packed> getAllSnapshots(@NotNull User user) {
|
||||||
|
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString());
|
||||||
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
|
for (Document doc : iterable) {
|
||||||
|
final UUID versionUuid = UUID.fromString(doc.getString("version_uuid"));
|
||||||
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
||||||
|
final Binary bin = doc.get("data", Binary.class);
|
||||||
|
final byte[] dataByteArray = bin.getData();
|
||||||
|
retrievedData.add(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
|
||||||
|
}
|
||||||
|
return retrievedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
|
||||||
|
*
|
||||||
|
* @param user The user to get data for
|
||||||
|
* @param versionUuid The UUID of the {@link DataSnapshot} entry to get
|
||||||
|
* @return An optional containing the {@link DataSnapshot}, if it exists
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", versionUuid.toString());
|
||||||
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
|
Document doc = iterable.first();
|
||||||
|
if (doc != null) {
|
||||||
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
||||||
|
final Binary bin = doc.get("data", Binary.class);
|
||||||
|
final byte[] dataByteArray = bin.getData();
|
||||||
|
return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
||||||
|
*
|
||||||
|
* @param user The user to prune data for
|
||||||
|
* @implNote Data snapshots marked as {@code pinned} are exempt from rotation
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
protected void rotateSnapshots(@NotNull User user) {
|
||||||
|
try {
|
||||||
|
final List<DataSnapshot.Packed> unpinnedUserData = getAllSnapshots(user).stream()
|
||||||
|
.filter(dataSnapshot -> !dataSnapshot.isPinned()).toList();
|
||||||
|
final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots();
|
||||||
|
if (unpinnedUserData.size() > maxSnapshots) {
|
||||||
|
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString()).append("pinned", false);
|
||||||
|
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
||||||
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
||||||
|
.find(filter)
|
||||||
|
.sort(sort)
|
||||||
|
.limit(unpinnedUserData.size() - maxSnapshots);
|
||||||
|
|
||||||
|
for (Document doc : iterable) {
|
||||||
|
mongoCollectionHelper.deleteDocument(userDataTable, doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
|
||||||
|
*
|
||||||
|
* @param user The user to get data for
|
||||||
|
* @param versionUuid The UUID of the {@link DataSnapshot} entry to delete
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
|
try {
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", versionUuid.toString());
|
||||||
|
Document doc = mongoCollectionHelper.getCollection(userDataTable).find(filter).first();
|
||||||
|
if (doc == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mongoCollectionHelper.deleteDocument(userDataTable, doc);
|
||||||
|
return true;
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to delete specific user data from the database", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the most recent data snapshot by the given {@link User user}
|
||||||
|
* The snapshot must have been created after {@link OffsetDateTime time} and NOT be pinned
|
||||||
|
* Facilities the backup frequency feature, reducing redundant snapshots from being saved longer than needed
|
||||||
|
*
|
||||||
|
* @param user The user to delete a snapshot for
|
||||||
|
* @param within The time to delete a snapshot after
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
||||||
|
try {
|
||||||
|
Document filter = new Document("player_uuid", user.getUuid().toString()).append("pinned", false);
|
||||||
|
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
||||||
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
||||||
|
.find(filter)
|
||||||
|
.sort(sort);
|
||||||
|
|
||||||
|
for (Document doc : iterable) {
|
||||||
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId()
|
||||||
|
);
|
||||||
|
if (timestamp.isAfter(within)) {
|
||||||
|
mongoCollectionHelper.deleteDocument(userDataTable, doc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>Internal</b> - Create user data in the database
|
||||||
|
*
|
||||||
|
* @param user The user to add data for
|
||||||
|
* @param data The {@link DataSnapshot} to set.
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
|
try {
|
||||||
|
Document doc = new Document("player_uuid", user.getUuid().toString())
|
||||||
|
.append("version_uuid", data.getId().toString())
|
||||||
|
.append("timestamp", data.getTimestamp().toInstant().toEpochMilli())
|
||||||
|
.append("save_cause", data.getSaveCause().name())
|
||||||
|
.append("pinned", data.isPinned())
|
||||||
|
.append("data", new Binary(data.asBytes(plugin)));
|
||||||
|
mongoCollectionHelper.insertDocument(userDataTable, doc);
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a saved {@link DataSnapshot} by given version UUID
|
||||||
|
*
|
||||||
|
* @param user The user whose data snapshot
|
||||||
|
* @param data The {@link DataSnapshot} to update
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
|
try {
|
||||||
|
Document doc = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", data.getId().toString());
|
||||||
|
Bson updates = Updates.combine(
|
||||||
|
Updates.set("save_cause", data.getSaveCause().name()),
|
||||||
|
Updates.set("pinned", data.isPinned()),
|
||||||
|
Updates.set("data", new Binary(data.asBytes(plugin)))
|
||||||
|
);
|
||||||
|
mongoCollectionHelper.updateDocument(userDataTable, doc, updates);
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to pin user data in the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wipes <b>all</b> {@link User} entries from the database.
|
||||||
|
* <b>This should only be used when preparing tables for a data migration.</b>
|
||||||
|
*/
|
||||||
|
@Blocking
|
||||||
|
@Override
|
||||||
|
public void wipeDatabase() {
|
||||||
|
try {
|
||||||
|
mongoCollectionHelper.deleteCollection(usersTable);
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the database connection
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void terminate() {
|
||||||
|
if (mongoConnectionHandler != null) {
|
||||||
|
mongoConnectionHandler.closeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.database.mongo;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class MongoCollectionHelper {
|
||||||
|
private final MongoConnectionHandler database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the collection helper
|
||||||
|
* @param database Instance of {@link MongoConnectionHandler}
|
||||||
|
*/
|
||||||
|
public MongoCollectionHelper(@NotNull MongoConnectionHandler database) {
|
||||||
|
this.database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a collection
|
||||||
|
* @param collectionName the collection name
|
||||||
|
*/
|
||||||
|
public void createCollection(@NotNull String collectionName) {
|
||||||
|
database.getDatabase().createCollection(collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a collection
|
||||||
|
* @param collectionName the collection name
|
||||||
|
*/
|
||||||
|
public void deleteCollection(@NotNull String collectionName) {
|
||||||
|
database.getDatabase().getCollection(collectionName).drop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a collection
|
||||||
|
* @param collectionName the collection name
|
||||||
|
* @return MongoCollection<Document>
|
||||||
|
*/
|
||||||
|
public MongoCollection<Document> getCollection(@NotNull String collectionName) {
|
||||||
|
return database.getDatabase().getCollection(collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a document to a collection
|
||||||
|
* @param collectionName collection to add to
|
||||||
|
* @param document Document to add
|
||||||
|
*/
|
||||||
|
public void insertDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
|
collection.insertOne(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a document
|
||||||
|
* @param collectionName collection the document is in
|
||||||
|
* @param document filter of document
|
||||||
|
* @param updates Bson of updates
|
||||||
|
*/
|
||||||
|
public void updateDocument(@NotNull String collectionName, @NotNull Document document, @NotNull Bson updates) {
|
||||||
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
|
collection.updateOne(document, updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a document
|
||||||
|
* @param collectionName collection the document is in
|
||||||
|
* @param document filter to remove
|
||||||
|
*/
|
||||||
|
public void deleteDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
|
collection.deleteOne(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.database.mongo;
|
||||||
|
|
||||||
|
import com.mongodb.MongoClientSettings;
|
||||||
|
import com.mongodb.MongoCredential;
|
||||||
|
import com.mongodb.ServerAddress;
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class MongoConnectionHandler {
|
||||||
|
private final MongoClient mongoClient;
|
||||||
|
private final MongoDatabase database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate a connection to a Mongo Server
|
||||||
|
* @param host The IP/Host Name of the Mongo Server
|
||||||
|
* @param port The Port of the Mongo Server
|
||||||
|
* @param username The Username of the user with the appropriate permissions
|
||||||
|
* @param password The Password of the user with the appropriate permissions
|
||||||
|
* @param databaseName The database to use.
|
||||||
|
* @param authDb The database to authenticate with.
|
||||||
|
*/
|
||||||
|
public MongoConnectionHandler(@NotNull String host, @NotNull Integer port, @NotNull String username, @NotNull String password, @NotNull String databaseName, @NotNull String authDb) {
|
||||||
|
final ServerAddress serverAddress = new ServerAddress(host, port);
|
||||||
|
final MongoCredential credential = MongoCredential.createCredential(username, authDb, password.toCharArray());
|
||||||
|
|
||||||
|
final MongoClientSettings settings = MongoClientSettings.builder()
|
||||||
|
.credential(credential)
|
||||||
|
.applyToClusterSettings(builder -> builder.hosts(Collections.singletonList(serverAddress)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.mongoClient = MongoClients.create(settings);
|
||||||
|
this.database = mongoClient.getDatabase(databaseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the connection with the database
|
||||||
|
*/
|
||||||
|
public void closeConnection() {
|
||||||
|
if (this.mongoClient != null) {
|
||||||
|
this.mongoClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,11 @@ 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),
|
||||||
|
(saved, data) -> plugin.getRedisManager().clearUserData(saved)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close outstanding connections
|
// Close outstanding connections
|
||||||
@@ -168,7 +172,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);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
locales:
|
locales:
|
||||||
synchronization_complete: '[⏵資料已同步!](#00fb9a)'
|
synchronization_complete: '[⏵ 資料已同步!](#00fb9a)'
|
||||||
synchronization_failed: '[⏵ 無法同步您的資料! 請聯繫管理員](#ff7e5e)'
|
synchronization_failed: '[⏵ 無法同步您的資料! 請聯繫管理員](#ff7e5e)'
|
||||||
inventory_viewer_menu_title: '&0%1% 的背包'
|
inventory_viewer_menu_title: '&0%1% 的背包'
|
||||||
ender_chest_viewer_menu_title: '&0%1% 的終界箱'
|
ender_chest_viewer_menu_title: '&0%1% 的終界箱'
|
||||||
inventory_viewer_opened: '[查看](#00fb9a) [%1%](#00fb9a bold) [於 ⌚ %2% 的背包快照資料](#00fb9a)'
|
inventory_viewer_opened: '[查看備份](#00fb9a) [%1%](#00fb9a bold) [於 ⌚ %2% 的背包備份](#00fb9a)'
|
||||||
ender_chest_viewer_opened: '[查看](#00fb9a) [%1%](#00fb9a bold) [於 ⌚ %2% 的終界箱快照資料](#00fb9a)'
|
ender_chest_viewer_opened: '[查看備份](#00fb9a) [%1%](#00fb9a bold) [於 ⌚ %2% 的終界箱備份](#00fb9a)'
|
||||||
data_update_complete: '[🔔 你的資料已更新!](#00fb9a)'
|
data_update_complete: '[🔔 你的數據資料已更新!](#00fb9a)'
|
||||||
data_update_failed: '[🔔 無法更新您的資料! 請聯繫管理員](#ff7e5e)'
|
data_update_failed: '[🔔 無法更新你的數據資料! 請聯繫管理員](#ff7e5e)'
|
||||||
user_registration_complete: '[⭐ User registration complete!](#00fb9a)'
|
user_registration_complete: '[⭐ 用戶註冊完成!](#00fb9a)'
|
||||||
data_manager_title: '[查看](#00fb9a) [%3%](#00fb9a bold show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [:](#00fb9a)'
|
data_manager_title: '[正在查看玩家](#00fb9a) [%3%](#00fb9a bold show_text=&7玩家UUID:\n&8%4%) [的數據備份](#00fb9a) [%1%](#00fb9a show_text=&7備份版本UUID:\n&8%2%)[:](#00fb9a)'
|
||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7快照時間:\n&8何時保存的資料)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7備份時間:\n&8數據保存時間)'
|
||||||
data_manager_pinned: '[※ 被標記的快照](#d8ff2b show_text=&7標記:\n&8此快照資料不會自動輪換更新)'
|
data_manager_pinned: '[※ 被標記的備份](#d8ff2b show_text=&7已標記:\n&8此玩家數據備份不會按照備份時間自動排序)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存原因:\n&8保存此快照的原因)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存原因:\n&8導致數據保存的原因)'
|
||||||
data_manager_server: '[☁ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
data_manager_server: '[☁ %1%](#ff87b3-#f5538e show_text=&7伺服器:\n&8數據保存所在伺服器的名稱)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7備份大小:\n&8備份的估計文件大小(以KiB為單位))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7飽食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7經驗等級) [🏹 %5%](dark_aqua show_text=&7遊戲模式)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7飽食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7經驗等級) [🏹 %5%](dark_aqua show_text=&7遊戲模式)'
|
||||||
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7已獲得的成就:\n&8%2%) [⌛ 遊戲時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7遊戲內的遊玩時間\n&8⚠ 根據遊戲內統計)\n'
|
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7已獲得的成就:\n&8%2%) [⌛ 遊玩時間: %3%小時](color=#62a9f5-#7ab8fa show_text=&7在遊戲内遊玩的時間\n&8⚠ 基於遊戲内的統計訊息)\n'
|
||||||
data_manager_item_buttons: '[查看:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7點擊查看 run_command=/inventory %1% %2%) [[⌀ 終界箱…]](#b649c4-#d254ff show_text=&7點擊查看 run_command=/enderchest %1% %2%)'
|
data_manager_item_buttons: '[查看:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7點擊查看 run_command=/inventory %1% %2%) [[⌀ 終界箱…]](#b649c4-#d254ff show_text=&7點擊查看 run_command=/enderchest %1% %2%)'
|
||||||
data_manager_management_buttons: '[管理:](gray) [[❌ 刪除…]](#ff3300 show_text=&7點擊刪除這個快照\n&8這不會影像目前玩家的資料\n&#ff3300&⚠ 此操作不能取消! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 恢復…]](#00fb9a show_text=&7點擊將玩家資料覆蓋為此快照\n&8這將導致玩家的資料會被此快照覆蓋\n&#ff3300&⚠ %1% 當前的資料將被覆蓋! suggest_command=/husksync:userdata restore %1% %2%) [[※ 標記…]](#d8ff2b show_text=&7點擊切換標記狀態\n&8被標記的快照將不會自動輪換更新 run_command=/userdata pin %1% %2%)'
|
data_manager_management_buttons: '[管理:](gray) [[❌ 刪除…]](#ff3300 show_text=&7點擊刪除此玩家數據的備份.\n&8這不會影響玩家的當前數據.\n&#ff3300&⚠ 此操作無法撤銷! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 恢復…]](#00fb9a show_text=&7點擊還原此玩家的數據.\n&8這將會讓用户的數據恢復到此備份.\n&#ff3300&⚠ %1%當前的數據將被覆蓋! suggest_command=/husksync:userdata restore %1% %2%) [[※ 標記/取消標記…]](#d8ff2b show_text=&7點擊標記或取消標記此玩家數據備份\n&8已標記的備份不會按照備份時間自動排序 run_command=/userdata pin %1% %2%)'
|
||||||
data_manager_system_buttons: '[系統:](gray) [[⏷ 本地轉存…]](dark_gray show_text=&7點擊將此玩家資料快照轉存到本地文件中\n&8轉存的資料可以在以下路徑找到 ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ 雲端轉存…]](dark_gray show_text=&7點擊將此玩家資料快照轉存到 mc-logs 服務\n&8您將獲得一個包含資料的 URL. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_system_buttons: '[系統:](gray) [[⏷ 本地轉存…]](dark_gray show_text=&7點擊將此玩家數據資料轉存到本地文件中.\n&8轉存的資料可以在以下路徑找到~/plugins/HuskSync/dumps/中找到 run_command=/husksync:userdata dump %1% %2% file) [[☂ 雲端轉存…]](dark_gray show_text=&7點擊將此玩家數據資料轉存到 mc-logs 服務中\n&8您將獲得一個包含資料的URL. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_manager_advancements_preview_remaining: '還有 %1% …'
|
data_manager_advancements_preview_remaining: '以及其他 %1%…'
|
||||||
data_list_title: '[%1% 的玩家資料快照:](#00fb9a) [(%2%-%3% 共](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[%1%的玩家數據備份:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7User Data Snapshot for %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Pinned:\n&8Pinned snapshots won''t be automatically rotated. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Version timestamp:&7\n&8When the data was saved\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:&7\n&8Estimated file size of the snapshot (in KiB) run_command=/userdata view %2% %3%)'
|
data_list_item: '[%1%](gray show_text=&7玩家數據備份 %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7已標記:\n&8標記的備份不會自動加載 run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7備份時間:&7\n&8數據保存時間\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7保存原因:\n&8導致數據保存的原因 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7備份大小:&7\n&8備份的估計文件大小(以KiB為單位) run_command=/userdata view %2% %3%)'
|
||||||
data_deleted: '[❌ 成功刪除:](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
data_deleted: '[❌ 成功刪除玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&7%4%) [的數據備份](#00fb9a) [%1%.](#00fb9a show_text=&7備份版本UUID:\n&7%2%)'
|
||||||
data_restored: '[⏪ 成功將玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&8%2%)[的資料恢復為 快照:](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
data_restored: '[⏪ 成功恢復玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&7%2%)[的數據備份](#00fb9a) [%3%.](#00fb9a show_text=&7備份版本UUID:\n&7%4%)'
|
||||||
data_pinned: '[※ 成功標記](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
data_pinned: '[※ 成功標記玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的數據備份](#00fb9a) [%1%.](#00fb9a show_text=&7備份版本UUID:\n&8%2%)'
|
||||||
data_unpinned: '[※ 成功解除](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [的標記](#00fb9a)'
|
data_unpinned: '[※ 成功取消標記玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的數據備份](#00fb9a) [%1%.](#00fb9a show_text=&7備份版本UUID:\n&8%2%)'
|
||||||
data_dumped: '[☂ 成功將 %2% 資料快照 %1% 儲存至:](#00fb9a) &7%3%'
|
data_dumped: '[☂ 已成功將 %1% 的玩家數據快照 %2% 儲存至:](#00fb9a) &7%3%'
|
||||||
list_footer: '\n%1%[頁面](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
list_footer: '\n%1%[頁數](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
list_previous_page_button: '[◀](white show_text=&7查看上一頁 run_command=%2% %1%) '
|
list_previous_page_button: '[◀](white show_text=&7查看上一頁 run_command=%2% %1%) '
|
||||||
list_next_page_button: ' [▶](white show_text=&7查看下一頁 run_command=%2% %1%)'
|
list_next_page_button: ' [▶](white show_text=&7查看下一頁 run_command=%2% %1%)'
|
||||||
list_page_jumpers: '(%1%)'
|
list_page_jumpers: '(%1%)'
|
||||||
@@ -35,29 +35,29 @@ locales:
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
save_cause_disconnect: 'disconnect'
|
save_cause_disconnect: '斷開連接'
|
||||||
save_cause_world_save: 'world save'
|
save_cause_world_save: '保存世界'
|
||||||
save_cause_death: 'death'
|
save_cause_death: '死亡'
|
||||||
save_cause_server_shutdown: 'server shutdown'
|
save_cause_server_shutdown: '伺服器關閉'
|
||||||
save_cause_inventory_command: 'inventory command'
|
save_cause_inventory_command: '背包指令'
|
||||||
save_cause_enderchest_command: 'enderchest command'
|
save_cause_enderchest_command: '終界箱指令'
|
||||||
save_cause_backup_restore: 'backup restore'
|
save_cause_backup_restore: '備份還原'
|
||||||
save_cause_api: 'API'
|
save_cause_api: 'API'
|
||||||
save_cause_mpdb_migration: 'MPDB migration'
|
save_cause_mpdb_migration: 'MPDB遷移'
|
||||||
save_cause_legacy_migration: 'legacy migration'
|
save_cause_legacy_migration: '舊版遷移'
|
||||||
save_cause_converted_from_v2: 'converted from v2'
|
save_cause_converted_from_v2: '從v2轉換'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| 您運行的是最新版本的 HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| 您運行的是最新版本的HuskSync(v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本:v%1%(當前版本:v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件.](#00fb9a)\n[⚠ 確保所有伺服器上的配置文件都是最新的!](#00fb9a)\n[需要重新啟動配置更改才能生效.](#00fb9a italic)'
|
||||||
system_status_header: '[HuskSync](#00fb9a bold) [| System status report:](#00fb9a)'
|
system_status_header: '[HuskSync](#00fb9a bold) [| 系統狀態報告:](#00fb9a)'
|
||||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&點擊建議 suggest_command=%1%)'
|
||||||
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家.](#ff7e5e)'
|
||||||
error_no_permission: '[錯誤:](#ff3300) [您沒有權限執行這個指令](#ff7e5e)'
|
error_no_permission: '[錯誤:](#ff3300) [您沒有權限執行這個指令](#ff7e5e)'
|
||||||
error_console_command_only: '[錯誤:](#ff3300) [該指令只能透過 控制台 執行](#ff7e5e)'
|
error_console_command_only: '[錯誤:](#ff3300) [該指令只能透過 控制台 執行](#ff7e5e)'
|
||||||
error_in_game_command_only: '[錯誤:](#ff3300) [該指令只能在遊戲內執行](#ff7e5e)'
|
error_in_game_command_only: '錯誤: 該指令只能在遊戲內執行.'
|
||||||
error_no_data_to_display: '[錯誤:](#ff3300) [找不到任何可顯示的用戶資訊.](#ff7e5e)'
|
error_no_data_to_display: '[錯誤:](#ff3300) [找不到任何可顯示的玩家數據.](#ff7e5e)'
|
||||||
error_invalid_version_uuid: '[錯誤:](#ff3300) [找不到正確的 Version UUID.](#ff7e5e)'
|
error_invalid_version_uuid: '[錯誤:](#ff3300) [找不到該版本UUID的任何玩家數據.](#ff7e5e)'
|
||||||
husksync_command_description: 'Manage the HuskSync plugin'
|
husksync_command_description: '管理HuskSync插件'
|
||||||
userdata_command_description: 'View, manage & restore player userdata'
|
userdata_command_description: '查看、管理和恢復玩家用户數據'
|
||||||
inventory_command_description: 'View & edit a player''s inventory'
|
inventory_command_description: '查看和編輯玩家的背包'
|
||||||
enderchest_command_description: 'View & edit a player''s Ender Chest'
|
enderchest_command_description: '查看和編輯玩家的終界箱'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ This will walk you through installing HuskSync on your network of Spigot servers
|
|||||||
## Requirements
|
## Requirements
|
||||||
> **Note:** If the plugin fails to load, please check that you are not running an [incompatible version combination](Unsupported-Versions)
|
> **Note:** If the plugin fails to load, please check that you are not running an [incompatible version combination](Unsupported-Versions)
|
||||||
|
|
||||||
* A MySQL Database (v8.0+)
|
* A MySQL Database (v8.0+) (or MongoDB Database)
|
||||||
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
||||||
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.17.1+, running Java 17+)
|
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.17.1+, running Java 17+)
|
||||||
|
|
||||||
@@ -15,11 +15,19 @@ This will walk you through installing HuskSync on your network of Spigot servers
|
|||||||
- Start, then stop every server to let HuskSync generate the [[config file]].
|
- Start, then stop every server to let HuskSync generate the [[config file]].
|
||||||
- HuskSync will throw an error in the console and disable itself as it is unable to connect to the database. You haven't set the credentials yet, so this is expected.
|
- HuskSync will throw an error in the console and disable itself as it is unable to connect to the database. You haven't set the credentials yet, so this is expected.
|
||||||
- Advanced users: If you'd prefer, you can just create one config.yml file and create symbolic links in each `/plugins/HuskSync/` folder to it to make updating it easier.
|
- Advanced users: If you'd prefer, you can just create one config.yml file and create symbolic links in each `/plugins/HuskSync/` folder to it to make updating it easier.
|
||||||
### 3. Enter MySQL & Redis database credentials
|
### 3. Enter Mysql & Redis database credentials
|
||||||
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
||||||
- Under `credentials` in the `database` section, enter the credentials of your MySQL Database. You shouldn't touch the `connection_pool` properties.
|
- Under `credentials` in the `database` section, enter the credentials of your MySQL Database. You shouldn't touch the `connection_pool` properties.
|
||||||
- Under `credentials` in the `redis` section, enter the credentials of your Redis Database. If your Redis server doesn't have a password, leave the password blank as it is.
|
- Under `credentials` in the `redis` section, enter the credentials of your Redis Database. If your Redis server doesn't have a password, leave the password blank as it is.
|
||||||
- Unless you want to have multiple clusters of servers within your network, each with separate user data, you should not change the value of `cluster_id`.
|
- Unless you want to have multiple clusters of servers within your network, each with separate user data, you should not change the value of `cluster_id`.
|
||||||
|
<details>
|
||||||
|
<summary><b>For MongoDB Users</b></summary>
|
||||||
|
|
||||||
|
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
||||||
|
- Under `credentials` in the `database` section, enter the credentials of your MongoDB Database. You shouldn't touch the `connection_pool` properties.
|
||||||
|
- Be sure to fill in the `mongo_auth_db` field with the database that the username and password is authenticated in. (In most cases this will {and should be} be the same database as the database your trying to connect to.)
|
||||||
|
</details>
|
||||||
|
|
||||||
### 4. Set server names in server.yml files
|
### 4. Set server names in server.yml files
|
||||||
- Navigate to the HuskSync server name file on each server (`~/plugins/HuskSync/server.yml`)
|
- Navigate to the HuskSync server name file on each server (`~/plugins/HuskSync/server.yml`)
|
||||||
- Set the `name:` of the server in this file to the ID of this server as defined in the config of your proxy (e.g., if this is the "hub" server you access with `/server hub`, put `'hub'` here)
|
- Set the `name:` of the server in this file to the ID of this server as defined in the config of your proxy (e.g., if this is the "hub" server you access with `/server hub`, put `'hub'` here)
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ 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.4
|
||||||
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
|
||||||
|
|
||||||
jedis_version=5.1.0
|
jedis_version=5.1.0
|
||||||
mysql_driver_version=8.3.0
|
mysql_driver_version=8.3.0
|
||||||
mariadb_driver_version=3.3.2
|
mariadb_driver_version=3.3.2
|
||||||
|
mongodb_driver_version=3.12.14
|
||||||
snappy_version=1.1.10.5
|
snappy_version=1.1.10.5
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ shadowJar {
|
|||||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'de.exlll', 'net.william278.huskclaims.libraries'
|
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ libraries:
|
|||||||
- 'redis.clients:jedis:${jedis_version}'
|
- 'redis.clients:jedis:${jedis_version}'
|
||||||
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
||||||
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
||||||
|
- 'org.mongodb:mongodb-driver:${mongodb_driver_version}'
|
||||||
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||||
Reference in New Issue
Block a user