mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2026-01-06 15:41:56 +00:00
User data pinning, version validation checks, fixes
This commit is contained in:
@@ -3,7 +3,7 @@ package net.william278.husksync.database;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.VersionedUserData;
|
||||
import net.william278.husksync.data.UserDataSnapshot;
|
||||
import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.User;
|
||||
@@ -157,40 +157,41 @@ public abstract class Database {
|
||||
* 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 {@link VersionedUserData}, if it exists, or an empty optional if it does not
|
||||
* @return an optional containing the {@link UserDataSnapshot}, if it exists, or an empty optional if it does not
|
||||
*/
|
||||
public abstract CompletableFuture<Optional<VersionedUserData>> getCurrentUserData(@NotNull User user);
|
||||
public abstract CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Get all {@link VersionedUserData} entries for a user from the database.
|
||||
* Get all {@link UserDataSnapshot} 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 {@link VersionedUserData} entries
|
||||
* @return A future returning a list of a user's {@link UserDataSnapshot} entries
|
||||
*/
|
||||
public abstract CompletableFuture<List<VersionedUserData>> getUserData(@NotNull User user);
|
||||
public abstract CompletableFuture<List<UserDataSnapshot>> getUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Gets a specific {@link VersionedUserData} entry for a user from the database, by its UUID.
|
||||
* Gets a specific {@link UserDataSnapshot} 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 VersionedUserData} entry to get
|
||||
* @return A future returning an optional containing the {@link VersionedUserData}, if it exists, or an empty optional if it does not
|
||||
* @param versionUuid The UUID of the {@link UserDataSnapshot} entry to get
|
||||
* @return A future returning an optional containing the {@link UserDataSnapshot}, if it exists, or an empty optional if it does not
|
||||
*/
|
||||
public abstract CompletableFuture<Optional<VersionedUserData>> getUserData(@NotNull User user, @NotNull UUID versionUuid);
|
||||
public abstract CompletableFuture<Optional<UserDataSnapshot>> getUserData(@NotNull User user, @NotNull UUID versionUuid);
|
||||
|
||||
/**
|
||||
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured
|
||||
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
||||
*
|
||||
* @param user The user to prune data for
|
||||
* @return A future returning void when complete
|
||||
* @implNote Data snapshots marked as {@code pinned} are exempt from rotation
|
||||
*/
|
||||
protected abstract CompletableFuture<Void> pruneUserData(@NotNull User user);
|
||||
protected abstract CompletableFuture<Void> rotateUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Deletes a specific {@link VersionedUserData} entry for a user from the database, by its UUID.
|
||||
* Deletes a specific {@link UserDataSnapshot} 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 VersionedUserData} entry to delete
|
||||
* @param versionUuid The UUID of the {@link UserDataSnapshot} entry to delete
|
||||
* @return A future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Boolean> deleteUserData(@NotNull User user, @NotNull UUID versionUuid);
|
||||
@@ -202,10 +203,30 @@ public abstract class Database {
|
||||
* @param user The user to add data for
|
||||
* @param userData The {@link UserData} to set. The implementation should version it with a random UUID and the current timestamp during insertion.
|
||||
* @return A future returning void when complete
|
||||
* @see VersionedUserData#version(UserData)
|
||||
* @see UserDataSnapshot#create(UserData)
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData, @NotNull DataSaveCause dataSaveCause);
|
||||
|
||||
/**
|
||||
* Pin a saved {@link UserDataSnapshot} by given version UUID, setting it's {@code pinned} state to {@code true}.
|
||||
*
|
||||
* @param user The user to pin the data for
|
||||
* @param versionUuid The UUID of the user's {@link UserDataSnapshot} entry to pin
|
||||
* @return A future returning a boolean; {@code true} if the operation completed successfully, {@code false} if it failed
|
||||
* @see UserDataSnapshot#pinned()
|
||||
*/
|
||||
public abstract CompletableFuture<Void> pinUserData(@NotNull User user, @NotNull UUID versionUuid);
|
||||
|
||||
/**
|
||||
* Unpin a saved {@link UserDataSnapshot} by given version UUID, setting it's {@code pinned} state to {@code false}.
|
||||
*
|
||||
* @param user The user to unpin the data for
|
||||
* @param versionUuid The UUID of the user's {@link UserDataSnapshot} entry to unpin
|
||||
* @return A future returning a boolean; {@code true} if the operation completed successfully, {@code false} if it failed
|
||||
* @see UserDataSnapshot#pinned()
|
||||
*/
|
||||
public abstract CompletableFuture<Void> unpinUserData(@NotNull User user, @NotNull UUID versionUuid);
|
||||
|
||||
/**
|
||||
* Wipes <b>all</b> {@link UserData} entries from the database.
|
||||
* <b>This should never be used</b>, except when preparing tables for migration.
|
||||
|
||||
@@ -13,8 +13,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -206,11 +206,11 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<VersionedUserData>> getCurrentUserData(@NotNull User user) {
|
||||
public CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `data`
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data`
|
||||
FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=?
|
||||
ORDER BY `timestamp` DESC
|
||||
@@ -221,10 +221,11 @@ public class MySqlDatabase extends Database {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
return Optional.of(new VersionedUserData(
|
||||
return Optional.of(new UserDataSnapshot(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray)));
|
||||
}
|
||||
}
|
||||
@@ -236,12 +237,12 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<VersionedUserData>> getUserData(@NotNull User user) {
|
||||
public CompletableFuture<List<UserDataSnapshot>> getUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final List<VersionedUserData> retrievedData = new ArrayList<>();
|
||||
final List<UserDataSnapshot> retrievedData = new ArrayList<>();
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `data`
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data`
|
||||
FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=?
|
||||
ORDER BY `timestamp` DESC;"""))) {
|
||||
@@ -251,10 +252,11 @@ public class MySqlDatabase extends Database {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
final VersionedUserData data = new VersionedUserData(
|
||||
final UserDataSnapshot data = new UserDataSnapshot(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray));
|
||||
retrievedData.add(data);
|
||||
}
|
||||
@@ -268,11 +270,11 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<VersionedUserData>> getUserData(@NotNull User user, @NotNull UUID versionUuid) {
|
||||
public CompletableFuture<Optional<UserDataSnapshot>> getUserData(@NotNull User user, @NotNull UUID versionUuid) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `data`
|
||||
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data`
|
||||
FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=? AND `version_uuid`=?
|
||||
ORDER BY `timestamp` DESC
|
||||
@@ -284,10 +286,11 @@ public class MySqlDatabase extends Database {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
return Optional.of(new VersionedUserData(
|
||||
return Optional.of(new UserDataSnapshot(
|
||||
UUID.fromString(resultSet.getString("version_uuid")),
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray)));
|
||||
}
|
||||
}
|
||||
@@ -299,16 +302,19 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> pruneUserData(@NotNull User user) {
|
||||
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(data -> {
|
||||
if (data.size() > maxUserDataRecords) {
|
||||
protected CompletableFuture<Void> rotateUserData(@NotNull User user) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
||||
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
||||
if (unpinnedUserData.size() > maxUserDataRecords) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
DELETE FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=?
|
||||
AND `pinned` IS FALSE
|
||||
ORDER BY `timestamp` ASC
|
||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
||||
Integer.toString(data.size() - maxUserDataRecords))))) {
|
||||
Integer.toString(unpinnedUserData.size() - maxUserDataRecords))))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
@@ -316,7 +322,7 @@ public class MySqlDatabase extends Database {
|
||||
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -361,7 +367,45 @@ public class MySqlDatabase extends Database {
|
||||
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||
}
|
||||
}
|
||||
}).thenRun(() -> pruneUserData(user).join());
|
||||
}).thenRun(() -> rotateUserData(user).join());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> pinUserData(@NotNull User user, @NotNull UUID versionUuid) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
UPDATE `%user_data_table%`
|
||||
SET `pinned`=TRUE
|
||||
WHERE `player_uuid`=? AND `version_uuid`=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.setString(2, versionUuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to pin user data in the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> unpinUserData(@NotNull User user, @NotNull UUID versionUuid) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
UPDATE `%user_data_table%`
|
||||
SET `pinned`=FALSE
|
||||
WHERE `player_uuid`=? AND `version_uuid`=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.setString(2, versionUuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to unpin user data in the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user