mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2026-01-04 15:31:37 +00:00
feat: rework locked maps syncing (#464)
* Better maps syncing (#2) * Do not create new views for maps from current world * Fix maps in shulkers not converting * Add bundle support for map conversion * Rework map sync * Fix empty statements in database * Fix missing imports * Rename connectMapIds -> bindMapIds * Use data adapter to save maps * Split Mongo readMapData * Split MySQL readMapData * Split Postgres readMapData * Update database schemas Use server names instead of world UUIDs * Update Database class * Update MongoDbDatabase class * Update MySqlDatabase class * Update PostgresDatabase class * Update BukkitMapPersister class Use server names instead of world UUIDs * Remove unused code * Add my nickname to contributors :) * Start implementing Redis map caching * Continue implementing Redis map caching * Bind map ids on Redis before writing to DB * Finish implementing Redis map data caching * refactor: decouple new map logic Redis caching from DB * test: enable debug logging in test suite * docs: update docs with new username method * feat: adjust a method name --------- Co-authored-by: Sóla Lusøt <60041069+solaluset@users.noreply.github.com>
This commit is contained in:
@@ -51,7 +51,7 @@ public class EnderChestCommand extends ItemsCommand {
|
||||
}
|
||||
|
||||
// Display opening message
|
||||
plugin.getLocales().getLocale("ender_chest_viewer_opened", user.getUsername(),
|
||||
plugin.getLocales().getLocale("ender_chest_viewer_opened", user.getName(),
|
||||
snapshot.getTimestamp().format(DateTimeFormatter
|
||||
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
||||
.ifPresent(viewer::sendMessage);
|
||||
@@ -60,8 +60,8 @@ public class EnderChestCommand extends ItemsCommand {
|
||||
final Data.Items.EnderChest enderChest = optionalEnderChest.get();
|
||||
viewer.showGui(
|
||||
enderChest,
|
||||
plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getUsername())
|
||||
.orElse(new MineDown(String.format("%s's Ender Chest", user.getUsername()))),
|
||||
plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getName())
|
||||
.orElse(new MineDown(String.format("%s's Ender Chest", user.getName()))),
|
||||
allowEdit,
|
||||
enderChest.getSlotCount(),
|
||||
(itemsOnClose) -> {
|
||||
|
||||
@@ -69,7 +69,8 @@ public class HuskSyncCommand extends PluginCommand {
|
||||
AboutMenu.Credit.of("HookWoods").description("Code"),
|
||||
AboutMenu.Credit.of("Preva1l").description("Code"),
|
||||
AboutMenu.Credit.of("hanbings").description("Code (Fabric porting)"),
|
||||
AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)"))
|
||||
AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)"),
|
||||
AboutMenu.Credit.of("VinerDream").description("Code"))
|
||||
.credits("Translators",
|
||||
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
||||
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
||||
|
||||
@@ -52,7 +52,7 @@ public class InventoryCommand extends ItemsCommand {
|
||||
}
|
||||
|
||||
// Display opening message
|
||||
plugin.getLocales().getLocale("inventory_viewer_opened", user.getUsername(),
|
||||
plugin.getLocales().getLocale("inventory_viewer_opened", user.getName(),
|
||||
snapshot.getTimestamp().format(DateTimeFormatter
|
||||
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
||||
.ifPresent(viewer::sendMessage);
|
||||
@@ -61,8 +61,8 @@ public class InventoryCommand extends ItemsCommand {
|
||||
final Data.Items.Inventory inventory = optionalInventory.get();
|
||||
viewer.showGui(
|
||||
inventory,
|
||||
plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getUsername())
|
||||
.orElse(new MineDown(String.format("%s's Inventory", user.getUsername()))),
|
||||
plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getName())
|
||||
.orElse(new MineDown(String.format("%s's Inventory", user.getName()))),
|
||||
allowEdit,
|
||||
inventory.getSlotCount(),
|
||||
(itemsOnClose) -> {
|
||||
|
||||
@@ -83,7 +83,7 @@ public abstract class PluginCommand extends Command {
|
||||
() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader)
|
||||
);
|
||||
}, (context, builder) -> {
|
||||
plugin.getOnlineUsers().forEach(u -> builder.suggest(u.getUsername()));
|
||||
plugin.getOnlineUsers().forEach(u -> builder.suggest(u.getName()));
|
||||
return builder.buildFuture();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class UserDataCommand extends PluginCommand {
|
||||
plugin.getLocales().getLocale("data_deleted",
|
||||
version.toString().split("-")[0],
|
||||
version.toString(),
|
||||
user.getUsername(),
|
||||
user.getName(),
|
||||
user.getUuid().toString())
|
||||
.ifPresent(executor::sendMessage);
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public class UserDataCommand extends PluginCommand {
|
||||
plugin.getDataSyncer().saveData(user, data, (u, s) -> {
|
||||
redis.getUserData(u).ifPresent(d -> redis.setUserData(u, s, RedisKeyType.TTL_1_YEAR));
|
||||
redis.sendUserDataUpdate(u, s);
|
||||
plugin.getLocales().getLocale("data_restored", u.getUsername(), u.getUuid().toString(),
|
||||
plugin.getLocales().getLocale("data_restored", u.getName(), u.getUuid().toString(),
|
||||
s.getShortId(), s.getId().toString()).ifPresent(executor::sendMessage);
|
||||
});
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public class UserDataCommand extends PluginCommand {
|
||||
plugin.getDatabase().pinSnapshot(user, data.getId());
|
||||
}
|
||||
plugin.getLocales().getLocale(data.isPinned() ? "data_unpinned" : "data_pinned", data.getShortId(),
|
||||
data.getId().toString(), user.getUsername(), user.getUuid().toString())
|
||||
data.getId().toString(), user.getName(), user.getUuid().toString())
|
||||
.ifPresent(executor::sendMessage);
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ public class UserDataCommand extends PluginCommand {
|
||||
final DataSnapshot.Packed userData = data.get();
|
||||
final UserDataDumper dumper = UserDataDumper.create(userData, user, plugin);
|
||||
try {
|
||||
plugin.getLocales().getLocale("data_dumped", userData.getShortId(), user.getUsername(),
|
||||
plugin.getLocales().getLocale("data_dumped", userData.getShortId(), user.getName(),
|
||||
(type == DumpType.WEB ? dumper.toWeb() : dumper.toFile()))
|
||||
.ifPresent(executor::sendMessage);
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -56,8 +57,8 @@ public abstract class Database {
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@NotNull
|
||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
||||
return formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName))
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||
return Arrays.stream(formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName))
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";")).filter(s -> !s.isBlank()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +71,9 @@ public abstract class Database {
|
||||
protected final String formatStatementTables(@NotNull 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));
|
||||
.replaceAll("%user_data_table%", settings.getTableName(TableName.USER_DATA))
|
||||
.replaceAll("%map_data_table%", settings.getTableName(TableName.MAP_DATA))
|
||||
.replaceAll("%map_ids_table%", settings.getTableName(TableName.MAP_IDS));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,6 +249,58 @@ public abstract class Database {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write map data to a database
|
||||
*
|
||||
* @param serverName Name of the server the map originates from
|
||||
* @param mapId Original map ID
|
||||
* @param data Map data
|
||||
*/
|
||||
@Blocking
|
||||
public abstract void saveMapData(@NotNull String serverName, int mapId, byte @NotNull [] data);
|
||||
|
||||
/**
|
||||
* Read map data from a 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)
|
||||
*/
|
||||
@Blocking
|
||||
public abstract @Nullable Map.Entry<byte[], Boolean> getMapData(@NotNull String serverName, int mapId);
|
||||
|
||||
/**
|
||||
* Get a map server -> ID binding in the 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)
|
||||
*/
|
||||
@Blocking
|
||||
public abstract @Nullable Map.Entry<String, Integer> getMapBinding(@NotNull String serverName, int mapId);
|
||||
|
||||
/**
|
||||
* Bind map IDs across different servers
|
||||
*
|
||||
* @param fromServerName Name of the server the map originates from
|
||||
* @param fromMapId Original map ID
|
||||
* @param toServerName Name of the new server
|
||||
* @param toMapId New map ID
|
||||
*/
|
||||
@Blocking
|
||||
public abstract void setMapBinding(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName, int toMapId);
|
||||
|
||||
/**
|
||||
* Get map ID for the new server
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@Blocking
|
||||
public abstract int getBoundMapId(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName);
|
||||
|
||||
/**
|
||||
* Wipes <b>all</b> {@link User} entries from the database.
|
||||
* <b>This should only be used when preparing tables for a data migration.</b>
|
||||
@@ -283,7 +338,9 @@ public abstract class Database {
|
||||
@Getter
|
||||
public enum TableName {
|
||||
USERS("husksync_users"),
|
||||
USER_DATA("husksync_user_data");
|
||||
USER_DATA("husksync_user_data"),
|
||||
MAP_DATA("husksync_map_data"),
|
||||
MAP_IDS("husksync_map_ids");
|
||||
|
||||
private final String defaultName;
|
||||
|
||||
|
||||
@@ -35,13 +35,11 @@ import org.bson.conversions.Bson;
|
||||
import org.bson.types.Binary;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class MongoDbDatabase extends Database {
|
||||
@@ -50,11 +48,15 @@ public class MongoDbDatabase extends Database {
|
||||
|
||||
private final String usersTable;
|
||||
private final String userDataTable;
|
||||
private final String mapDataTable;
|
||||
private final String mapIdsTable;
|
||||
|
||||
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
||||
super(plugin);
|
||||
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
||||
this.userDataTable = plugin.getSettings().getDatabase().getTableName(TableName.USER_DATA);
|
||||
this.mapDataTable = plugin.getSettings().getDatabase().getTableName(TableName.MAP_DATA);
|
||||
this.mapIdsTable = plugin.getSettings().getDatabase().getTableName(TableName.MAP_IDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,9 +76,15 @@ public class MongoDbDatabase extends Database {
|
||||
if (mongoCollectionHelper.getCollection(userDataTable) == null) {
|
||||
mongoCollectionHelper.createCollection(userDataTable);
|
||||
}
|
||||
if (mongoCollectionHelper.getCollection(mapDataTable) == null) {
|
||||
mongoCollectionHelper.createCollection(mapDataTable);
|
||||
}
|
||||
if (mongoCollectionHelper.getCollection(mapIdsTable) == null) {
|
||||
mongoCollectionHelper.createCollection(mapIdsTable);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +107,7 @@ public class MongoDbDatabase extends Database {
|
||||
try {
|
||||
getUser(user.getUuid()).ifPresentOrElse(
|
||||
existingUser -> {
|
||||
if (!existingUser.getUsername().equals(user.getUsername())) {
|
||||
if (!existingUser.getName().equals(user.getName())) {
|
||||
// Update a user's name if it has changed in the database
|
||||
try {
|
||||
Document filter = new Document("uuid", existingUser.getUuid());
|
||||
@@ -108,7 +116,7 @@ public class MongoDbDatabase extends Database {
|
||||
throw new MongoException("User document returned null!");
|
||||
}
|
||||
|
||||
Bson updates = Updates.set("username", user.getUsername());
|
||||
Bson updates = Updates.set("username", user.getName());
|
||||
mongoCollectionHelper.updateDocument(usersTable, doc, updates);
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||
@@ -118,7 +126,7 @@ public class MongoDbDatabase extends Database {
|
||||
() -> {
|
||||
// Insert new player data into the database
|
||||
try {
|
||||
Document doc = new Document("uuid", user.getUuid()).append("username", user.getUsername());
|
||||
Document doc = new Document("uuid", user.getUuid()).append("username", user.getName());
|
||||
mongoCollectionHelper.insertDocument(usersTable, doc);
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||
@@ -345,6 +353,85 @@ public class MongoDbDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void saveMapData(@NotNull String serverName, int mapId, byte @NotNull [] data) {
|
||||
try {
|
||||
Document doc = new Document("server_name", serverName)
|
||||
.append("map_id", mapId)
|
||||
.append("data", new Binary(data));
|
||||
mongoCollectionHelper.insertDocument(mapDataTable, doc);
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to write map data to the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<byte[], Boolean> getMapData(@NotNull String serverName, int mapId) {
|
||||
try {
|
||||
Document filter = new Document("server_name", serverName).append("map_id", mapId);
|
||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(mapDataTable).find(filter);
|
||||
Document doc = iterable.first();
|
||||
if (doc != null) {
|
||||
final Binary bin = doc.get("data", Binary.class);
|
||||
return Map.entry(bin.getData(), true);
|
||||
}
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get map data from the database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<String, Integer> getMapBinding(@NotNull String serverName, int mapId) {
|
||||
final Document filter = new Document("to_server_name", serverName).append("to_id", mapId);
|
||||
final FindIterable<Document> iterable = mongoCollectionHelper.getCollection(mapIdsTable).find(filter);
|
||||
final Document doc = iterable.first();
|
||||
if (doc != null) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(
|
||||
doc.getString("server_name"),
|
||||
doc.getInteger("to_id")
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void setMapBinding(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName, int toMapId) {
|
||||
try {
|
||||
final Document doc = new Document("from_server_name", fromServerName)
|
||||
.append("from_id", fromMapId)
|
||||
.append("to_server_name", toServerName)
|
||||
.append("to_id", toMapId);
|
||||
mongoCollectionHelper.insertDocument(mapIdsTable, doc);
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to connect map IDs in the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public int getBoundMapId(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName) {
|
||||
try {
|
||||
final Document filter = new Document("from_server_name", fromServerName)
|
||||
.append("from_id", fromMapId)
|
||||
.append("to_server_name", toServerName);
|
||||
final FindIterable<Document> iterable = mongoCollectionHelper.getCollection(mapIdsTable).find(filter);
|
||||
|
||||
final Document doc = iterable.first();
|
||||
if (doc != null) {
|
||||
return doc.getInteger("to_id");
|
||||
}
|
||||
return -1;
|
||||
} catch (MongoException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get new map id from the database", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void wipeDatabase() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -127,11 +128,11 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
||||
"and that your connecting user account has privileges to create tables.", e);
|
||||
"and that your connecting user account has privileges to create tables.", e);
|
||||
}
|
||||
} catch (SQLException | IOException e) {
|
||||
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +141,7 @@ public class MySqlDatabase extends Database {
|
||||
public void ensureUser(@NotNull User user) {
|
||||
getUser(user.getUuid()).ifPresentOrElse(
|
||||
existingUser -> {
|
||||
if (!existingUser.getUsername().equals(user.getUsername())) {
|
||||
if (!existingUser.getName().equals(user.getName())) {
|
||||
// Update a user's name if it has changed in the database
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
@@ -148,11 +149,12 @@ public class MySqlDatabase extends Database {
|
||||
SET `username`=?
|
||||
WHERE `uuid`=?"""))) {
|
||||
|
||||
statement.setString(1, user.getUsername());
|
||||
statement.setString(1, user.getName());
|
||||
statement.setString(2, existingUser.getUuid().toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
plugin.log(Level.INFO, "Updated " + user.getUsername() + "'s name in the database (" + existingUser.getUsername() + " -> " + user.getUsername() + ")");
|
||||
plugin.log(Level.INFO, "Updated " + user.getName() + "'s name in the database ("
|
||||
+ existingUser.getName() + " -> " + user.getName() + ")");
|
||||
} catch (SQLException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
||||
}
|
||||
@@ -166,7 +168,7 @@ public class MySqlDatabase extends Database {
|
||||
VALUES (?,?);"""))) {
|
||||
|
||||
statement.setString(1, user.getUuid().toString());
|
||||
statement.setString(2, user.getUsername());
|
||||
statement.setString(2, user.getName());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -433,6 +435,120 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void saveMapData(@NotNull String serverName, int mapId, byte @NotNull [] data) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
INSERT INTO `%map_data_table%`
|
||||
(`server_name`,`map_id`,`data`)
|
||||
VALUES (?,?,?);"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
statement.setBlob(3, new ByteArrayInputStream(data));
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to write map data to the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<byte[], Boolean> getMapData(@NotNull String serverName, int mapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `data`
|
||||
FROM `%map_data_table%`
|
||||
WHERE `server_name`=? AND `map_id`=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
final Blob blob = resultSet.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
return Map.entry(dataByteArray, true);
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get map data from the database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<String, Integer> getMapBinding(@NotNull String serverName, int mapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `from_server_name`, `from_id`
|
||||
FROM `%map_ids_table%`
|
||||
WHERE `to_server_name`=? AND `to_id`=?
|
||||
LIMIT 1;
|
||||
"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(
|
||||
resultSet.getString("from_server_name"),
|
||||
resultSet.getInt("from_id")
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get map data from the database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void setMapBinding(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName, int toMapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
INSERT INTO `%map_ids_table%`
|
||||
(`from_server_name`,`from_id`,`to_server_name`,`to_id`)
|
||||
VALUES (?,?,?,?);"""))) {
|
||||
statement.setString(1, fromServerName);
|
||||
statement.setInt(2, fromMapId);
|
||||
statement.setString(3, toServerName);
|
||||
statement.setInt(4, toMapId);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to connect map IDs in the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public int getBoundMapId(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT `to_id`
|
||||
FROM `%map_ids_table%`
|
||||
WHERE `from_server_name`=? AND `from_id`=? AND `to_server_name`=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, fromServerName);
|
||||
statement.setInt(2, fromMapId);
|
||||
statement.setString(3, toServerName);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getInt("to_id");
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get new map id from the database", e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeDatabase() {
|
||||
try (Connection connection = getConnection()) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
@@ -120,11 +121,11 @@ public class PostgresDatabase extends Database {
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running PostgreSQL " +
|
||||
"and that your connecting user account has privileges to create tables.", e);
|
||||
"and that your connecting user account has privileges to create tables.", e);
|
||||
}
|
||||
} catch (SQLException | IOException e) {
|
||||
throw new IllegalStateException("Failed to establish a connection to the PostgreSQL database. " +
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ public class PostgresDatabase extends Database {
|
||||
public void ensureUser(@NotNull User user) {
|
||||
getUser(user.getUuid()).ifPresentOrElse(
|
||||
existingUser -> {
|
||||
if (!existingUser.getUsername().equals(user.getUsername())) {
|
||||
if (!existingUser.getName().equals(user.getName())) {
|
||||
// Update a user's name if it has changed in the database
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
@@ -141,11 +142,11 @@ public class PostgresDatabase extends Database {
|
||||
SET username=?
|
||||
WHERE uuid=?;"""))) {
|
||||
|
||||
statement.setString(1, user.getUsername());
|
||||
statement.setString(1, user.getName());
|
||||
statement.setObject(2, existingUser.getUuid());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
plugin.log(Level.INFO, "Updated " + user.getUsername() + "'s name in the database (" + existingUser.getUsername() + " -> " + user.getUsername() + ")");
|
||||
plugin.log(Level.INFO, "Updated " + user.getName() + "'s name in the database (" + existingUser.getName() + " -> " + user.getName() + ")");
|
||||
} catch (SQLException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
||||
}
|
||||
@@ -159,7 +160,7 @@ public class PostgresDatabase extends Database {
|
||||
VALUES (?,?);"""))) {
|
||||
|
||||
statement.setObject(1, user.getUuid());
|
||||
statement.setString(2, user.getUsername());
|
||||
statement.setString(2, user.getName());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -430,6 +431,118 @@ public class PostgresDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void saveMapData(@NotNull String serverName, int mapId, byte @NotNull [] data) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
INSERT INTO %map_data_table%
|
||||
(server_name,map_id,data)
|
||||
VALUES (?,?,?);"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
statement.setBytes(3, data);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to write map data to the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<byte[], Boolean> getMapData(@NotNull String serverName, int mapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT data
|
||||
FROM %map_data_table%
|
||||
WHERE server_name=? AND map_id=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
final byte[] data = resultSet.getBytes("data");
|
||||
return new AbstractMap.SimpleImmutableEntry<>(data, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get map data from the database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public @Nullable Map.Entry<String, Integer> getMapBinding(@NotNull String serverName, int mapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT from_server_name, from_id
|
||||
FROM %map_ids_table%
|
||||
WHERE to_server_name=? AND to_id=?
|
||||
LIMIT 1;
|
||||
"""))) {
|
||||
statement.setString(1, serverName);
|
||||
statement.setInt(2, mapId);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(
|
||||
resultSet.getString("from_server_name"),
|
||||
resultSet.getInt("from_id")
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get map data from the database", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public void setMapBinding(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName, int toMapId) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
INSERT INTO %map_ids_table%
|
||||
(from_server_name,from_id,to_server_name,to_id)
|
||||
VALUES (?,?,?,?);"""))) {
|
||||
statement.setString(1, fromServerName);
|
||||
statement.setInt(2, fromMapId);
|
||||
statement.setString(3, toServerName);
|
||||
statement.setInt(4, toMapId);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to connect map IDs in the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
@Override
|
||||
public int getBoundMapId(@NotNull String fromServerName, int fromMapId, @NotNull String toServerName) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
SELECT to_id
|
||||
FROM %map_ids_table%
|
||||
WHERE from_server_name=? AND from_id=? AND to_server_name=?
|
||||
LIMIT 1;"""))) {
|
||||
statement.setString(1, fromServerName);
|
||||
statement.setInt(2, fromMapId);
|
||||
statement.setString(3, toServerName);
|
||||
|
||||
final ResultSet resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getInt("to_id");
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdapter.AdaptionException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to get new map id from the database", e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeDatabase() {
|
||||
try (Connection connection = getConnection()) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.maps;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.william278.husksync.adapter.Adaptable;
|
||||
import net.william278.mapdataapi.MapData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class AdaptableMapData implements Adaptable {
|
||||
|
||||
@SerializedName("data")
|
||||
private final byte[] data;
|
||||
|
||||
public AdaptableMapData(@NotNull MapData data) {
|
||||
this(data.toBytes());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MapData getData(int dataVersion) throws IOException {
|
||||
return MapData.fromByteArray(dataVersion, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,10 @@ public enum RedisKeyType {
|
||||
|
||||
LATEST_SNAPSHOT,
|
||||
SERVER_SWITCH,
|
||||
DATA_CHECKOUT;
|
||||
DATA_CHECKOUT,
|
||||
MAP_ID,
|
||||
MAP_ID_REVERSED,
|
||||
MAP_DATA;
|
||||
|
||||
public static final int TTL_1_YEAR = 60 * 60 * 24 * 7 * 52; // 1 year
|
||||
public static final int TTL_10_SECONDS = 10; // 10 seconds
|
||||
|
||||
@@ -25,6 +25,7 @@ import net.william278.husksync.data.DataSnapshot;
|
||||
import net.william278.husksync.user.User;
|
||||
import org.jetbrains.annotations.Blocking;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
import redis.clients.jedis.util.Pool;
|
||||
@@ -261,7 +262,7 @@ public class RedisManager extends JedisPubSub {
|
||||
timeToLive,
|
||||
data.asBytes(plugin)
|
||||
);
|
||||
plugin.debug(String.format("[%s] Set %s key on Redis", user.getUsername(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
plugin.debug(String.format("[%s] Set %s key on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred setting user data on Redis", e);
|
||||
}
|
||||
@@ -273,7 +274,7 @@ public class RedisManager extends JedisPubSub {
|
||||
jedis.del(
|
||||
getKey(RedisKeyType.LATEST_SNAPSHOT, user.getUuid(), clusterId)
|
||||
);
|
||||
plugin.debug(String.format("[%s] Cleared %s on Redis", user.getUsername(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
plugin.debug(String.format("[%s] Cleared %s on Redis", user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred clearing user data on Redis", e);
|
||||
}
|
||||
@@ -291,11 +292,11 @@ public class RedisManager extends JedisPubSub {
|
||||
} else {
|
||||
if (jedis.del(key.getBytes(StandardCharsets.UTF_8)) == 0) {
|
||||
plugin.debug(String.format("[%s] %s key not set on Redis when attempting removal (%s)",
|
||||
user.getUsername(), RedisKeyType.DATA_CHECKOUT, key));
|
||||
user.getName(), RedisKeyType.DATA_CHECKOUT, key));
|
||||
return;
|
||||
}
|
||||
}
|
||||
plugin.debug(String.format("[%s] %s %s key %s Redis (%s)", user.getUsername(),
|
||||
plugin.debug(String.format("[%s] %s %s key %s Redis (%s)", user.getName(),
|
||||
checkedOut ? "Set" : "Removed", RedisKeyType.DATA_CHECKOUT, checkedOut ? "to" : "from", key));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred setting checkout to", e);
|
||||
@@ -310,13 +311,13 @@ public class RedisManager extends JedisPubSub {
|
||||
if (readData != null) {
|
||||
final String checkoutServer = new String(readData, StandardCharsets.UTF_8);
|
||||
plugin.debug(String.format("[%s] Waiting for %s %s key to be unset on Redis",
|
||||
user.getUsername(), checkoutServer, RedisKeyType.DATA_CHECKOUT));
|
||||
user.getName(), checkoutServer, RedisKeyType.DATA_CHECKOUT));
|
||||
return Optional.of(checkoutServer);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred getting a user's checkout key from Redis", e);
|
||||
}
|
||||
plugin.debug(String.format("[%s] %s key not set on Redis", user.getUsername(),
|
||||
plugin.debug(String.format("[%s] %s key not set on Redis", user.getName(),
|
||||
RedisKeyType.DATA_CHECKOUT));
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -354,7 +355,7 @@ public class RedisManager extends JedisPubSub {
|
||||
new byte[0]
|
||||
);
|
||||
plugin.debug(String.format("[%s] Set %s key to Redis",
|
||||
user.getUsername(), RedisKeyType.SERVER_SWITCH));
|
||||
user.getName(), RedisKeyType.SERVER_SWITCH));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch key from Redis", e);
|
||||
}
|
||||
@@ -373,11 +374,11 @@ public class RedisManager extends JedisPubSub {
|
||||
final byte[] dataByteArray = jedis.get(key);
|
||||
if (dataByteArray == null) {
|
||||
plugin.debug(String.format("[%s] Waiting for %s key from Redis",
|
||||
user.getUsername(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
return Optional.empty();
|
||||
}
|
||||
plugin.debug(String.format("[%s] Read %s key from Redis",
|
||||
user.getUsername(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
user.getName(), RedisKeyType.LATEST_SNAPSHOT));
|
||||
|
||||
// Consume the key (delete from redis)
|
||||
jedis.del(key);
|
||||
@@ -397,11 +398,11 @@ public class RedisManager extends JedisPubSub {
|
||||
final byte[] readData = jedis.get(key);
|
||||
if (readData == null) {
|
||||
plugin.debug(String.format("[%s] Waiting for %s key from Redis",
|
||||
user.getUsername(), RedisKeyType.SERVER_SWITCH));
|
||||
user.getName(), RedisKeyType.SERVER_SWITCH));
|
||||
return false;
|
||||
}
|
||||
plugin.debug(String.format("[%s] Read %s key from Redis",
|
||||
user.getUsername(), RedisKeyType.SERVER_SWITCH));
|
||||
user.getName(), RedisKeyType.SERVER_SWITCH));
|
||||
|
||||
// Consume the key (delete from redis)
|
||||
jedis.del(key);
|
||||
@@ -439,6 +440,97 @@ public class RedisManager extends JedisPubSub {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void bindMapIds(@NotNull String fromServer, int fromId, @NotNull String toServer, int toId) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(
|
||||
getMapIdKey(fromServer, fromId, toServer, clusterId),
|
||||
RedisKeyType.TTL_1_YEAR,
|
||||
String.valueOf(toId).getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
jedis.setex(
|
||||
getReversedMapIdKey(toServer, toId, clusterId),
|
||||
RedisKeyType.TTL_1_YEAR,
|
||||
String.format("%s:%s", fromServer, fromId).getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
plugin.debug(String.format("Bound map %s:%s -> %s:%s on Redis", fromServer, fromId, toServer, toId));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred binding map ids on Redis", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public Optional<Integer> getBoundMapId(@NotNull String fromServer, int fromId, @NotNull String toServer) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final byte[] readData = jedis.get(getMapIdKey(fromServer, fromId, toServer, clusterId));
|
||||
if (readData == null) {
|
||||
plugin.debug(String.format("[%s:%s] No bound map id for server %s Redis",
|
||||
fromServer, fromId, toServer));
|
||||
return Optional.empty();
|
||||
}
|
||||
plugin.debug(String.format("[%s:%s] Read bound map id for server %s from Redis",
|
||||
fromServer, fromId, toServer));
|
||||
|
||||
return Optional.of(Integer.parseInt(new String(readData, StandardCharsets.UTF_8)));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred getting bound map id from Redis", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public @Nullable Map.Entry<String, Integer> getReversedMapBound(@NotNull String toServer, int toId) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final byte[] readData = jedis.get(getReversedMapIdKey(toServer, toId, clusterId));
|
||||
if (readData == null) {
|
||||
plugin.debug(String.format("[%s:%s] No reversed map bound on Redis",
|
||||
toServer, toId));
|
||||
return null;
|
||||
}
|
||||
plugin.debug(String.format("[%s:%s] Read reversed map bound from Redis",
|
||||
toServer, toId));
|
||||
|
||||
String[] parts = new String(readData, StandardCharsets.UTF_8).split(":");
|
||||
return Map.entry(parts[0], Integer.parseInt(parts[1]));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred reading reversed map bound from Redis", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void setMapData(@NotNull String serverName, int mapId, byte[] data) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(
|
||||
getMapDataKey(serverName, mapId, clusterId),
|
||||
RedisKeyType.TTL_1_YEAR,
|
||||
data
|
||||
);
|
||||
plugin.debug(String.format("Set map data %s:%s on Redis", serverName, mapId));
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred setting map data on Redis", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public byte @Nullable [] getMapData(@NotNull String serverName, int mapId) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
final byte[] readData = jedis.get(getMapDataKey(serverName, mapId, clusterId));
|
||||
if (readData == null) {
|
||||
plugin.debug(String.format("[%s:%s] No map data on Redis",
|
||||
serverName, mapId));
|
||||
return null;
|
||||
}
|
||||
plugin.debug(String.format("[%s:%s] Read map data from Redis",
|
||||
serverName, mapId));
|
||||
|
||||
return readData;
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.SEVERE, "An exception occurred reading map data from Redis", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Blocking
|
||||
public void terminate() {
|
||||
enabled = false;
|
||||
@@ -459,4 +551,16 @@ public class RedisManager extends JedisPubSub {
|
||||
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid);
|
||||
}
|
||||
|
||||
private static byte[] getMapIdKey(@NotNull String fromServer, int fromId, @NotNull String toServer, @NotNull String clusterId) {
|
||||
return String.format("%s:%s:%s:%s", RedisKeyType.MAP_ID.getKeyPrefix(clusterId), fromServer, fromId, toServer).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static byte[] getReversedMapIdKey(@NotNull String toServer, int toId, @NotNull String clusterId) {
|
||||
return String.format("%s:%s:%s", RedisKeyType.MAP_ID_REVERSED.getKeyPrefix(clusterId), toServer, toId).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static byte[] getMapDataKey(@NotNull String serverName, int mapId, @NotNull String clusterId) {
|
||||
return String.format("%s:%s:%s", RedisKeyType.MAP_DATA.getKeyPrefix(clusterId), serverName, mapId).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ public abstract class DataSyncer {
|
||||
() -> user.completeSync(true, DataSnapshot.UpdateCause.NEW_USER, plugin)
|
||||
);
|
||||
} catch (Throwable e) {
|
||||
plugin.log(Level.WARNING, "Failed to set %s's data from the database".formatted(user.getUsername()), e);
|
||||
plugin.log(Level.WARNING, "Failed to set %s's data from the database".formatted(user.getName()), e);
|
||||
user.completeSync(false, DataSnapshot.UpdateCause.SYNCHRONIZED, plugin);
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public abstract class DataSyncer {
|
||||
if (plugin.isDisabling() || timesRun.getAndIncrement() > maxListenAttempts) {
|
||||
task.get().cancel();
|
||||
plugin.debug(String.format("[%s] Redis timed out after %s attempts; setting from database",
|
||||
user.getUsername(), timesRun.get()));
|
||||
user.getName(), timesRun.get()));
|
||||
setUserFromDatabase(user);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
||||
getPlugin().fireEvent(getPlugin().getPreSyncEvent(this, snapshot), (event) -> {
|
||||
if (!isOffline()) {
|
||||
getPlugin().debug(String.format("Applying snapshot (%s) to %s (cause: %s)",
|
||||
snapshot.getShortId(), getUsername(), cause.getDisplayName()
|
||||
snapshot.getShortId(), getName(), cause.getDisplayName()
|
||||
));
|
||||
UserDataHolder.super.applySnapshot(
|
||||
event.getData(), (succeeded) -> completeSync(succeeded, cause, getPlugin())
|
||||
|
||||
@@ -49,7 +49,7 @@ public class DataSnapshotList {
|
||||
.map(snapshot -> plugin.getLocales()
|
||||
.getRawLocale(!snapshot.isInvalid() ? "data_list_item" : "data_list_item_invalid",
|
||||
getNumberIcon(snapshotNumber.getAndIncrement()),
|
||||
dataOwner.getUsername(),
|
||||
dataOwner.getName(),
|
||||
snapshot.getId().toString(),
|
||||
snapshot.getShortId(),
|
||||
snapshot.isPinned() ? "※" : " ",
|
||||
@@ -63,10 +63,10 @@ public class DataSnapshotList {
|
||||
.orElse("• " + snapshot.getId())).toList(),
|
||||
plugin.getLocales().getBaseChatList(6)
|
||||
.setHeaderFormat(plugin.getLocales()
|
||||
.getRawLocale("data_list_title", dataOwner.getUsername(),
|
||||
.getRawLocale("data_list_title", dataOwner.getName(),
|
||||
"%first_item_on_page_index%", "%last_item_on_page_index%", "%total_items%")
|
||||
.orElse(""))
|
||||
.setCommand("/husksync:userdata list " + dataOwner.getUsername())
|
||||
.setCommand("/husksync:userdata list " + dataOwner.getName())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public class DataSnapshotOverview {
|
||||
// Title message, timestamp, owner and cause.
|
||||
final Locales locales = plugin.getLocales();
|
||||
locales.getLocale("data_manager_title", snapshot.getShortId(), snapshot.getId().toString(),
|
||||
dataOwner.getUsername(), dataOwner.getUuid().toString())
|
||||
dataOwner.getName(), dataOwner.getUuid().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
locales.getLocale("data_manager_timestamp",
|
||||
snapshot.getTimestamp().format(DateTimeFormatter
|
||||
@@ -107,13 +107,13 @@ public class DataSnapshotOverview {
|
||||
|
||||
if (user.hasPermission("husksync.command.inventory.edit")
|
||||
&& user.hasPermission("husksync.command.enderchest.edit")) {
|
||||
locales.getLocale("data_manager_item_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
||||
locales.getLocale("data_manager_item_buttons", dataOwner.getName(), snapshot.getId().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
locales.getLocale("data_manager_management_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
||||
locales.getLocale("data_manager_management_buttons", dataOwner.getName(), snapshot.getId().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
if (user.hasPermission("husksync.command.userdata.dump")) {
|
||||
locales.getLocale("data_manager_system_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
||||
locales.getLocale("data_manager_system_buttons", dataOwner.getName(), snapshot.getId().toString())
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public class UserDataDumper {
|
||||
@NotNull
|
||||
private String getFileName() {
|
||||
return new StringJoiner("_")
|
||||
.add(user.getUsername())
|
||||
.add(user.getName())
|
||||
.add(snapshot.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")))
|
||||
.add(snapshot.getSaveCause().name().toLowerCase(Locale.ENGLISH))
|
||||
.add(snapshot.getShortId())
|
||||
|
||||
@@ -29,4 +29,28 @@ CREATE TABLE IF NOT EXISTS `%user_data_table%`
|
||||
FOREIGN KEY (`player_uuid`) REFERENCES `%users_table%` (`uuid`) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_unicode_ci;
|
||||
COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
-- Create the map data table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS `%map_data_table%`
|
||||
(
|
||||
`server_name` varchar(32) NOT NULL,
|
||||
`map_id` int NOT NULL,
|
||||
`data` longblob NOT NULL,
|
||||
PRIMARY KEY (`server_name`, `map_id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
-- Create the map ids table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS `%map_ids_table%`
|
||||
(
|
||||
`from_server_name` varchar(32) NOT NULL,
|
||||
`from_id` int NOT NULL,
|
||||
`to_server_name` varchar(32) NOT NULL,
|
||||
`to_id` int NOT NULL,
|
||||
PRIMARY KEY (`from_server_name`, `from_id`, `to_server_name`),
|
||||
FOREIGN KEY (`from_server_name`, `from_id`) REFERENCES `%map_data_table%` (`server_name`, `map_id`) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_unicode_ci;
|
||||
|
||||
@@ -26,4 +26,26 @@ CREATE TABLE IF NOT EXISTS `%user_data_table%`
|
||||
PRIMARY KEY (`version_uuid`, `player_uuid`),
|
||||
FOREIGN KEY (`player_uuid`) REFERENCES `%users_table%` (`uuid`) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8
|
||||
COLLATE utf8_unicode_ci;
|
||||
COLLATE utf8_unicode_ci;
|
||||
|
||||
# Create the map data table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS `%map_data_table%`
|
||||
(
|
||||
`server_name` varchar(32) NOT NULL,
|
||||
`map_id` int NOT NULL,
|
||||
`data` longblob NOT NULL,
|
||||
PRIMARY KEY (`server_name`, `map_id`)
|
||||
) CHARACTER SET utf8
|
||||
COLLATE utf8_unicode_ci;
|
||||
|
||||
# Create the map ids table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS `%map_ids_table%`
|
||||
(
|
||||
`from_server_name` varchar(32) NOT NULL,
|
||||
`from_id` int NOT NULL,
|
||||
`to_server_name` varchar(32) NOT NULL,
|
||||
`to_id` int NOT NULL,
|
||||
PRIMARY KEY (`from_server_name`, `from_id`, `to_server_name`),
|
||||
FOREIGN KEY (`from_server_name`, `from_id`) REFERENCES `%map_data_table%` (`server_name`, `map_id`) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8
|
||||
COLLATE utf8_unicode_ci;
|
||||
|
||||
@@ -19,4 +19,24 @@ CREATE TABLE IF NOT EXISTS "%user_data_table%"
|
||||
|
||||
PRIMARY KEY (version_uuid, player_uuid),
|
||||
FOREIGN KEY (player_uuid) REFERENCES "%users_table%" (uuid) ON DELETE CASCADE
|
||||
);
|
||||
);
|
||||
|
||||
-- Create the map data table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS "%map_data_table%"
|
||||
(
|
||||
server_name varchar(32) NOT NULL,
|
||||
map_id int NOT NULL,
|
||||
data bytea NOT NULL,
|
||||
PRIMARY KEY (server_name, map_id)
|
||||
);
|
||||
|
||||
-- Create the map ids table if it does not exist
|
||||
CREATE TABLE IF NOT EXISTS "%map_ids_table%"
|
||||
(
|
||||
from_server_name varchar(32) NOT NULL,
|
||||
from_id int NOT NULL,
|
||||
to_server_name varchar(32) NOT NULL,
|
||||
to_id int NOT NULL,
|
||||
PRIMARY KEY (from_server_name, from_id, to_server_name),
|
||||
FOREIGN KEY (from_server_name, from_id) REFERENCES "%map_data_table%" (server_name, map_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user