9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-19 14:59:21 +00:00

feat: add legacy upgrade command

This commit is contained in:
William
2024-08-26 16:00:21 +01:00
parent 7d8ef7b6b3
commit 50eb9a7543
5 changed files with 103 additions and 86 deletions

View File

@@ -30,9 +30,11 @@ import net.kyori.adventure.text.format.TextColor;
import net.william278.desertwell.about.AboutMenu; import net.william278.desertwell.about.AboutMenu;
import net.william278.desertwell.util.UpdateChecker; import net.william278.desertwell.util.UpdateChecker;
import net.william278.husksync.HuskSync; import net.william278.husksync.HuskSync;
import net.william278.husksync.data.DataSnapshot;
import net.william278.husksync.database.Database; import net.william278.husksync.database.Database;
import net.william278.husksync.migrator.Migrator; import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.user.CommandUser; import net.william278.husksync.user.CommandUser;
import net.william278.husksync.util.LegacyConverter;
import net.william278.uniform.BaseCommand; import net.william278.uniform.BaseCommand;
import net.william278.uniform.CommandProvider; import net.william278.uniform.CommandProvider;
import net.william278.uniform.Permission; import net.william278.uniform.Permission;
@@ -40,8 +42,10 @@ import net.william278.uniform.element.ArgumentElement;
import org.apache.commons.text.WordUtils; import org.apache.commons.text.WordUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.time.OffsetDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -96,6 +100,7 @@ public class HuskSyncCommand extends PluginCommand {
command.addSubCommand("status", needsOp("status"), status()); command.addSubCommand("status", needsOp("status"), status());
command.addSubCommand("reload", needsOp("reload"), reload()); command.addSubCommand("reload", needsOp("reload"), reload());
command.addSubCommand("update", needsOp("update"), update()); command.addSubCommand("update", needsOp("update"), update());
command.addSubCommand("forceupgrade", forceUpgrade());
command.addSubCommand("migrate", migrate()); command.addSubCommand("migrate", migrate());
} }
@@ -182,6 +187,35 @@ public class HuskSyncCommand extends PluginCommand {
}; };
} }
@NotNull
private CommandProvider forceUpgrade() {
return (sub) -> {
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
sub.setDefaultExecutor((ctx) -> {
final LegacyConverter converter = plugin.getLegacyConverter().orElse(null);
if (converter == null) {
return;
}
plugin.runAsync(() -> {
final Database database = plugin.getDatabase();
plugin.log(Level.INFO, "Beginning forced legacy data upgrade for all users...");
database.getAllUsers().forEach(user -> database.getLatestSnapshot(user).ifPresent(snapshot -> {
final DataSnapshot.Packed upgraded = converter.convert(
snapshot.asBytes(plugin),
UUID.randomUUID(),
OffsetDateTime.now()
);
upgraded.setSaveCause(DataSnapshot.SaveCause.CONVERTED_FROM_V2);
plugin.getDatabase().addSnapshot(user, upgraded);
plugin.getRedisManager().clearUserData(user);
}));
plugin.log(Level.INFO, "Legacy data upgrade complete!");
});
});
};
}
@NotNull @NotNull
private <S> ArgumentElement<S, Migrator> migrator() { private <S> ArgumentElement<S, Migrator> migrator() {
return new ArgumentElement<>("migrator", reader -> { return new ArgumentElement<>("migrator", reader -> {

View File

@@ -107,6 +107,14 @@ public abstract class Database {
@Blocking @Blocking
public abstract Optional<User> getUserByName(@NotNull String username); public abstract Optional<User> getUserByName(@NotNull String username);
/**
* Get all users
*
* @return A list of all users
*/
@NotNull
@Blocking
public abstract List<User> getAllUsers();
/** /**
* Get the latest data snapshot for a user. * Get the latest data snapshot for a user.

View File

@@ -57,11 +57,6 @@ public class MongoDbDatabase extends Database {
this.userDataTable = plugin.getSettings().getDatabase().getTableName(TableName.USER_DATA); 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 @Override
public void initialize() throws IllegalStateException { public void initialize() throws IllegalStateException {
final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials(); final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials();
@@ -94,11 +89,6 @@ public class MongoDbDatabase extends Database {
return new ConnectionString(baseURI); return new ConnectionString(baseURI);
} }
/**
* 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 @Blocking
@Override @Override
public void ensureUser(@NotNull User user) { public void ensureUser(@NotNull User user) {
@@ -136,12 +126,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
public Optional<User> getUser(@NotNull UUID uuid) { public Optional<User> getUser(@NotNull UUID uuid) {
@@ -158,12 +142,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
public Optional<User> getUserByName(@NotNull String username) { public Optional<User> getUserByName(@NotNull String username) {
@@ -181,12 +159,24 @@ public class MongoDbDatabase extends Database {
} }
} }
/** @Override
* Get the latest data snapshot for a user. @NotNull
* public List<User> getAllUsers() {
* @param user The user to get data for final List<User> users = Lists.newArrayList();
* @return an optional containing the {@link DataSnapshot}, if it exists, or an empty optional if it does not try {
*/ final FindIterable<Document> doc = mongoCollectionHelper.getCollection(usersTable).find();
for (Document document : doc) {
users.add(new User(
UUID.fromString(document.getString("uuid")),
document.getString("username")
));
}
} catch (MongoException e) {
plugin.log(Level.SEVERE, "Failed to get all users from the database", e);
}
return users;
}
@Blocking @Blocking
@Override @Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) { public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
@@ -209,12 +199,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
@NotNull @NotNull
@@ -238,13 +222,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
@@ -266,12 +243,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* <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 @Blocking
@Override @Override
protected void rotateSnapshots(@NotNull User user) { protected void rotateSnapshots(@NotNull User user) {
@@ -297,12 +268,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) { public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
@@ -320,14 +285,6 @@ public class MongoDbDatabase extends Database {
return false; 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 @Blocking
@Override @Override
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) { protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
@@ -352,12 +309,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* <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 @Blocking
@Override @Override
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
@@ -374,12 +325,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* Update a saved {@link DataSnapshot} by given version UUID
*
* @param user The user whose data snapshot
* @param data The {@link DataSnapshot} to update
*/
@Blocking @Blocking
@Override @Override
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
@@ -396,10 +341,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* 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 @Blocking
@Override @Override
public void wipeDatabase() { public void wipeDatabase() {
@@ -410,9 +351,6 @@ public class MongoDbDatabase extends Database {
} }
} }
/**
* Close the database connection
*/
@Override @Override
public void terminate() { public void terminate() {
if (mongoConnectionHandler != null) { if (mongoConnectionHandler != null) {

View File

@@ -218,6 +218,27 @@ public class MySqlDatabase extends Database {
return Optional.empty(); return Optional.empty();
} }
@Override
@NotNull
public List<User> getAllUsers() {
final List<User> users = Lists.newArrayList();
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT `uuid`, `username`
FROM `%users_table%`;
"""))) {
final ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
users.add(new User(UUID.fromString(resultSet.getString("uuid")),
resultSet.getString("username")));
}
}
} catch (SQLException e) {
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
}
return users;
}
@Blocking @Blocking
@Override @Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) { public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {

View File

@@ -51,12 +51,6 @@ public class PostgresDatabase extends Database {
this.driverClass = "org.postgresql.Driver"; this.driverClass = "org.postgresql.Driver";
} }
/**
* Fetch the auto-closeable connection from the hikariDataSource
*
* @return The {@link Connection} to the MySQL database
* @throws SQLException if the connection fails for some reason
*/
@Blocking @Blocking
@NotNull @NotNull
private Connection getConnection() throws SQLException { private Connection getConnection() throws SQLException {
@@ -217,6 +211,28 @@ public class PostgresDatabase extends Database {
return Optional.empty(); return Optional.empty();
} }
@Override
@NotNull
public List<User> getAllUsers() {
final List<User> users = Lists.newArrayList();
try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT `uuid`, `username`
FROM `%users_table%`;
"""))) {
final ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
users.add(new User(UUID.fromString(resultSet.getString("uuid")),
resultSet.getString("username")));
}
}
} catch (SQLException e) {
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
}
return users;
}
@Blocking @Blocking
@Override @Override
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) { public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {