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 628d5b6c..0fb5ad82 100644 --- a/common/src/main/java/net/william278/husksync/database/Database.java +++ b/common/src/main/java/net/william278/husksync/database/Database.java @@ -24,6 +24,7 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.config.Settings; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; +import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -68,7 +69,7 @@ public abstract class Database { * @return the formatted statement, with table placeholders replaced with the correct names */ @NotNull - protected final String formatStatementTables(@NotNull String sql) { + protected final String formatStatementTables(@NotNull @Language("SQL") String sql) { final Settings.DatabaseSettings settings = plugin.getSettings().getDatabase(); return sql.replaceAll("%users_table%", settings.getTableName(TableName.USERS)) .replaceAll("%user_data_table%", settings.getTableName(TableName.USER_DATA)) @@ -138,6 +139,16 @@ public abstract class Database { @NotNull public abstract List getAllSnapshots(@NotNull User user); + /** + * Get the number of {@link DataSnapshot}s a user has + * + * @param user the user to count snapshots for + * @param includePinned whether to include pinned snapshots in the search + * @return the number of snapshots this user has saved + */ + @Blocking + public abstract int getSnapshotCount(@NotNull User user, boolean includePinned); + /** * Gets a specific {@link DataSnapshot} entry for a user from the database, by its UUID. * @@ -264,7 +275,7 @@ public abstract class Database { * * @param serverName Name of the server the map originates from * @param mapId Original map ID - * @return Map.Entry (key: map data, value: is from current world) + * @return Map.Entry (key: map data, value: is from current world) */ @Blocking public abstract @Nullable Map.Entry getMapData(@NotNull String serverName, int mapId); @@ -274,7 +285,7 @@ public abstract class Database { * * @param serverName Name of the server the map originates from * @param mapId Original map ID - * @return Map.Entry (key: server name, value: map ID) + * @return Map.Entry (key: server name, value: map ID) */ @Blocking public abstract @Nullable Map.Entry getMapBinding(@NotNull String serverName, int mapId); @@ -296,7 +307,7 @@ public abstract class Database { * @param fromServerName Name of the server the map originates from * @param fromMapId Original map ID * @param toServerName Name of the new server - * @return New map ID or -1 if not found + * @return New map ID or -1 if not found */ @Blocking public abstract int getBoundMapId(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName); 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 68daaf63..fcf7f502 100644 --- a/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java @@ -234,6 +234,20 @@ public class MongoDbDatabase extends Database { } } + @Override + public int getSnapshotCount(@NotNull User user, boolean includePinned) { + try { + Document filter = new Document("player_uuid", user.getUuid()); + if (!includePinned) { + filter = filter.append("pinned", false); + } + return (int) mongoCollectionHelper.getCollection(userDataTable).countDocuments(filter); + } catch (MongoException e) { + plugin.log(Level.SEVERE, "Failed to fetch a user's current snapshot count", e); + } + return 0; + } + @Blocking @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { @@ -259,17 +273,14 @@ public class MongoDbDatabase extends Database { @Override protected void rotateSnapshots(@NotNull User user) { try { - final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + final int unpinnedSnapshots = getSnapshotCount(user, false); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); - if (unpinnedUserData.size() > maxSnapshots) { - + if (unpinnedSnapshots > maxSnapshots) { Document filter = new Document("player_uuid", user.getUuid()).append("pinned", false); Document sort = new Document("timestamp", 1); // 1 = Ascending FindIterable iterable = mongoCollectionHelper.getCollection(userDataTable) - .find(filter) - .sort(sort) - .limit(unpinnedUserData.size() - maxSnapshots); + .find(filter).sort(sort) + .limit(unpinnedSnapshots - maxSnapshots); for (Document doc : iterable) { mongoCollectionHelper.deleteDocument(userDataTable, doc); 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 8779c102..f387c9cb 100644 --- a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java @@ -299,11 +299,31 @@ public class MySqlDatabase extends Database { return retrievedData; } } catch (SQLException | DataAdapter.AdaptionException e) { - plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e); + plugin.log(Level.SEVERE, "Failed to fetch a user's list of snapshots from the database", e); } return retrievedData; } + @Override + public int getSnapshotCount(@NotNull User user, boolean includePinned) { + try (Connection connection = getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" + SELECT COUNT(`version_uuid`) + FROM `%user_data_table%` AND `pinned`=false OR `pinned`=? + WHERE `player_uuid`=?;"""))) { + statement.setString(1, user.getUuid().toString()); + statement.setBoolean(2, includePinned); + final ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getInt(1); + } + } + } catch (SQLException e) { + plugin.log(Level.SEVERE, "Failed to fetch a user's current snapshot count", e); + } + return 0; + } + @Blocking @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { @@ -336,10 +356,9 @@ public class MySqlDatabase extends Database { @Blocking @Override protected void rotateSnapshots(@NotNull User user) { - final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + final int unpinnedSnapshots = getSnapshotCount(user, false); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); - if (unpinnedUserData.size() > maxSnapshots) { + if (unpinnedSnapshots > maxSnapshots) { try (Connection connection = getConnection()) { try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" DELETE FROM `%user_data_table%` @@ -347,7 +366,7 @@ public class MySqlDatabase extends Database { AND `pinned` IS FALSE ORDER BY `timestamp` ASC LIMIT %entry_count%;""".replace("%entry_count%", - Integer.toString(unpinnedUserData.size() - maxSnapshots))))) { + Integer.toString(unpinnedSnapshots - maxSnapshots))))) { statement.setString(1, user.getUuid().toString()); statement.executeUpdate(); } 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 a776ab17..4b57d7c6 100644 --- a/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java @@ -288,11 +288,31 @@ public class PostgresDatabase extends Database { return retrievedData; } } catch (SQLException | DataAdapter.AdaptionException e) { - plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e); + plugin.log(Level.SEVERE, "Failed to fetch a user's list of snapshots from the database", e); } return retrievedData; } + @Override + public int getSnapshotCount(@NotNull User user, boolean includePinned) { + try (Connection connection = getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" + SELECT COUNT(`version_uuid`) + FROM `%user_data_table%` AND `pinned`=false OR `pinned`=? + WHERE `player_uuid`=?;"""))) { + statement.setString(1, user.getUuid().toString()); + statement.setBoolean(2, includePinned); + final ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getInt(1); + } + } + } catch (SQLException e) { + plugin.log(Level.SEVERE, "Failed to fetch a user's current snapshot count", e); + } + return 0; + } + @Blocking @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { @@ -323,10 +343,9 @@ public class PostgresDatabase extends Database { @Blocking @Override protected void rotateSnapshots(@NotNull User user) { - final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + final int unpinnedSnapshots = getSnapshotCount(user, false); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); - if (unpinnedUserData.size() > maxSnapshots) { + if (unpinnedSnapshots > maxSnapshots) { try (Connection connection = getConnection()) { try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" WITH cte AS ( @@ -339,7 +358,7 @@ public class PostgresDatabase extends Database { ) DELETE FROM %user_data_table% WHERE version_uuid IN (SELECT version_uuid FROM cte);""".replace("%entry_count%", - Integer.toString(unpinnedUserData.size() - maxSnapshots))))) { + Integer.toString(unpinnedSnapshots - maxSnapshots))))) { statement.setObject(1, user.getUuid()); statement.executeUpdate(); }