Shrink built jar file size, work on MySQLPlayerDataBridge migrator

feat/data-edit-commands
William 3 years ago
parent f650db4438
commit 96c6a878c4

@ -13,10 +13,9 @@ shadowJar {
relocate 'org.jetbrains', 'net.william278.husksync.libraries' relocate 'org.jetbrains', 'net.william278.husksync.libraries'
relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'org.intellij', 'net.william278.husksync.libraries'
relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries'
relocate 'org.slf4j', 'net.william278.husksync.libraries.slf4j'
relocate 'com.google', 'net.william278.husksync.libraries' relocate 'com.google', 'net.william278.husksync.libraries'
//relocate 'org.xerial', 'net.william278.husksync.libraries'
relocate 'redis.clients', 'net.william278.husksync.libraries' relocate 'redis.clients', 'net.william278.husksync.libraries'
relocate 'org.json', 'net.william278.husksync.libraries.json'
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby' relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'

@ -1,28 +1,25 @@
dependencies { dependencies {
implementation project(path: ':common') implementation project(path: ':common')
implementation 'org.bstats:bstats-bukkit:3.0.0' implementation 'org.bstats:bstats-bukkit:3.0.0'
implementation 'net.william278:mpdbdataconverter:1.0' implementation 'net.william278:mpdbdataconverter:1.0'
implementation 'net.byteflux:libby-bukkit:1.1.5'
compileOnly 'redis.clients:jedis:4.2.3' compileOnly 'redis.clients:jedis:4.2.3'
compileOnly 'commons-io:commons-io:2.11.0' compileOnly 'commons-io:commons-io:2.11.0'
compileOnly 'de.themoep:minedown:1.7.1-SNAPSHOT' compileOnly 'de.themoep:minedown:1.7.1-SNAPSHOT'
compileOnly 'dev.dejvokep:boosted-yaml:1.2' compileOnly 'dev.dejvokep:boosted-yaml:1.2'
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
compileOnly 'com.zaxxer:HikariCP:5.0.1'
} }
shadowJar { shadowJar {
relocate 'org.apache', 'net.william278.husksync.libraries' relocate 'org.apache', 'net.william278.husksync.libraries'
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
relocate 'de.themoep', 'net.william278.husksync.libraries' relocate 'de.themoep', 'net.william278.husksync.libraries'
relocate 'org.jetbrains', 'net.william278.husksync.libraries' relocate 'org.jetbrains', 'net.william278.husksync.libraries'
relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'org.intellij', 'net.william278.husksync.libraries'
relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries'
relocate 'org.slf4j', 'net.william278.husksync.libraries.slf4j'
relocate 'com.google', 'net.william278.husksync.libraries' relocate 'com.google', 'net.william278.husksync.libraries'
//relocate 'org.xerial', 'net.william278.husksync.libraries'
relocate 'redis.clients', 'net.william278.husksync.libraries' relocate 'redis.clients', 'net.william278.husksync.libraries'
relocate 'org.json', 'net.william278.husksync.libraries.json'
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby' relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'

@ -6,7 +6,9 @@ import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
import net.william278.husksync.command.*; import net.william278.husksync.command.BukkitCommand;
import net.william278.husksync.command.BukkitCommandType;
import net.william278.husksync.command.Permission;
import net.william278.husksync.config.Locales; import net.william278.husksync.config.Locales;
import net.william278.husksync.config.Settings; import net.william278.husksync.config.Settings;
import net.william278.husksync.data.CompressedDataAdapter; import net.william278.husksync.data.CompressedDataAdapter;
@ -19,6 +21,9 @@ import net.william278.husksync.event.BukkitEventCannon;
import net.william278.husksync.event.EventCannon; import net.william278.husksync.event.EventCannon;
import net.william278.husksync.listener.BukkitEventListener; import net.william278.husksync.listener.BukkitEventListener;
import net.william278.husksync.listener.EventListener; import net.william278.husksync.listener.EventListener;
import net.william278.husksync.migrator.LegacyMigrator;
import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.migrator.MpdbMigrator;
import net.william278.husksync.player.BukkitPlayer; import net.william278.husksync.player.BukkitPlayer;
import net.william278.husksync.player.OnlineUser; import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.redis.RedisManager; import net.william278.husksync.redis.RedisManager;
@ -27,6 +32,7 @@ import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault; import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -40,24 +46,16 @@ import java.util.stream.Collectors;
public class BukkitHuskSync extends JavaPlugin implements HuskSync { public class BukkitHuskSync extends JavaPlugin implements HuskSync {
private Database database; private Database database;
private RedisManager redisManager; private RedisManager redisManager;
private Logger logger; private Logger logger;
private ResourceReader resourceReader; private ResourceReader resourceReader;
private EventListener eventListener; private EventListener eventListener;
private DataAdapter dataAdapter; private DataAdapter dataAdapter;
private DataEditor dataEditor; private DataEditor dataEditor;
private EventCannon eventCannon; private EventCannon eventCannon;
private Settings settings; private Settings settings;
private Locales locales; private Locales locales;
private List<Migrator> availableMigrators;
private static BukkitHuskSync instance; private static BukkitHuskSync instance;
/** /**
@ -72,18 +70,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
@Override @Override
public void onLoad() { public void onLoad() {
instance = this; instance = this;
/*getLogger().log(Level.INFO, "Loading runtime libraries...");
final BukkitLibraryManager libraryManager = new BukkitLibraryManager(this);
final Library[] libraries = new Library[]{
Library.builder().groupId("redis{}clients")
.artifactId("jedis")
.version("4.2.3")
.id("jedis")
.build()
};
libraryManager.addMavenCentral();
Arrays.stream(libraries).forEach(libraryManager::loadLibrary);
getLogger().log(Level.INFO, "Successfully loaded runtime libraries.");*/
} }
@Override @Override
@ -127,6 +113,17 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
dataEditor = new DataEditor(locales); dataEditor = new DataEditor(locales);
} }
return succeeded; return succeeded;
}).thenApply(succeeded -> {
// Prepare migrators
if (succeeded) {
availableMigrators = new ArrayList<>();
availableMigrators.add(new LegacyMigrator(this));
final Plugin mySqlPlayerDataBridge = Bukkit.getPluginManager().getPlugin("MySqlPlayerDataBridge");
if (mySqlPlayerDataBridge != null) {
availableMigrators.add(new MpdbMigrator(this, mySqlPlayerDataBridge));
}
}
return succeeded;
}).thenApply(succeeded -> { }).thenApply(succeeded -> {
// Establish connection to the database // Establish connection to the database
if (succeeded) { if (succeeded) {
@ -254,6 +251,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
return eventCannon; return eventCannon;
} }
@NotNull
@Override
public List<Migrator> getAvailableMigrators() {
return null;
}
@Override @Override
public @NotNull Settings getSettings() { public @NotNull Settings getSettings() {
return settings; return settings;

@ -48,10 +48,10 @@ public class BukkitCommand implements CommandExecutor, TabExecutor {
if (sender instanceof Player player) { if (sender instanceof Player player) {
this.command.onExecute(BukkitPlayer.adapt(player), args); this.command.onExecute(BukkitPlayer.adapt(player), args);
} else { } else {
if (command instanceof ConsoleExecutable consoleExecutable) { if (this.command instanceof ConsoleExecutable consoleExecutable) {
consoleExecutable.onConsoleExecute(args); consoleExecutable.onConsoleExecute(args);
} else { } else {
plugin.getLocales().getLocale("error_in_game_only"). plugin.getLocales().getLocale("error_in_game_command_only").
ifPresent(locale -> sender.spigot().sendMessage(locale.toComponent())); ifPresent(locale -> sender.spigot().sendMessage(locale.toComponent()));
} }
} }

@ -0,0 +1,243 @@
package net.william278.husksync.migrator;
import com.zaxxer.hikari.HikariDataSource;
import net.william278.husksync.BukkitHuskSync;
import net.william278.husksync.config.Settings;
import net.william278.husksync.data.*;
import net.william278.husksync.player.User;
import net.william278.mpdbconverter.MPDBConverter;
import org.bukkit.Bukkit;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class MpdbMigrator extends Migrator {
private final MPDBConverter mpdbConverter;
private String sourceHost;
private int sourcePort;
private String sourceUsername;
private String sourcePassword;
private String sourceDatabase;
private String sourceInventoryTable;
private String sourceEnderChestTable;
private String sourceExperienceTable;
public MpdbMigrator(@NotNull BukkitHuskSync plugin, @NotNull Plugin mySqlPlayerDataBridge) {
super(plugin);
this.mpdbConverter = MPDBConverter.getInstance(mySqlPlayerDataBridge);
this.sourceHost = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_HOST);
this.sourcePort = plugin.getSettings().getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
this.sourceUsername = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
this.sourcePassword = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
this.sourceDatabase = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_NAME);
this.sourceInventoryTable = "mpdb_inventory";
this.sourceEnderChestTable = "mpdb_enderchest";
this.sourceExperienceTable = "mpdb_experience";
}
@Override
public CompletableFuture<Boolean> start() {
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
final long startTime = System.currentTimeMillis();
return CompletableFuture.supplyAsync(() -> {
// Create jdbc driver connection url
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
// Create a new data source for the mpdb converter
try (final HikariDataSource connectionPool = new HikariDataSource()) {
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
connectionPool.setJdbcUrl(jdbcUrl);
connectionPool.setUsername(sourceUsername);
connectionPool.setPassword(sourcePassword);
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database...");
final List<MpdbData> dataToMigrate = new ArrayList<>();
try (final Connection connection = connectionPool.getConnection()) {
try (final PreparedStatement statement = connection.prepareStatement("""
SELECT `player_uuid`, `player_name`, `inventory`, `armor`, `enderchest`, `exp_lvl`, `exp`, `total_exp`
FROM `%source_inventory_table%`
INNER JOIN `%source_ender_chest_table%`
ON `%source_inventory_table%`.`player_uuid` = `%source_ender_chest_table%`.`player_uuid`
INNER JOIN `%source_experience_table%`
ON `%source_inventory_table%`.`player_uuid` = `%source_experience_table%`.`player_uuid`;
""".replaceAll(Pattern.quote("%source_inventory_table%"), sourceInventoryTable).replaceAll(Pattern.quote("%source_ender_chest_table%"), sourceEnderChestTable).replaceAll(Pattern.quote("%source_experience_table%"), sourceExperienceTable))) {
try (final ResultSet resultSet = statement.executeQuery()) {
int playersMigrated = 0;
while (resultSet.next()) {
dataToMigrate.add(new MpdbData(
new User(UUID.fromString(resultSet.getString("player_uuid")),
resultSet.getString("player_name")),
resultSet.getString("inventory"),
resultSet.getString("armor"),
resultSet.getString("enderchest"),
resultSet.getInt("exp_lvl"),
resultSet.getInt("exp"),
resultSet.getInt("total_exp")
));
playersMigrated++;
if (playersMigrated % 25 == 0) {
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players...");
}
}
}
}
}
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the MySQLPlayerDataBridge database!");
plugin.getLoggingAdapter().log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data...");
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter).thenAccept(convertedData ->
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))));
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
return true;
} catch (Exception e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating data: " + e.getMessage());
return false;
}
});
}
@Override
public void handleConfigurationCommand(@NotNull String[] args) {
if (args.length == 2) {
if (switch (args[0].toLowerCase()) {
case "host" -> {
this.sourceHost = args[1];
yield true;
}
case "port" -> {
try {
this.sourcePort = Integer.parseInt(args[1]);
yield true;
} catch (NumberFormatException e) {
yield false;
}
}
case "username" -> {
this.sourceUsername = args[1];
yield true;
}
case "password" -> {
this.sourcePassword = args[1];
yield true;
}
case "database" -> {
this.sourceDatabase = args[1];
yield true;
}
case "inventory_table" -> {
this.sourceInventoryTable = args[1];
yield true;
}
case "ender_chest_table" -> {
this.sourceEnderChestTable = args[1];
yield true;
}
case "experience_table" -> {
this.sourceExperienceTable = args[1];
yield true;
}
default -> false;
}) {
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " + args[1]);
} else {
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " + args[1] + " (is it a valid option?)");
}
}
}
@NotNull
@Override
public String getIdentifier() {
return "mpdb";
}
@NotNull
@Override
public String getName() {
return "MySQLPlayerDataBridge";
}
@NotNull
@Override
public String getHelpMenu() {
return """
=== MySQLPlayerDataBridge Migration Wizard ==========
This will migrate inventories, ender chests and XP
from the MySQLPlayerDataBridge plugin to HuskSync.
To prevent excessive migration times, other non-vital
data will not be transferred.
STEP 1] Please ensure no players are on the server.
STEP 2] HuskSync will need to connect to the database
used to hold the source MySQLPlayerDataBridge data.
Please check these database parameters are OK:
- host: %source_host%
- port: %source_port%
- username: %source_username%
- password: %source_password%
- database: %source_database%
- inventory_table: %source_inventory_table%
- ender_chest_table: %source_ender_chest_table%
- experience_table: %source_xp_table%
If any of these are not correct, please correct them
using the command:
"husksync migrate mpdb set <parameter> <host>"
(e.g.: "husksync migrate mpdb set host 123.456.789")
STEP 3] HuskSync will migrate data into the database
tables configures in the config.yml file of this
server. Please make sure you're happy with this
before proceeding.
STEP 4] To start the migration, please run:
"husksync migrate mpdb start"
""";
}
private record MpdbData(@NotNull User user, @NotNull String serializedInventory,
@NotNull String serializedArmor, @NotNull String serializedEnderChest,
int expLevel, float expProgress, int totalExp) {
@NotNull
public CompletableFuture<UserData> toUserData(@NotNull MPDBConverter converter) {
return CompletableFuture.supplyAsync(() -> {
// Combine inventory and armour
final Inventory inventory = Bukkit.createInventory(null, InventoryType.PLAYER);
inventory.setContents(converter.getItemStackFromSerializedData(serializedInventory));
final ItemStack[] armor = converter.getItemStackFromSerializedData(serializedArmor).clone();
for (int i = 36; i < 36 + armor.length; i++) {
inventory.setItem(i, armor[i - 36]);
}
// Create user data record
return new UserData(
new StatusData(20, 20, 0, 20, 10,
1, 0, totalExp, expLevel, expProgress, "SURVIVAL",
false),
new ItemData(BukkitSerializer.serializeItemStackArray(inventory.getContents()).join()),
new ItemData(BukkitSerializer.serializeItemStackArray(converter
.getItemStackFromSerializedData(serializedEnderChest)).join()),
new PotionEffectData(""), new ArrayList<>(),
new StatisticsData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()),
new LocationData("world", UUID.randomUUID(), "NORMAL", 0, 0, 0,
0f, 0f),
new PersistentDataContainerData(new HashMap<>()));
});
}
}
}

@ -8,6 +8,8 @@ website: 'https://william278.net'
softdepend: [ MysqlPlayerDataBridge ] softdepend: [ MysqlPlayerDataBridge ]
libraries: libraries:
- 'mysql:mysql-connector-java:8.0.29' - 'mysql:mysql-connector-java:8.0.29'
- 'org.xerial.snappy:snappy-java:1.1.8.4'
- 'dev.dejvokep:boosted-yaml:1.2'
commands: commands:
husksync: husksync:
usage: '/husksync <update|info|reload|migrate>' usage: '/husksync <update|info|reload|migrate>'

@ -1,24 +1,26 @@
dependencies { dependencies {
implementation 'commons-io:commons-io:2.11.0' implementation 'commons-io:commons-io:2.11.0'
implementation 'dev.dejvokep:boosted-yaml:1.2'
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT' implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
implementation 'com.zaxxer:HikariCP:5.0.1'
implementation 'com.google.code.gson:gson:2.9.0' implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.xerial.snappy:snappy-java:1.1.8.4' implementation('redis.clients:jedis:4.2.3') {
implementation 'redis.clients:jedis:4.2.3' exclude module: 'slf4j-api'
}
implementation ('com.zaxxer:HikariCP:5.0.1') {
exclude module: 'slf4j-api'
}
compileOnly 'dev.dejvokep:boosted-yaml:1.2'
compileOnly 'org.xerial.snappy:snappy-java:1.1.8.4'
compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
} }
shadowJar { shadowJar {
relocate 'org.apache', 'net.william278.husksync.libraries' relocate 'org.apache', 'net.william278.husksync.libraries'
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
relocate 'de.themoep', 'net.william278.husksync.libraries' relocate 'de.themoep', 'net.william278.husksync.libraries'
relocate 'org.jetbrains', 'net.william278.husksync.libraries' relocate 'org.jetbrains', 'net.william278.husksync.libraries'
relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'org.intellij', 'net.william278.husksync.libraries'
relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries'
relocate 'org.slf4j', 'net.william278.husksync.libraries.slf4j'
relocate 'com.google', 'net.william278.husksync.libraries' relocate 'com.google', 'net.william278.husksync.libraries'
//relocate 'org.xerial', 'net.william278.husksync.libraries'
relocate 'redis.clients', 'net.william278.husksync.libraries' relocate 'redis.clients', 'net.william278.husksync.libraries'
relocate 'org.json', 'net.william278.husksync.libraries.json'
} }

@ -6,11 +6,13 @@ import net.william278.husksync.data.DataAdapter;
import net.william278.husksync.editor.DataEditor; import net.william278.husksync.editor.DataEditor;
import net.william278.husksync.database.Database; import net.william278.husksync.database.Database;
import net.william278.husksync.event.EventCannon; import net.william278.husksync.event.EventCannon;
import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.player.OnlineUser; import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.redis.RedisManager; import net.william278.husksync.redis.RedisManager;
import net.william278.husksync.util.Logger; import net.william278.husksync.util.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -32,6 +34,8 @@ public interface HuskSync {
@NotNull EventCannon getEventCannon(); @NotNull EventCannon getEventCannon();
@NotNull List<Migrator> getAvailableMigrators();
@NotNull Settings getSettings(); @NotNull Settings getSettings();
@NotNull Locales getLocales(); @NotNull Locales getLocales();

@ -82,7 +82,7 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
@Override @Override
public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) { public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) {
return plugin.getOnlineUsers().stream().map(user -> user.username) return plugin.getOnlineUsers().stream().map(user -> user.username)
.filter(argument -> argument.startsWith(args.length >= 1 ? args[1] : "")) .filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
.sorted().collect(Collectors.toList()); .sorted().collect(Collectors.toList());
} }

@ -3,12 +3,14 @@ package net.william278.husksync.command;
import de.themoep.minedown.MineDown; import de.themoep.minedown.MineDown;
import net.william278.husksync.HuskSync; import net.william278.husksync.HuskSync;
import net.william278.husksync.config.Locales; import net.william278.husksync.config.Locales;
import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.player.OnlineUser; import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.util.UpdateChecker; import net.william278.husksync.util.UpdateChecker;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -50,11 +52,13 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
return; return;
} }
plugin.reload(); plugin.reload();
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| Reloaded config & message files.](#00fb9a)")); plugin.getLocales().getLocale("reload_complete").ifPresent(player::sendMessage);
} }
case "migrate" ->
plugin.getLocales().getLocale("error_console_command_only").ifPresent(player::sendMessage);
default -> plugin.getLocales().getLocale("error_invalid_syntax", default -> plugin.getLocales().getLocale("error_invalid_syntax",
"/husksync <update/info/reload>") "/husksync <update/info/reload>")
.ifPresent(player::sendMessage); .ifPresent(player::sendMessage);
} }
} }
@ -74,13 +78,46 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files."); plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files.");
} }
case "migrate" -> { case "migrate" -> {
//todo - MPDB migrator if (args.length < 2) {
plugin.getLoggingAdapter().log(Level.INFO,
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
logMigratorsList();
return;
}
final Optional<Migrator> selectedMigrator = plugin.getAvailableMigrators().stream().filter(availableMigrator ->
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
selectedMigrator.ifPresentOrElse(migrator -> {
if (args.length < 3) {
plugin.getLoggingAdapter().log(Level.INFO,
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + "<start/set>");
return;
}
switch (args[2]) {
case "start" -> migrator.start();
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
default -> plugin.getLoggingAdapter().log(Level.INFO,
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + "<start/set>");
}
}, () -> {
plugin.getLoggingAdapter().log(Level.INFO,
"Please specify a valid migrator.\n" +
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
logMigratorsList();
});
} }
default -> plugin.getLoggingAdapter().log(Level.INFO, default -> plugin.getLoggingAdapter().log(Level.INFO,
"Invalid syntax. Console usage: \"husksync <update/info/reload/migrate>\""); "Invalid syntax. Console usage: \"husksync <update/info/reload/migrate>\"");
} }
} }
private void logMigratorsList() {
plugin.getLoggingAdapter().log(Level.INFO,
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
plugin.getAvailableMigrators().stream()
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
.collect(Collectors.joining("\n")));
}
@Override @Override
public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) { public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) {
return Arrays.stream(COMMAND_ARGUMENTS) return Arrays.stream(COMMAND_ARGUMENTS)

@ -81,7 +81,7 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
@Override @Override
public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) { public List<String> onTabComplete(@NotNull OnlineUser player, @NotNull String[] args) {
return plugin.getOnlineUsers().stream().map(user -> user.username) return plugin.getOnlineUsers().stream().map(user -> user.username)
.filter(argument -> argument.startsWith(args.length >= 1 ? args[1] : "")) .filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
.sorted().collect(Collectors.toList()); .sorted().collect(Collectors.toList());
} }
} }

@ -58,10 +58,18 @@ public enum DataSaveCause {
* @since 2.0 * @since 2.0
*/ */
API, API,
/**
MPDB_IMPORT, * Indicates data was saved from being imported from MySQLPlayerDataBridge
LEGACY_IMPORT, *
MANUAL_IMPORT, * @since 2.0
*/
MPDB_MIGRATION,
/**
* Indicates data was saved from being imported from a legacy version (v1.x)
*
* @since 2.0
*/
LEGACY_MIGRATION,
/** /**
* Indicates data was saved by an unknown cause. * Indicates data was saved by an unknown cause.
* </p> * </p>

@ -13,6 +13,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/** /**
* Provides methods for displaying and editing user data * Provides methods for displaying and editing user data
@ -121,16 +122,18 @@ public class DataEditor {
private @NotNull String generateAdvancementPreview(@NotNull List<AdvancementData> advancementData) { private @NotNull String generateAdvancementPreview(@NotNull List<AdvancementData> advancementData) {
final StringJoiner joiner = new StringJoiner("\n"); final StringJoiner joiner = new StringJoiner("\n");
final List<AdvancementData> advancementsToPreview = advancementData.stream().filter(dataItem ->
!dataItem.key.startsWith("minecraft:recipes/")).toList();
final int PREVIEW_SIZE = 8; final int PREVIEW_SIZE = 8;
for (int i = 0; i < advancementData.size(); i++) { for (int i = 0; i < advancementsToPreview.size(); i++) {
joiner.add(advancementData.get(i).key); joiner.add(advancementsToPreview.get(i).key);
if (i >= PREVIEW_SIZE) { if (i >= PREVIEW_SIZE) {
break; break;
} }
} }
final int remainingAdvancements = advancementData.size() - PREVIEW_SIZE; final int remainingAdvancements = advancementsToPreview.size() - PREVIEW_SIZE;
if (remainingAdvancements > 0) { if (remainingAdvancements > 0) {
joiner.add(locales.getRawLocale("data_manager_advancement_preview_remaining", joiner.add(locales.getRawLocale("data_manager_advancements_preview_remaining",
Integer.toString(remainingAdvancements)).orElse("+" + remainingAdvancements + "…")); Integer.toString(remainingAdvancements)).orElse("+" + remainingAdvancements + "…"));
} }
return joiner.toString(); return joiner.toString();

@ -0,0 +1,43 @@
package net.william278.husksync.migrator;
import net.william278.husksync.HuskSync;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
//todo: implement this
public class LegacyMigrator extends Migrator {
public LegacyMigrator(@NotNull HuskSync plugin) {
super(plugin);
}
@Override
public CompletableFuture<Boolean> start() {
return null;
}
@Override
public void handleConfigurationCommand(@NotNull String[] args) {
}
@NotNull
@Override
public String getIdentifier() {
return "legacy";
}
@NotNull
@Override
public String getName() {
return "HuskSync v1.x --> v2.x";
}
@NotNull
@Override
public String getHelpMenu() {
return null;
}
}

@ -0,0 +1,38 @@
package net.william278.husksync.migrator;
import net.william278.husksync.HuskSync;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public abstract class Migrator {
protected final HuskSync plugin;
protected Migrator(@NotNull HuskSync plugin) {
this.plugin = plugin;
}
/**
* Start the migrator
*
* @return A future that will be completed when the migrator is done
*/
public abstract CompletableFuture<Boolean> start();
/**
* Handle a command that sets migrator configuration parameters
* @param args The command arguments
*/
public abstract void handleConfigurationCommand(@NotNull String[] args);
@NotNull
public abstract String getIdentifier();
@NotNull
public abstract String getName();
@NotNull
public abstract String getHelpMenu();
}

@ -1,15 +1,10 @@
synchronisation_complete: '[⏵ Data synchronised!](#00fb9a)' synchronisation_complete: '[⏵ Data synchronised!](#00fb9a)'
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)' reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)'
error_invalid_syntax: '[Error:](#ff3300) [Incorrect syntax. Usage: %1%](#ff7e5e)' error_invalid_syntax: '[Error:](#ff3300) [Incorrect syntax. Usage: %1%](#ff7e5e)'
error_invalid_player: '[Error:](#ff3300) [Could not find that player.](#ff7e5e)' error_invalid_player: '[Error:](#ff3300) [Could not find a player by that name.](#ff7e5e)'
error_no_permission: '[Error:](#ff3300) [You do not have permission to execute this command](#ff7e5e)' error_no_permission: '[Error:](#ff3300) [You do not have permission to execute this command](#ff7e5e)'
error_cannot_view_inventory_online: '[Error:](#ff3300) [You can''t access the inventory of an online player through HuskSync](#ff7e5e)' error_console_command_only: '[Error:](#ff3300) [That command can only be run through console](#ff7e5e)'
error_cannot_view_ender_chest_online: '[Error:](#ff3300) [You can''t access the ender chest of an online player through HuskSync](#ff7e5e)' error_in_game_command_only: 'Error: That command can only be used in-game.'
error_cannot_view_own_inventory: '[Error:](#ff3300) [You can''t access your own inventory!](#ff7e5e)'
error_cannot_view_own_ender_chest: '[Error:](#ff3300) [You can''t access your own ender chest!](#ff7e5e)'
error_console_command_only: '[Error:](#ff3300) [That command can only be run through the %1% console](#ff7e5e)'
error_no_servers_proxied: '[Error:](#ff3300) [Failed to process operation; no servers are online that have HuskSync installed. Please ensure HuskSync is installed on both the Proxy server and all servers you wish to synchronise data between.](#ff7e5e)'
error_invalid_cluster: '[Error:](#ff3300) [Please specify the ID of a valid cluster.](#ff7e5e)'
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)' error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)' error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
inventory_viewer_menu_title: '&0%1%''s Inventory' inventory_viewer_menu_title: '&0%1%''s Inventory'
@ -23,9 +18,9 @@ data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\\n&7What
data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)' data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7⚠ Based on in-game statistics)' data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7⚠ Based on in-game statistics)'
data_manager_item_buttons: '[[🪣 Inventory…]](color=#a17b5f-#f5b98c show_text=&7Click to view run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Click to view run_command=/enderchest %1% %2%)\\n' data_manager_item_buttons: '[[🪣 Inventory…]](color=#a17b5f-#f5b98c show_text=&7Click to view run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Click to view run_command=/enderchest %1% %2%)\\n'
data_manager_management_buttons: '[Manage:](gray) [[❌ Delete…]](#ff3300 show_text=&7Click to delete this user data run_command=/userdata restore %1% %2%) [[⏪ Restore…]](#00fb9a show_text=&7Click to restore this user data.\\n&#ff3300&⚠ Warning: %1%''s current data will be overwritten! run_command=/userdata delete %1% %2%)\\n' data_manager_management_buttons: '[Manage:](gray) [[❌ Delete…]](#ff3300 show_text=&7Click to delete this snapshot of user data.\\nThis will not affect the user''s current data.\\n&#ff3300&⚠ This cannot be undone! run_command=/userdata delete %1% %2%) [[⏪ Restore…]](#00fb9a show_text=&7Click to restore this user data.\\nThis will set the user''s data to this snapshot.\\n&#ff3300&⚠ %1%''s current data will be overwritten! run_command=/userdata restore %1% %2%)\\n'
data_manager_advancement_preview_remaining: '&7+%1% more…' data_manager_advancements_preview_remaining: '&7and %1% more…'
data_list_title: '[List of](#00fb9a) [%1%](#00fb9a bold show_text=&7UUID: %2%)[''s user data snapshots:](#00fb9a)\\n' data_list_title: '[List of](#00fb9a) [%1%](#00fb9a bold show_text=&7UUID: %2%)[''s user data snapshots:](#00fb9a)\\n'
data_list_item: '[%1%](gray run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Version timestamp&7When the data was saved run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Version UUID:&7%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Save cause&7What caused the data to be saved run_command=/userdata view %6% %4%)' data_list_item: '[%1%](gray show_text=&7Snapshot %3% run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Version timestamp&7\\n&8When the data was saved run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Version UUID:&7\\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Save cause\\n&8What caused the data to be saved run_command=/userdata view %6% %4%)'
data_deleted: '[❌ Successfully deleted user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\\n&7%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\\n&7%4%)' data_deleted: '[❌ Successfully deleted user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\\n&7%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\\n&7%4%)'
data_restored: '[⏪ Successfully restored](#00fb9a) [%1%](#00fb9a show_text=&7Player UUID:\\n&7%2%)[''s current user data from snapshot](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\\n&7%4%)' data_restored: '[⏪ Successfully restored](#00fb9a) [%1%](#00fb9a show_text=&7Player UUID:\\n&7%2%)[''s current user data from snapshot](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\\n&7%4%)'
Loading…
Cancel
Save