mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-29 03:29:12 +00:00
Basic bukkit implementation
This commit is contained in:
@@ -2,16 +2,16 @@ package net.william278.husksync;
|
||||
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.listener.EventListener;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface HuskSync {
|
||||
|
||||
@@ -19,8 +19,6 @@ public interface HuskSync {
|
||||
|
||||
@NotNull Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid);
|
||||
|
||||
@NotNull EventListener getEventListener();
|
||||
|
||||
@NotNull Database getDatabase();
|
||||
|
||||
@NotNull RedisManager getRedisManager();
|
||||
@@ -29,10 +27,10 @@ public interface HuskSync {
|
||||
|
||||
@NotNull Locales getLocales();
|
||||
|
||||
@NotNull Logger getLogger();
|
||||
@NotNull Logger getLoggingAdapter();
|
||||
|
||||
@NotNull String getVersion();
|
||||
|
||||
void reload();
|
||||
CompletableFuture<Boolean> reload();
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
final UpdateChecker updateChecker = new UpdateChecker(plugin.getVersion(), plugin.getLogger());
|
||||
final UpdateChecker updateChecker = new UpdateChecker(plugin.getVersion(), plugin.getLoggingAdapter());
|
||||
updateChecker.fetchLatestVersion().thenAccept(latestVersion -> {
|
||||
if (updateChecker.isUpdateAvailable(latestVersion)) {
|
||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| A new update is available:](#00fb9a) [HuskSync " + updateChecker.fetchLatestVersion() + "](#00fb9a bold)" +
|
||||
@@ -56,22 +56,22 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
@Override
|
||||
public void onConsoleExecute(@NotNull String[] args) {
|
||||
if (args.length < 1) {
|
||||
plugin.getLogger().log(Level.INFO, "Console usage: /husksync <update|info|reload|migrate>");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: /husksync <update|info|reload|migrate>");
|
||||
return;
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "update", "version" -> new UpdateChecker(plugin.getVersion(), plugin.getLogger()).logToConsole();
|
||||
case "info", "about" -> plugin.getLogger().log(Level.INFO, plugin.getLocales().stripMineDown(
|
||||
case "update", "version" -> new UpdateChecker(plugin.getVersion(), plugin.getLoggingAdapter()).logToConsole();
|
||||
case "info", "about" -> plugin.getLoggingAdapter().log(Level.INFO, plugin.getLocales().stripMineDown(
|
||||
Locales.PLUGIN_INFORMATION.replace("%version%", plugin.getVersion())));
|
||||
case "reload" -> {
|
||||
plugin.reload();
|
||||
plugin.getLogger().log(Level.INFO, "Reloaded config & message files.");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files.");
|
||||
}
|
||||
case "migrate" -> {
|
||||
//todo - MPDB migrator
|
||||
}
|
||||
default ->
|
||||
plugin.getLogger().log(Level.INFO, "Invalid syntax. Console usage: /husksync <update|info|reload|migrate>");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid syntax. Console usage: /husksync <update|info|reload|migrate>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ public class Settings {
|
||||
LANGUAGE("language", OptionType.STRING, "en-gb"),
|
||||
CHECK_FOR_UPDATES("check_for_updates", OptionType.BOOLEAN, true),
|
||||
|
||||
CLUSTER_ID("cluster_id", OptionType.STRING, ""), //todo implement this
|
||||
CLUSTER_ID("cluster_id", OptionType.STRING, ""),
|
||||
|
||||
DATABASE_HOST("database.credentials.host", OptionType.STRING, "localhost"),
|
||||
DATABASE_PORT("database.credentials.port", OptionType.INTEGER, 3306),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
@@ -25,4 +26,8 @@ public class AdvancementData {
|
||||
public AdvancementData() {
|
||||
}
|
||||
|
||||
public AdvancementData(@NotNull String key, @NotNull Map<String, Date> awardedCriteria) {
|
||||
this.key = key;
|
||||
this.completedCriteria = awardedCriteria;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,21 @@ package net.william278.husksync.data;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Store's a user's persistent data container, holding a map of plugin-set persistent values
|
||||
*/
|
||||
public class PersistentDataContainerData {
|
||||
|
||||
/**
|
||||
* A base64 string of platform-serialized PersistentDataContainer data
|
||||
* Map of namespaced key strings to a byte array representing the persistent data
|
||||
*/
|
||||
@SerializedName("serialized_persistent_data_container")
|
||||
public String serializedPersistentDataContainer;
|
||||
@SerializedName("persistent_data_map")
|
||||
public Map<String, Byte[]> persistentDataMap;
|
||||
|
||||
public PersistentDataContainerData(@NotNull final String serializedPersistentDataContainer) {
|
||||
this.serializedPersistentDataContainer = serializedPersistentDataContainer;
|
||||
public PersistentDataContainerData(@NotNull final Map<String, Byte[]> persistentDataMap) {
|
||||
this.persistentDataMap = persistentDataMap;
|
||||
}
|
||||
|
||||
public PersistentDataContainerData() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Stores information about a player's statistics
|
||||
@@ -14,30 +15,30 @@ public class StatisticsData {
|
||||
* Map of untyped statistic names to their values
|
||||
*/
|
||||
@SerializedName("untyped_statistics")
|
||||
public HashMap<String, Integer> untypedStatistic;
|
||||
public Map<String, Integer> untypedStatistic;
|
||||
|
||||
/**
|
||||
* Map of block type statistics to a map of material types to values
|
||||
*/
|
||||
@SerializedName("block_statistics")
|
||||
public HashMap<String, HashMap<String, Integer>> blockStatistics;
|
||||
public Map<String, Map<String, Integer>> blockStatistics;
|
||||
|
||||
/**
|
||||
* Map of item type statistics to a map of material types to values
|
||||
*/
|
||||
@SerializedName("item_statistics")
|
||||
public HashMap<String, HashMap<String, Integer>> itemStatistics;
|
||||
public Map<String, Map<String, Integer>> itemStatistics;
|
||||
|
||||
/**
|
||||
* Map of entity type statistics to a map of entity types to values
|
||||
*/
|
||||
@SerializedName("entity_statistics")
|
||||
public HashMap<String, HashMap<String, Integer>> entityStatistics;
|
||||
public Map<String, Map<String, Integer>> entityStatistics;
|
||||
|
||||
public StatisticsData(@NotNull HashMap<String, Integer> untypedStatistic,
|
||||
@NotNull HashMap<String, HashMap<String, Integer>> blockStatistics,
|
||||
@NotNull HashMap<String, HashMap<String, Integer>> itemStatistics,
|
||||
@NotNull HashMap<String, HashMap<String, Integer>> entityStatistics) {
|
||||
public StatisticsData(@NotNull Map<String, Integer> untypedStatistic,
|
||||
@NotNull Map<String, Map<String, Integer>> blockStatistics,
|
||||
@NotNull Map<String, Map<String, Integer>> itemStatistics,
|
||||
@NotNull Map<String, Map<String, Integer>> entityStatistics) {
|
||||
this.untypedStatistic = untypedStatistic;
|
||||
this.blockStatistics = blockStatistics;
|
||||
this.itemStatistics = itemStatistics;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StatusData {
|
||||
public float expProgress;
|
||||
|
||||
/**
|
||||
* The player's game mode string (one of "survival", "creative", "adventure", "spectator")
|
||||
* The player's game mode string (one of "SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR")
|
||||
*/
|
||||
@SerializedName("game_mode")
|
||||
public String gameMode;
|
||||
|
||||
@@ -5,24 +5,12 @@ import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
* Stores data about a user
|
||||
*/
|
||||
public class UserData implements Comparable<UserData> {
|
||||
|
||||
/**
|
||||
* The unique identifier for this user data version
|
||||
*/
|
||||
protected UUID dataUuidVersion;
|
||||
|
||||
/**
|
||||
* An epoch milliseconds timestamp of when this data was created
|
||||
*/
|
||||
protected long creationTimestamp;
|
||||
public class UserData {
|
||||
|
||||
/**
|
||||
* Stores the user's status data, including health, food, etc.
|
||||
@@ -52,7 +40,7 @@ public class UserData implements Comparable<UserData> {
|
||||
* Stores the set of this user's advancements
|
||||
*/
|
||||
@SerializedName("advancements")
|
||||
protected HashSet<AdvancementData> advancementData;
|
||||
protected List<AdvancementData> advancementData;
|
||||
|
||||
/**
|
||||
* Stores the user's set of statistics
|
||||
@@ -74,10 +62,8 @@ public class UserData implements Comparable<UserData> {
|
||||
|
||||
public UserData(@NotNull StatusData statusData, @NotNull InventoryData inventoryData,
|
||||
@NotNull InventoryData enderChestData, @NotNull PotionEffectData potionEffectData,
|
||||
@NotNull HashSet<AdvancementData> advancementData, @NotNull StatisticsData statisticData,
|
||||
@NotNull List<AdvancementData> advancementData, @NotNull StatisticsData statisticData,
|
||||
@NotNull LocationData locationData, @NotNull PersistentDataContainerData persistentDataContainerData) {
|
||||
this.dataUuidVersion = UUID.randomUUID();
|
||||
this.creationTimestamp = Instant.now().toEpochMilli();
|
||||
this.statusData = statusData;
|
||||
this.inventoryData = inventoryData;
|
||||
this.enderChestData = enderChestData;
|
||||
@@ -91,40 +77,6 @@ public class UserData implements Comparable<UserData> {
|
||||
protected UserData() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare UserData by creation timestamp
|
||||
*
|
||||
* @param other the other UserData to be compared
|
||||
* @return the comparison result; the more recent UserData is greater than the less recent UserData
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(@NotNull UserData other) {
|
||||
return Long.compare(this.creationTimestamp, other.creationTimestamp);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static UserData fromJson(String json) throws JsonSyntaxException {
|
||||
return new GsonBuilder().create().fromJson(json, UserData.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toJson() {
|
||||
return new GsonBuilder().create().toJson(this);
|
||||
}
|
||||
|
||||
public void setMetadata(@NotNull UUID dataUuidVersion, long creationTimestamp) {
|
||||
this.dataUuidVersion = dataUuidVersion;
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
}
|
||||
|
||||
public UUID getDataUuidVersion() {
|
||||
return dataUuidVersion;
|
||||
}
|
||||
|
||||
public long getCreationTimestamp() {
|
||||
return creationTimestamp;
|
||||
}
|
||||
|
||||
public StatusData getStatusData() {
|
||||
return statusData;
|
||||
}
|
||||
@@ -141,7 +93,7 @@ public class UserData implements Comparable<UserData> {
|
||||
return potionEffectData;
|
||||
}
|
||||
|
||||
public HashSet<AdvancementData> getAdvancementData() {
|
||||
public List<AdvancementData> getAdvancementData() {
|
||||
return advancementData;
|
||||
}
|
||||
|
||||
@@ -156,4 +108,15 @@ public class UserData implements Comparable<UserData> {
|
||||
public PersistentDataContainerData getPersistentDataContainerData() {
|
||||
return persistentDataContainerData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static UserData fromJson(String json) throws JsonSyntaxException {
|
||||
return new GsonBuilder().create().fromJson(json, UserData.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toJson() {
|
||||
return new GsonBuilder().create().toJson(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a uniquely versioned and timestamped snapshot of a user's data
|
||||
*
|
||||
* @param versionUUID The unique identifier for this user data version
|
||||
* @param versionTimestamp An epoch milliseconds timestamp of when this data was created
|
||||
* @param userData The {@link UserData} that has been versioned
|
||||
*/
|
||||
public record VersionedUserData(@NotNull UUID versionUUID, @NotNull Date versionTimestamp,
|
||||
@NotNull UserData userData) implements Comparable<VersionedUserData> {
|
||||
|
||||
public VersionedUserData(@NotNull final UUID versionUUID, @NotNull final Date versionTimestamp,
|
||||
@NotNull UserData userData) {
|
||||
this.versionUUID = versionUUID;
|
||||
this.versionTimestamp = versionTimestamp;
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
public static VersionedUserData version(@NotNull UserData userData) {
|
||||
return new VersionedUserData(UUID.randomUUID(), new Date(), userData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare UserData by creation timestamp
|
||||
*
|
||||
* @param other the other UserData to be compared
|
||||
* @return the comparison result; the more recent UserData is greater than the less recent UserData
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(@NotNull VersionedUserData other) {
|
||||
return Long.compare(this.versionTimestamp.getTime(), other.versionTimestamp.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.william278.husksync.database;
|
||||
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.VersionedUserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
@@ -71,10 +72,8 @@ public abstract class Database {
|
||||
* @throws IOException if the resource could not be read
|
||||
*/
|
||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
||||
return formatStatementTables(
|
||||
new String(resourceReader.getResource(schemaFileName)
|
||||
.readAllBytes(), StandardCharsets.UTF_8))
|
||||
.split(";");
|
||||
return formatStatementTables(new String(resourceReader.getResource(schemaFileName)
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,9 +90,9 @@ public abstract class Database {
|
||||
/**
|
||||
* Initialize the database and ensure tables are present; create tables if they do not exist.
|
||||
*
|
||||
* @return A future returning void when complete
|
||||
* @return A future returning boolean - if the connection could be established.
|
||||
*/
|
||||
public abstract CompletableFuture<Void> initialize();
|
||||
public abstract boolean initialize();
|
||||
|
||||
/**
|
||||
* Ensure a {@link User} has an entry in the database and that their username is up-to-date
|
||||
@@ -120,20 +119,20 @@ public abstract class Database {
|
||||
public abstract CompletableFuture<Optional<User>> getUserByName(@NotNull String username);
|
||||
|
||||
/**
|
||||
* Get the current user data for a given user, if it exists.
|
||||
* Get the current uniquely versioned user data for a given user, if it exists.
|
||||
*
|
||||
* @param user the user to get data for
|
||||
* @return an optional containing the user data, if it exists, or an empty optional if it does not
|
||||
* @return an optional containing the {@link VersionedUserData}, if it exists, or an empty optional if it does not
|
||||
*/
|
||||
public abstract CompletableFuture<Optional<UserData>> getCurrentUserData(@NotNull User user);
|
||||
public abstract CompletableFuture<Optional<VersionedUserData>> getCurrentUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Get all UserData entries for a user from the database.
|
||||
* Get all {@link VersionedUserData} entries for a user from the database.
|
||||
*
|
||||
* @param user The user to get data for
|
||||
* @return A future returning a list of a user's data
|
||||
* @return A future returning a list of a user's {@link VersionedUserData} entries
|
||||
*/
|
||||
public abstract CompletableFuture<List<UserData>> getUserData(@NotNull User user);
|
||||
public abstract CompletableFuture<List<VersionedUserData>> getUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Prune user data records for a given user to the maximum value as configured
|
||||
@@ -148,9 +147,14 @@ public abstract class Database {
|
||||
* 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 userData The data to add
|
||||
* @param userData The uniquely versioned data to add as a {@link VersionedUserData}
|
||||
* @return A future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData);
|
||||
public abstract CompletableFuture<Void> setUserData(@NotNull User user, @NotNull VersionedUserData userData);
|
||||
|
||||
/**
|
||||
* Close the database connection
|
||||
*/
|
||||
public abstract void close();
|
||||
|
||||
}
|
||||
|
||||
@@ -3,19 +3,24 @@ package net.william278.husksync.database;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.VersionedUserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.xerial.snappy.Snappy;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class MySqlDatabase extends Database {
|
||||
|
||||
/**
|
||||
* MySQL server hostname
|
||||
*/
|
||||
@@ -40,9 +45,12 @@ public class MySqlDatabase extends Database {
|
||||
private final int hikariKeepAliveTime;
|
||||
private final int hikariConnectionTimeOut;
|
||||
|
||||
private static final String DATA_POOL_NAME = "HuskHomesHikariPool";
|
||||
private static final String DATA_POOL_NAME = "HuskSyncHikariPool";
|
||||
|
||||
private HikariDataSource dataSource;
|
||||
/**
|
||||
* The Hikari data source - a pool of database connections that can be fetched on-demand
|
||||
*/
|
||||
private HikariDataSource connectionPool;
|
||||
|
||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger) {
|
||||
super(settings.getStringValue(Settings.ConfigOption.DATABASE_PLAYERS_TABLE_NAME),
|
||||
@@ -69,31 +77,31 @@ public class MySqlDatabase extends Database {
|
||||
* @throws SQLException if the connection fails for some reason
|
||||
*/
|
||||
private Connection getConnection() throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> initialize() {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
public boolean initialize() {
|
||||
try {
|
||||
// Create jdbc driver connection url
|
||||
final String jdbcUrl = "jdbc:mysql://" + mySqlHost + ":" + mySqlPort + "/" + mySqlDatabaseName + mySqlConnectionParameters;
|
||||
dataSource = new HikariDataSource();
|
||||
dataSource.setJdbcUrl(jdbcUrl);
|
||||
connectionPool = new HikariDataSource();
|
||||
connectionPool.setJdbcUrl(jdbcUrl);
|
||||
|
||||
// Authenticate
|
||||
dataSource.setUsername(mySqlUsername);
|
||||
dataSource.setPassword(mySqlPassword);
|
||||
connectionPool.setUsername(mySqlUsername);
|
||||
connectionPool.setPassword(mySqlPassword);
|
||||
|
||||
// Set various additional parameters
|
||||
dataSource.setMaximumPoolSize(hikariMaximumPoolSize);
|
||||
dataSource.setMinimumIdle(hikariMinimumIdle);
|
||||
dataSource.setMaxLifetime(hikariMaximumLifetime);
|
||||
dataSource.setKeepaliveTime(hikariKeepAliveTime);
|
||||
dataSource.setConnectionTimeout(hikariConnectionTimeOut);
|
||||
dataSource.setPoolName(DATA_POOL_NAME);
|
||||
connectionPool.setMaximumPoolSize(hikariMaximumPoolSize);
|
||||
connectionPool.setMinimumIdle(hikariMinimumIdle);
|
||||
connectionPool.setMaxLifetime(hikariMaximumLifetime);
|
||||
connectionPool.setKeepaliveTime(hikariKeepAliveTime);
|
||||
connectionPool.setConnectionTimeout(hikariConnectionTimeOut);
|
||||
connectionPool.setPoolName(DATA_POOL_NAME);
|
||||
|
||||
// Prepare database schema; make tables if they don't exist
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
try (Connection connection = connectionPool.getConnection()) {
|
||||
// Load database schema CREATE statements from schema file
|
||||
final String[] databaseSchema = getSchemaStatements("database/mysql_schema.sql");
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
@@ -101,10 +109,14 @@ public class MySqlDatabase extends Database {
|
||||
statement.execute(tableCreationStatement);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (SQLException | IOException e) {
|
||||
getLogger().log(Level.SEVERE, "An error occurred creating tables on the MySQL database: ", e);
|
||||
getLogger().log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,7 +206,7 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<UserData>> getCurrentUserData(@NotNull User user) {
|
||||
public CompletableFuture<Optional<VersionedUserData>> getCurrentUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
@@ -206,13 +218,17 @@ public class MySqlDatabase extends Database {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
final UserData data = UserData.fromJson(resultSet.getString("data"));
|
||||
data.setMetadata(UUID.fromString(resultSet.getString("version_uuid")),
|
||||
resultSet.getTimestamp("timestamp").toInstant().toEpochMilli());
|
||||
return Optional.of(data);
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] compressedDataJson = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
return Optional.of(new VersionedUserData(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
UserData.fromJson(new String(Snappy.uncompress(compressedDataJson),
|
||||
StandardCharsets.UTF_8))));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
} catch (SQLException | IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
@@ -220,9 +236,9 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<UserData>> getUserData(@NotNull User user) {
|
||||
public CompletableFuture<List<VersionedUserData>> getUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final ArrayList<UserData> retrievedData = new ArrayList<>();
|
||||
final List<VersionedUserData> retrievedData = new ArrayList<>();
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `version_uuid`, `timestamp`, `data`
|
||||
@@ -232,14 +248,19 @@ public class MySqlDatabase extends Database {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
final UserData data = UserData.fromJson(resultSet.getString("data"));
|
||||
data.setMetadata(UUID.fromString(resultSet.getString("version_uuid")),
|
||||
resultSet.getTimestamp("timestamp").toInstant().toEpochMilli());
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] compressedDataJson = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
final VersionedUserData data = new VersionedUserData(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
UserData.fromJson(new String(Snappy.uncompress(compressedDataJson),
|
||||
StandardCharsets.UTF_8)));
|
||||
retrievedData.add(data);
|
||||
}
|
||||
return retrievedData;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
} catch (SQLException | IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return retrievedData;
|
||||
@@ -256,7 +277,7 @@ public class MySqlDatabase extends Database {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
DELETE FROM `%data_table%`
|
||||
WHERE `version_uuid`=?"""))) {
|
||||
statement.setString(1, dataToDelete.getDataUuidVersion().toString());
|
||||
statement.setString(1, dataToDelete.versionUUID().toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -268,7 +289,7 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData) {
|
||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull VersionedUserData userData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
@@ -276,14 +297,25 @@ public class MySqlDatabase extends Database {
|
||||
(`player_uuid`,`version_uuid`,`timestamp`,`data`)
|
||||
VALUES (?,?,?,?);"""))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.setString(2, userData.getDataUuidVersion().toString());
|
||||
statement.setTimestamp(3, Timestamp.from(Instant.ofEpochMilli(userData.getCreationTimestamp())));
|
||||
statement.setString(4, userData.toJson());
|
||||
statement.setString(2, userData.versionUUID().toString());
|
||||
statement.setTimestamp(3, Timestamp.from(userData.versionTimestamp().toInstant()));
|
||||
statement.setBlob(4, new ByteArrayInputStream(Snappy
|
||||
.compress(userData.userData().toJson().getBytes(StandardCharsets.UTF_8))));
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
} catch (SQLException | IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||
}
|
||||
}).thenRunAsync(() -> pruneUserDataRecords(user).join());
|
||||
})/*.thenRunAsync(() -> pruneUserDataRecords(user).join())*/;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (connectionPool != null) {
|
||||
if (!connectionPool.isClosed()) {
|
||||
connectionPool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,12 +12,25 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class EventListener {
|
||||
|
||||
/**
|
||||
* The plugin instance
|
||||
*/
|
||||
private final HuskSync huskSync;
|
||||
|
||||
/**
|
||||
* Set of UUIDs current awaiting item synchronization. Events will be cancelled for these users
|
||||
*/
|
||||
private final HashSet<UUID> usersAwaitingSync;
|
||||
|
||||
/**
|
||||
* Whether the plugin is currently being disabled
|
||||
*/
|
||||
private boolean disabling;
|
||||
|
||||
protected EventListener(@NotNull HuskSync huskSync) {
|
||||
this.huskSync = huskSync;
|
||||
this.usersAwaitingSync = new HashSet<>();
|
||||
this.disabling = false;
|
||||
}
|
||||
|
||||
public final void handlePlayerJoin(@NotNull OnlineUser user) {
|
||||
@@ -27,7 +40,7 @@ public class EventListener {
|
||||
userData -> user.setData(userData, huskSync.getSettings()).join(),
|
||||
() -> huskSync.getDatabase().getCurrentUserData(user).thenAccept(
|
||||
databaseUserData -> databaseUserData.ifPresent(
|
||||
data -> user.setData(data, huskSync.getSettings()).join())).join())).thenRunAsync(
|
||||
data -> user.setData(data.userData(), huskSync.getSettings()).join())).join())).thenRunAsync(
|
||||
() -> {
|
||||
huskSync.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar);
|
||||
usersAwaitingSync.remove(user.uuid);
|
||||
@@ -36,16 +49,35 @@ public class EventListener {
|
||||
}
|
||||
|
||||
public final void handlePlayerQuit(@NotNull OnlineUser user) {
|
||||
user.getUserData().thenAccept(userData -> huskSync.getRedisManager()
|
||||
.setPlayerData(user, userData, RedisManager.RedisKeyType.SERVER_CHANGE).thenRun(
|
||||
() -> huskSync.getDatabase().setUserData(user, userData).join()));
|
||||
if (disabling) {
|
||||
return;
|
||||
}
|
||||
user.getUserData().thenAccept(userData -> {
|
||||
System.out.println(userData.userData().toJson());
|
||||
huskSync.getRedisManager()
|
||||
.setUserData(user, userData.userData(), RedisManager.RedisKeyType.SERVER_CHANGE).thenRun(
|
||||
() -> huskSync.getDatabase().setUserData(user, userData).join());
|
||||
});
|
||||
}
|
||||
|
||||
public final void handleWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
||||
if (disabling) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> usersInWorld.forEach(user ->
|
||||
huskSync.getDatabase().setUserData(user, user.getUserData().join()).join()));
|
||||
}
|
||||
|
||||
public final void handlePluginDisable() {
|
||||
disabling = true;
|
||||
|
||||
huskSync.getOnlineUsers().stream().filter(user -> !usersAwaitingSync.contains(user.uuid)).forEach(user ->
|
||||
huskSync.getDatabase().setUserData(user, user.getUserData().join()).join());
|
||||
|
||||
huskSync.getDatabase().close();
|
||||
huskSync.getRedisManager().close();
|
||||
}
|
||||
|
||||
public final boolean cancelPlayerEvent(@NotNull OnlineUser user) {
|
||||
return usersAwaitingSync.contains(user.uuid);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -25,6 +25,22 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<StatusData> getStatus();
|
||||
|
||||
/**
|
||||
* Set the player's {@link StatusData}
|
||||
*
|
||||
* @param statusData the player's {@link StatusData}
|
||||
* @param setHealth whether to set the player's health
|
||||
* @param setMaxHealth whether to set the player's max health
|
||||
* @param setHunger whether to set the player's hunger
|
||||
* @param setExperience whether to set the player's experience
|
||||
* @param setGameMode whether to set the player's game mode
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
||||
final boolean setHealth, final boolean setMaxHealth,
|
||||
final boolean setHunger, final boolean setExperience,
|
||||
final boolean setGameMode, boolean setFlying);
|
||||
|
||||
/**
|
||||
* Get the player's inventory {@link InventoryData} contents
|
||||
*
|
||||
@@ -32,6 +48,14 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<InventoryData> getInventory();
|
||||
|
||||
/**
|
||||
* Set the player's {@link InventoryData}
|
||||
*
|
||||
* @param inventoryData The player's {@link InventoryData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setInventory(@NotNull InventoryData inventoryData);
|
||||
|
||||
/**
|
||||
* Get the player's ender chest {@link InventoryData} contents
|
||||
*
|
||||
@@ -39,6 +63,15 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<InventoryData> getEnderChest();
|
||||
|
||||
/**
|
||||
* Set the player's {@link InventoryData}
|
||||
*
|
||||
* @param enderChestData The player's {@link InventoryData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setEnderChest(@NotNull InventoryData enderChestData);
|
||||
|
||||
|
||||
/**
|
||||
* Get the player's {@link PotionEffectData}
|
||||
*
|
||||
@@ -46,12 +79,28 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<PotionEffectData> getPotionEffects();
|
||||
|
||||
/**
|
||||
* Set the player's {@link PotionEffectData}
|
||||
*
|
||||
* @param potionEffectData The player's {@link PotionEffectData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setPotionEffects(@NotNull PotionEffectData potionEffectData);
|
||||
|
||||
/**
|
||||
* Get the player's set of {@link AdvancementData}
|
||||
*
|
||||
* @return the player's set of {@link AdvancementData}
|
||||
*/
|
||||
public abstract CompletableFuture<HashSet<AdvancementData>> getAdvancements();
|
||||
public abstract CompletableFuture<List<AdvancementData>> getAdvancements();
|
||||
|
||||
/**
|
||||
* Set the player's {@link AdvancementData}
|
||||
*
|
||||
* @param advancementData List of the player's {@link AdvancementData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData);
|
||||
|
||||
/**
|
||||
* Get the player's {@link StatisticsData}
|
||||
@@ -60,6 +109,14 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<StatisticsData> getStatistics();
|
||||
|
||||
/**
|
||||
* Set the player's {@link StatisticsData}
|
||||
*
|
||||
* @param statisticsData The player's {@link StatisticsData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData);
|
||||
|
||||
/**
|
||||
* Get the player's {@link LocationData}
|
||||
*
|
||||
@@ -67,6 +124,14 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<LocationData> getLocation();
|
||||
|
||||
/**
|
||||
* Set the player's {@link LocationData}
|
||||
*
|
||||
* @param locationData the player's {@link LocationData}
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setLocation(@NotNull LocationData locationData);
|
||||
|
||||
/**
|
||||
* Get the player's {@link PersistentDataContainerData}
|
||||
*
|
||||
@@ -74,6 +139,14 @@ public abstract class OnlineUser extends User {
|
||||
*/
|
||||
public abstract CompletableFuture<PersistentDataContainerData> getPersistentDataContainer();
|
||||
|
||||
/**
|
||||
* Set the player's {@link PersistentDataContainerData}
|
||||
*
|
||||
* @param persistentDataContainerData The player's {@link PersistentDataContainerData} to set
|
||||
* @return A future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData);
|
||||
|
||||
/**
|
||||
* Set {@link UserData} to a player
|
||||
*
|
||||
@@ -81,7 +154,37 @@ public abstract class OnlineUser extends User {
|
||||
* @param settings Plugin settings, for determining what needs setting
|
||||
* @return a future that will be completed when done
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setData(@NotNull UserData data, @NotNull Settings settings);
|
||||
public final CompletableFuture<Void> setData(@NotNull UserData data, @NotNull Settings settings) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
|
||||
setInventory(data.getInventoryData()).join();
|
||||
}
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
|
||||
setEnderChest(data.getEnderChestData()).join();
|
||||
}
|
||||
setStatus(data.getStatusData(), settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HEALTH),
|
||||
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_MAX_HEALTH),
|
||||
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HUNGER),
|
||||
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_EXPERIENCE),
|
||||
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_GAME_MODE),
|
||||
settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)).join();
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
|
||||
setPotionEffects(data.getPotionEffectData()).join();
|
||||
}
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
|
||||
setAdvancements(data.getAdvancementData()).join();
|
||||
}
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
|
||||
setStatistics(data.getStatisticData()).join();
|
||||
}
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
|
||||
setPersistentDataContainer(data.getPersistentDataContainerData()).join();
|
||||
}
|
||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
|
||||
setLocation(data.getLocationData()).join();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a MineDown-formatted message to this player
|
||||
@@ -110,10 +213,11 @@ public abstract class OnlineUser extends User {
|
||||
*
|
||||
* @return the player's current {@link UserData}
|
||||
*/
|
||||
public final CompletableFuture<UserData> getUserData() {
|
||||
return CompletableFuture.supplyAsync(() -> new UserData(getStatus().join(), getInventory().join(),
|
||||
getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(),
|
||||
getStatistics().join(), getLocation().join(), getPersistentDataContainer().join()));
|
||||
public final CompletableFuture<VersionedUserData> getUserData() {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> VersionedUserData.version(new UserData(getStatus().join(), getInventory().join(),
|
||||
getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(),
|
||||
getStatistics().join(), getLocation().join(), getPersistentDataContainer().join())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,65 +4,130 @@ import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.xerial.snappy.Snappy;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Manages the connection to the Redis server, handling the caching of user data
|
||||
*/
|
||||
public class RedisManager {
|
||||
|
||||
private static final String KEY_NAMESPACE = "husksync:";
|
||||
private static String clusterId = "";
|
||||
private final JedisPool jedisPool;
|
||||
|
||||
private RedisManager(@NotNull Settings settings) {
|
||||
private final JedisPoolConfig jedisPoolConfig;
|
||||
|
||||
private final String redisHost;
|
||||
private final int redisPort;
|
||||
private final String redisPassword;
|
||||
private final boolean redisUseSsl;
|
||||
|
||||
private JedisPool jedisPool;
|
||||
|
||||
public RedisManager(@NotNull Settings settings) {
|
||||
clusterId = settings.getStringValue(Settings.ConfigOption.CLUSTER_ID);
|
||||
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||
jedisPoolConfig.setMaxIdle(0);
|
||||
jedisPoolConfig.setTestOnBorrow(true);
|
||||
jedisPoolConfig.setTestOnReturn(true);
|
||||
if (settings.getStringValue(Settings.ConfigOption.REDIS_PASSWORD).isBlank()) {
|
||||
jedisPool = new JedisPool(jedisPoolConfig,
|
||||
settings.getStringValue(Settings.ConfigOption.REDIS_HOST),
|
||||
settings.getIntegerValue(Settings.ConfigOption.REDIS_PORT),
|
||||
0,
|
||||
settings.getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL));
|
||||
} else {
|
||||
jedisPool = new JedisPool(jedisPoolConfig,
|
||||
settings.getStringValue(Settings.ConfigOption.REDIS_HOST),
|
||||
settings.getIntegerValue(Settings.ConfigOption.REDIS_PORT),
|
||||
0,
|
||||
settings.getStringValue(Settings.ConfigOption.REDIS_PASSWORD),
|
||||
settings.getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL));
|
||||
this.redisHost = settings.getStringValue(Settings.ConfigOption.REDIS_HOST);
|
||||
this.redisPort = settings.getIntegerValue(Settings.ConfigOption.REDIS_PORT);
|
||||
this.redisPassword = settings.getStringValue(Settings.ConfigOption.REDIS_PASSWORD);
|
||||
this.redisUseSsl = settings.getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL);
|
||||
|
||||
// Configure the jedis pool
|
||||
this.jedisPoolConfig = new JedisPoolConfig();
|
||||
this.jedisPoolConfig.setMaxIdle(0);
|
||||
this.jedisPoolConfig.setTestOnBorrow(true);
|
||||
this.jedisPoolConfig.setTestOnReturn(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the redis connection pool
|
||||
*
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public CompletableFuture<Boolean> initialize() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (redisPassword.isBlank()) {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
||||
} else {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisPassword, redisUseSsl);
|
||||
}
|
||||
try {
|
||||
jedisPool.getResource().ping();
|
||||
} catch (JedisException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a user's data to the Redis server
|
||||
*
|
||||
* @param user the user to set data for
|
||||
* @param userData the user's data to set
|
||||
* @param redisKeyType the type of key to set the data with. This determines the time to live for the data.
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull RedisKeyType redisKeyType) {
|
||||
try {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
// Set the user's data as a compressed byte array of the json using Snappy
|
||||
jedis.setex(getKey(redisKeyType, user.uuid), redisKeyType.timeToLive,
|
||||
Snappy.compress(userData.toJson().getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a user's data from the Redis server
|
||||
*
|
||||
* @param user The user to fetch data for
|
||||
* @param redisKeyType The type of key to fetch
|
||||
* @return The user's data, if it's present on the database. Otherwise, an empty optional.
|
||||
*/
|
||||
public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user,
|
||||
@NotNull RedisKeyType redisKeyType) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final byte[] compressedJson = jedis.get(getKey(redisKeyType, user.uuid));
|
||||
if (compressedJson == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// Use Snappy to decompress the json
|
||||
return Optional.of(UserData.fromJson(new String(Snappy.uncompress(compressedJson),
|
||||
StandardCharsets.UTF_8)));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (jedisPool != null) {
|
||||
if (!jedisPool.isClosed()) {
|
||||
jedisPool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> setPlayerData(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull RedisKeyType redisKeyType) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(redisKeyType.getKeyPrefix() + user.uuid.toString(),
|
||||
redisKeyType.timeToLive, userData.toJson());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<UserData>> getUserData(@NotNull User user, @NotNull RedisKeyType redisKeyType) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final String json = jedis.get(redisKeyType.getKeyPrefix() + user.uuid.toString());
|
||||
if (json == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(UserData.fromJson(json));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static CompletableFuture<RedisManager> initialize(@NotNull Settings settings) {
|
||||
return CompletableFuture.supplyAsync(() -> new RedisManager(settings));
|
||||
private static byte[] getKey(@NotNull RedisKeyType keyType, @NotNull UUID uuid) {
|
||||
return (keyType.getKeyPrefix() + ":" + uuid).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public enum RedisKeyType {
|
||||
@@ -77,7 +142,7 @@ public class RedisManager {
|
||||
|
||||
@NotNull
|
||||
public String getKeyPrefix() {
|
||||
return KEY_NAMESPACE.toLowerCase() + ":" + clusterId.toLowerCase() + ":" + name().toLowerCase() + ":";
|
||||
return KEY_NAMESPACE.toLowerCase() + ":" + clusterId.toLowerCase() + ":" + name().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user