From 50eb9a7543a7f2cfc78696783d1d20a1e3405641 Mon Sep 17 00:00:00 2001 From: William Date: Mon, 26 Aug 2024 16:00:21 +0100 Subject: [PATCH] feat: add legacy upgrade command --- .../husksync/command/HuskSyncCommand.java | 34 +++++++ .../husksync/database/Database.java | 8 ++ .../husksync/database/MongoDbDatabase.java | 98 ++++--------------- .../husksync/database/MySqlDatabase.java | 21 ++++ .../husksync/database/PostgresDatabase.java | 28 ++++-- 5 files changed, 103 insertions(+), 86 deletions(-) diff --git a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java index 80d38823..e2106db7 100644 --- a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java +++ b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java @@ -30,9 +30,11 @@ import net.kyori.adventure.text.format.TextColor; import net.william278.desertwell.about.AboutMenu; import net.william278.desertwell.util.UpdateChecker; import net.william278.husksync.HuskSync; +import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.database.Database; import net.william278.husksync.migrator.Migrator; import net.william278.husksync.user.CommandUser; +import net.william278.husksync.util.LegacyConverter; import net.william278.uniform.BaseCommand; import net.william278.uniform.CommandProvider; import net.william278.uniform.Permission; @@ -40,8 +42,10 @@ import net.william278.uniform.element.ArgumentElement; import org.apache.commons.text.WordUtils; import org.jetbrains.annotations.NotNull; +import java.time.OffsetDateTime; import java.util.Arrays; import java.util.List; +import java.util.UUID; import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; @@ -96,6 +100,7 @@ public class HuskSyncCommand extends PluginCommand { command.addSubCommand("status", needsOp("status"), status()); command.addSubCommand("reload", needsOp("reload"), reload()); command.addSubCommand("update", needsOp("update"), update()); + command.addSubCommand("forceupgrade", forceUpgrade()); 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 private ArgumentElement migrator() { return new ArgumentElement<>("migrator", reader -> { diff --git a/common/src/main/java/net/william278/husksync/database/Database.java b/common/src/main/java/net/william278/husksync/database/Database.java index 732b641f..c03bc2ad 100644 --- a/common/src/main/java/net/william278/husksync/database/Database.java +++ b/common/src/main/java/net/william278/husksync/database/Database.java @@ -107,6 +107,14 @@ public abstract class Database { @Blocking public abstract Optional getUserByName(@NotNull String username); + /** + * Get all users + * + * @return A list of all users + */ + @NotNull + @Blocking + public abstract List getAllUsers(); /** * Get the latest data snapshot for a user. diff --git a/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java b/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java index 08da5091..1208d493 100644 --- a/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java @@ -57,11 +57,6 @@ public class MongoDbDatabase extends Database { 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(); @@ -94,11 +89,6 @@ public class MongoDbDatabase extends Database { 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 @Override 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 @Override public Optional getUser(@NotNull UUID uuid) { @@ -158,12 +142,6 @@ public class MongoDbDatabase extends Database { } } - /** - * Get a user by their username (case-insensitive) - * - * @param username Username of the {@link User} to get (case-insensitive) - * @return An optional with the {@link User} present if they exist - */ @Blocking @Override public Optional getUserByName(@NotNull String username) { @@ -181,12 +159,24 @@ public class MongoDbDatabase extends Database { } } - /** - * 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 - */ + @Override + @NotNull + public List getAllUsers() { + final List users = Lists.newArrayList(); + try { + final FindIterable 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 @Override public Optional 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 @Override @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 @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { @@ -266,12 +243,6 @@ public class MongoDbDatabase extends Database { } } - /** - * (Internal) 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) { @@ -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 @Override public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) { @@ -320,14 +285,6 @@ public class MongoDbDatabase extends Database { 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) { @@ -352,12 +309,6 @@ public class MongoDbDatabase extends Database { } } - /** - * Internal - 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) { @@ -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 @Override public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { @@ -396,10 +341,6 @@ public class MongoDbDatabase extends Database { } } - /** - * Wipes all {@link User} entries from the database. - * This should only be used when preparing tables for a data migration. - */ @Blocking @Override public void wipeDatabase() { @@ -410,9 +351,6 @@ public class MongoDbDatabase extends Database { } } - /** - * Close the database connection - */ @Override public void terminate() { if (mongoConnectionHandler != null) { diff --git a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java index f3b38dc1..249e3989 100644 --- a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java @@ -218,6 +218,27 @@ public class MySqlDatabase extends Database { return Optional.empty(); } + @Override + @NotNull + public List getAllUsers() { + final List 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 @Override public Optional getLatestSnapshot(@NotNull User user) { diff --git a/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java b/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java index f414d2f5..d9522a56 100644 --- a/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java @@ -51,12 +51,6 @@ public class PostgresDatabase extends Database { 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 @NotNull private Connection getConnection() throws SQLException { @@ -217,6 +211,28 @@ public class PostgresDatabase extends Database { return Optional.empty(); } + + @Override + @NotNull + public List getAllUsers() { + final List 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 @Override public Optional getLatestSnapshot(@NotNull User user) {