Refactor; consolidate Logger and ResourceReader, simplify certain method arguments

feat/data-edit-commands
William 2 years ago
parent 33588c2345
commit f6663f0c09

@ -24,10 +24,6 @@ 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;
import net.william278.husksync.util.BukkitLogger;
import net.william278.husksync.util.BukkitResourceReader;
import net.william278.husksync.util.Logger;
import net.william278.husksync.util.ResourceReader;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
@ -54,8 +50,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
private static final int METRICS_ID = 13140; private static final int METRICS_ID = 13140;
private Database database; private Database database;
private RedisManager redisManager; private RedisManager redisManager;
private Logger logger;
private ResourceReader resourceReader;
private EventListener eventListener; private EventListener eventListener;
private DataAdapter dataAdapter; private DataAdapter dataAdapter;
private EventCannon eventCannon; private EventCannon eventCannon;
@ -85,19 +79,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
// Initialize HuskSync // Initialize HuskSync
final AtomicBoolean initialized = new AtomicBoolean(true); final AtomicBoolean initialized = new AtomicBoolean(true);
try { try {
// Set the logging adapter and resource reader
this.logger = new BukkitLogger(this.getLogger());
this.resourceReader = new BukkitResourceReader(this);
// Create adventure audience // Create adventure audience
this.audiences = BukkitAudiences.create(this); this.audiences = BukkitAudiences.create(this);
// Load settings and locales // Load settings and locales
getLoggingAdapter().log(Level.INFO, "Loading plugin configuration settings & locales..."); log(Level.INFO, "Loading plugin configuration settings & locales...");
initialized.set(reload().join()); initialized.set(reload().join());
if (initialized.get()) { if (initialized.get()) {
logger.showDebugLogs(settings.debugLogging); log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
getLoggingAdapter().log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
} else { } else {
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales"); throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
} }
@ -121,11 +110,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
} }
// Prepare database connection // Prepare database connection
this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter, eventCannon); this.database = new MySqlDatabase(this);
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database..."); log(Level.INFO, "Attempting to establish connection to the database...");
initialized.set(this.database.initialize()); initialized.set(this.database.initialize());
if (initialized.get()) { if (initialized.get()) {
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the database"); log(Level.INFO, "Successfully established a connection to the database");
} else { } else {
throw new HuskSyncInitializationException("Failed to establish a connection to the database. " + throw new HuskSyncInitializationException("Failed to establish a connection to the database. " +
"Please check the supplied database credentials in the config file"); "Please check the supplied database credentials in the config file");
@ -133,22 +122,22 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
// Prepare redis connection // Prepare redis connection
this.redisManager = new RedisManager(this); this.redisManager = new RedisManager(this);
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server..."); log(Level.INFO, "Attempting to establish connection to the Redis server...");
initialized.set(this.redisManager.initialize()); initialized.set(this.redisManager.initialize());
if (initialized.get()) { if (initialized.get()) {
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the Redis server"); log(Level.INFO, "Successfully established a connection to the Redis server");
} else { } else {
throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " + throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " +
"Please check the supplied Redis credentials in the config file"); "Please check the supplied Redis credentials in the config file");
} }
// Register events // Register events
getLoggingAdapter().log(Level.INFO, "Registering events..."); log(Level.INFO, "Registering events...");
this.eventListener = new BukkitEventListener(this); this.eventListener = new BukkitEventListener(this);
getLoggingAdapter().log(Level.INFO, "Successfully registered events listener"); log(Level.INFO, "Successfully registered events listener");
// Register permissions // Register permissions
getLoggingAdapter().log(Level.INFO, "Registering permissions & commands..."); log(Level.INFO, "Registering permissions & commands...");
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager() Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) { .addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
case EVERYONE -> PermissionDefault.TRUE; case EVERYONE -> PermissionDefault.TRUE;
@ -163,32 +152,32 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand); new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand);
} }
} }
getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands"); log(Level.INFO, "Successfully registered permissions & commands");
// Hook into plan // Hook into plan
if (Bukkit.getPluginManager().getPlugin("Plan") != null) { if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
getLoggingAdapter().log(Level.INFO, "Enabling Plan integration..."); log(Level.INFO, "Enabling Plan integration...");
new PlanHook(database, logger).hookIntoPlan(); new PlanHook(this).hookIntoPlan();
getLoggingAdapter().log(Level.INFO, "Plan integration enabled!"); log(Level.INFO, "Plan integration enabled!");
} }
// Hook into bStats metrics // Hook into bStats metrics
try { try {
new Metrics(this, METRICS_ID); new Metrics(this, METRICS_ID);
} catch (final Exception e) { } catch (final Exception e) {
getLoggingAdapter().log(Level.WARNING, "Skipped bStats metrics initialization due to an exception."); log(Level.WARNING, "Skipped bStats metrics initialization due to an exception.");
} }
// Check for updates // Check for updates
if (settings.checkForUpdates) { if (settings.checkForUpdates) {
getLoggingAdapter().log(Level.INFO, "Checking for updates..."); log(Level.INFO, "Checking for updates...");
getLatestVersionIfOutdated().thenAccept(newestVersion -> getLatestVersionIfOutdated().thenAccept(newestVersion ->
newestVersion.ifPresent(newVersion -> getLoggingAdapter().log(Level.WARNING, newestVersion.ifPresent(newVersion -> log(Level.WARNING,
"An update is available for HuskSync, v" + newVersion "An update is available for HuskSync, v" + newVersion
+ " (Currently running v" + getPluginVersion() + ")"))); + " (Currently running v" + getPluginVersion() + ")")));
} }
} catch (HuskSyncInitializationException exception) { } catch (HuskSyncInitializationException exception) {
getLoggingAdapter().log(Level.SEVERE, """ log(Level.SEVERE, """
*************************************************** ***************************************************
Failed to initialize HuskSync! Failed to initialize HuskSync!
@ -203,14 +192,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
.replaceAll("%error_message%", exception.getMessage())); .replaceAll("%error_message%", exception.getMessage()));
initialized.set(false); initialized.set(false);
} catch (Exception exception) { } catch (Exception exception) {
getLoggingAdapter().log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception); log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
initialized.set(false); initialized.set(false);
} finally { } finally {
// Validate initialization // Validate initialization
if (initialized.get()) { if (initialized.get()) {
getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion()); log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
} else { } else {
getLoggingAdapter().log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled"); log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled");
getServer().getPluginManager().disablePlugin(this); getServer().getPluginManager().disablePlugin(this);
} }
} }
@ -221,7 +210,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
if (this.eventListener != null) { if (this.eventListener != null) {
this.eventListener.handlePluginDisable(); this.eventListener.handlePluginDisable();
} }
getLoggingAdapter().log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion()); log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
} }
@Override @Override
@ -275,14 +264,8 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
} }
@Override @Override
public @NotNull Logger getLoggingAdapter() { public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
return logger; getLogger().log(level, message, throwable);
}
@NotNull
@Override
public ResourceReader getResourceReader() {
return resourceReader;
} }
@NotNull @NotNull
@ -327,7 +310,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
return true; return true;
} catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException | } catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException |
InstantiationException e) { InstantiationException e) {
getLoggingAdapter().log(Level.SEVERE, "Failed to load data from the config", e); log(Level.SEVERE, "Failed to load data from the config", e);
return false; return false;
} }
}); });

@ -18,13 +18,12 @@ public class BrigadierUtil {
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand, protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
@NotNull CommandBase command) { @NotNull CommandBase command) {
// Register command descriptions via commodore (brigadier wrapper) // Register command descriptions via commodore (brigadier wrapper)
try (InputStream pluginFile = plugin.getResourceReader() try (InputStream pluginFile = plugin.getResource("commodore/" + command.command + ".commodore")) {
.getResource("commodore/" + command.command + ".commodore")) {
CommodoreProvider.getCommodore(plugin).register(pluginCommand, CommodoreProvider.getCommodore(plugin).register(pluginCommand,
CommodoreFileReader.INSTANCE.parse(pluginFile), CommodoreFileReader.INSTANCE.parse(pluginFile),
player -> player.hasPermission(command.permission)); player -> player.hasPermission(command.permission));
} catch (IOException e) { } catch (IOException e) {
plugin.getLoggingAdapter().log(Level.SEVERE, plugin.log(Level.SEVERE,
"Failed to load " + command.command + ".commodore command definitions", e); "Failed to load " + command.command + ".commodore command definitions", e);
} }
} }

@ -48,7 +48,7 @@ public class BukkitMapHandler {
} }
// Get the map data // Get the map data
plugin.getLoggingAdapter().debug("Rendering map view onto canvas for locked map"); plugin.debug("Rendering map view onto canvas for locked map");
final LockedMapCanvas canvas = new LockedMapCanvas(mapView); final LockedMapCanvas canvas = new LockedMapCanvas(mapView);
for (MapRenderer renderer : mapView.getRenderers()) { for (MapRenderer renderer : mapView.getRenderers()) {
renderer.render(mapView, canvas, Bukkit.getServer() renderer.render(mapView, canvas, Bukkit.getServer()
@ -58,7 +58,7 @@ public class BukkitMapHandler {
} }
// Save the extracted rendered map data // Save the extracted rendered map data
plugin.getLoggingAdapter().debug("Saving pixel canvas data for locked map"); plugin.debug("Saving pixel canvas data for locked map");
if (!mapMeta.getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) { if (!mapMeta.getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) {
mapMeta.getPersistentDataContainer().set(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY, mapMeta.getPersistentDataContainer().set(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY,
canvas.extractMapData().toBytes()); canvas.extractMapData().toBytes());
@ -89,7 +89,7 @@ public class BukkitMapHandler {
final byte[] serializedData = itemStack.getItemMeta().getPersistentDataContainer() final byte[] serializedData = itemStack.getItemMeta().getPersistentDataContainer()
.get(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY); .get(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY);
final MapData mapData = MapData.fromByteArray(Objects.requireNonNull(serializedData)); final MapData mapData = MapData.fromByteArray(Objects.requireNonNull(serializedData));
plugin.getLoggingAdapter().debug("Setting deserialized map data for an item stack"); plugin.debug("Setting deserialized map data for an item stack");
// Create a new map view renderer with the map data color at each pixel // Create a new map view renderer with the map data color at each pixel
final MapView view = Bukkit.createMap(Bukkit.getWorlds().get(0)); final MapView view = Bukkit.createMap(Bukkit.getWorlds().get(0));
@ -101,7 +101,7 @@ public class BukkitMapHandler {
view.setUnlimitedTracking(false); view.setUnlimitedTracking(false);
mapMeta.setMapView(view); mapMeta.setMapView(view);
itemStack.setItemMeta(mapMeta); itemStack.setItemMeta(mapMeta);
plugin.getLoggingAdapter().debug("Successfully applied renderer to map item stack"); plugin.debug("Successfully applied renderer to map item stack");
} catch (IOException | NullPointerException e) { } catch (IOException | NullPointerException e) {
plugin.getLogger().log(Level.WARNING, "Failed to deserialize map data for a player", e); plugin.getLogger().log(Level.WARNING, "Failed to deserialize map data for a player", e);
} }

@ -52,7 +52,7 @@ public class BukkitSerializer {
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion // Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
return Base64Coder.encodeLines(byteOutputStream.toByteArray()); return Base64Coder.encodeLines(byteOutputStream.toByteArray());
} catch (IOException e) { } catch (IOException e) {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize item stack data", e); BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize item stack data", e);
throw new DataSerializationException("Failed to serialize item stack data", e); throw new DataSerializationException("Failed to serialize item stack data", e);
} }
}); });
@ -108,7 +108,7 @@ public class BukkitSerializer {
return inventoryContents; return inventoryContents;
} }
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize item stack data", e); BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize item stack data", e);
throw new DataSerializationException("Failed to deserialize item stack data", e); throw new DataSerializationException("Failed to deserialize item stack data", e);
} }
}); });
@ -165,7 +165,7 @@ public class BukkitSerializer {
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion // Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
return Base64Coder.encodeLines(byteOutputStream.toByteArray()); return Base64Coder.encodeLines(byteOutputStream.toByteArray());
} catch (IOException e) { } catch (IOException e) {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize potion effect data", e); BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize potion effect data", e);
throw new DataSerializationException("Failed to serialize potion effect data", e); throw new DataSerializationException("Failed to serialize potion effect data", e);
} }
}); });
@ -201,7 +201,7 @@ public class BukkitSerializer {
return potionEffects; return potionEffects;
} }
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize potion effect data", e); BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
throw new DataSerializationException("Failed to deserialize potion effects", e); throw new DataSerializationException("Failed to deserialize potion effects", e);
} }
}); });

@ -49,26 +49,26 @@ public class LegacyMigrator extends Migrator {
@Override @Override
public CompletableFuture<Boolean> start() { public CompletableFuture<Boolean> start() {
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration of legacy HuskSync v1.x data..."); plugin.log(Level.INFO, "Starting migration of legacy HuskSync v1.x data...");
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
// Wipe the existing database, preparing it for data import // Wipe the existing database, preparing it for data import
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)..."); plugin.log(Level.INFO, "Preparing existing database (wiping)...");
plugin.getDatabase().wipeDatabase().join(); plugin.getDatabase().wipeDatabase().join();
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)"); plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
// Create jdbc driver connection url // Create jdbc driver connection url
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase; final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
// Create a new data source for the mpdb converter // Create a new data source for the mpdb converter
try (final HikariDataSource connectionPool = new HikariDataSource()) { try (final HikariDataSource connectionPool = new HikariDataSource()) {
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to legacy database..."); plugin.log(Level.INFO, "Establishing connection to legacy database...");
connectionPool.setJdbcUrl(jdbcUrl); connectionPool.setJdbcUrl(jdbcUrl);
connectionPool.setUsername(sourceUsername); connectionPool.setUsername(sourceUsername);
connectionPool.setPassword(sourcePassword); connectionPool.setPassword(sourcePassword);
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase()); connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)..."); plugin.log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
final List<LegacyData> dataToMigrate = new ArrayList<>(); final List<LegacyData> dataToMigrate = new ArrayList<>();
try (final Connection connection = connectionPool.getConnection()) { try (final Connection connection = connectionPool.getConnection()) {
try (final PreparedStatement statement = connection.prepareStatement(""" try (final PreparedStatement statement = connection.prepareStatement("""
@ -106,33 +106,33 @@ public class LegacyMigrator extends Migrator {
)); ));
playersMigrated++; playersMigrated++;
if (playersMigrated % 50 == 0) { if (playersMigrated % 50 == 0) {
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players..."); plugin.log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
} }
} }
} }
} }
} }
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!"); plugin.log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!");
plugin.getLoggingAdapter().log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)..."); plugin.log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
final AtomicInteger playersConverted = new AtomicInteger(); final AtomicInteger playersConverted = new AtomicInteger();
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> { dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
plugin.getDatabase().ensureUser(data.user()).thenRun(() -> plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION) plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
.exceptionally(exception -> { .exceptionally(exception -> {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage()); plugin.log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
return null; return null;
})).join(); })).join();
playersConverted.getAndIncrement(); playersConverted.getAndIncrement();
if (playersConverted.get() % 50 == 0) { if (playersConverted.get() % 50 == 0) {
plugin.getLoggingAdapter().log(Level.INFO, "Converted legacy data for " + playersConverted + " players..."); plugin.log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
} }
}).join()); }).join());
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!"); plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
return true; return true;
} catch (Exception e) { } catch (Exception e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?"); plugin.log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?");
return false; return false;
} }
}); });
@ -176,15 +176,15 @@ public class LegacyMigrator extends Migrator {
} }
default -> false; default -> false;
}) { }) {
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, getHelpMenu());
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " + plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
obfuscateDataString(args[1])); obfuscateDataString(args[1]));
} else { } else {
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " + plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
obfuscateDataString(args[1]) + " (is it a valid option?)"); obfuscateDataString(args[1]) + " (is it a valid option?)");
} }
} else { } else {
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, getHelpMenu());
} }
} }

@ -56,26 +56,26 @@ public class MpdbMigrator extends Migrator {
@Override @Override
public CompletableFuture<Boolean> start() { public CompletableFuture<Boolean> start() {
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync..."); plugin.log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
// Wipe the existing database, preparing it for data import // Wipe the existing database, preparing it for data import
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)..."); plugin.log(Level.INFO, "Preparing existing database (wiping)...");
plugin.getDatabase().wipeDatabase().join(); plugin.getDatabase().wipeDatabase().join();
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)"); plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
// Create jdbc driver connection url // Create jdbc driver connection url
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase; final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
// Create a new data source for the mpdb converter // Create a new data source for the mpdb converter
try (final HikariDataSource connectionPool = new HikariDataSource()) { try (final HikariDataSource connectionPool = new HikariDataSource()) {
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database..."); plugin.log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
connectionPool.setJdbcUrl(jdbcUrl); connectionPool.setJdbcUrl(jdbcUrl);
connectionPool.setUsername(sourceUsername); connectionPool.setUsername(sourceUsername);
connectionPool.setPassword(sourcePassword); connectionPool.setPassword(sourcePassword);
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase()); connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)..."); plugin.log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
final List<MpdbData> dataToMigrate = new ArrayList<>(); final List<MpdbData> dataToMigrate = new ArrayList<>();
try (final Connection connection = connectionPool.getConnection()) { try (final Connection connection = connectionPool.getConnection()) {
try (final PreparedStatement statement = connection.prepareStatement(""" try (final PreparedStatement statement = connection.prepareStatement("""
@ -103,32 +103,32 @@ public class MpdbMigrator extends Migrator {
)); ));
playersMigrated++; playersMigrated++;
if (playersMigrated % 25 == 0) { if (playersMigrated % 25 == 0) {
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players..."); plugin.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.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 (this might take a while)..."); plugin.log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
final AtomicInteger playersConverted = new AtomicInteger(); final AtomicInteger playersConverted = new AtomicInteger();
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> { dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
plugin.getDatabase().ensureUser(data.user()).thenRun(() -> plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION)) plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
.exceptionally(exception -> { .exceptionally(exception -> {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage()); plugin.log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
return null; return null;
}).join(); }).join();
playersConverted.getAndIncrement(); playersConverted.getAndIncrement();
if (playersConverted.get() % 50 == 0) { if (playersConverted.get() % 50 == 0) {
plugin.getLoggingAdapter().log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players..."); plugin.log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
} }
}).join()); }).join());
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!"); plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
return true; return true;
} catch (Exception e) { } catch (Exception e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?"); plugin.log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
return false; return false;
} }
}); });
@ -176,15 +176,15 @@ public class MpdbMigrator extends Migrator {
} }
default -> false; default -> false;
}) { }) {
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, getHelpMenu());
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " + plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
obfuscateDataString(args[1])); obfuscateDataString(args[1]));
} else { } else {
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " + plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
obfuscateDataString(args[1]) + " (is it a valid option?)"); obfuscateDataString(args[1]) + " (is it a valid option?)");
} }
} else { } else {
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, getHelpMenu());
} }
} }

@ -60,6 +60,7 @@ public class BukkitPlayer extends OnlineUser {
this.audience = BukkitHuskSync.getInstance().getAudiences().player(player); this.audience = BukkitHuskSync.getInstance().getAudiences().player(player);
} }
@NotNull
public static BukkitPlayer adapt(@NotNull Player player) { public static BukkitPlayer adapt(@NotNull Player player) {
return new BukkitPlayer(player); return new BukkitPlayer(player);
} }
@ -507,7 +508,7 @@ public class BukkitPlayer extends OnlineUser {
} }
return new PersistentDataContainerData(persistentDataMap); return new PersistentDataContainerData(persistentDataMap);
}).exceptionally(throwable -> { }).exceptionally(throwable -> {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING, BukkitHuskSync.getInstance().log(Level.WARNING,
"Could not read " + player.getName() + "'s persistent data map, skipping!"); "Could not read " + player.getName() + "'s persistent data map, skipping!");
throwable.printStackTrace(); throwable.printStackTrace();
return new PersistentDataContainerData(new HashMap<>()); return new PersistentDataContainerData(new HashMap<>());
@ -515,65 +516,62 @@ public class BukkitPlayer extends OnlineUser {
} }
@Override @Override
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData) { public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData container) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
player.getPersistentDataContainer().getKeys().forEach(namespacedKey -> player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
player.getPersistentDataContainer().remove(namespacedKey)); player.getPersistentDataContainer().remove(namespacedKey));
persistentDataContainerData.getTags().forEach(keyString -> { container.getTags().forEach(keyString -> {
final NamespacedKey key = NamespacedKey.fromString(keyString); final NamespacedKey key = NamespacedKey.fromString(keyString);
if (key != null) { if (key != null) {
// Set a tag with the given key and value. This is crying out for a refactor. // Set a tag with the given key and value. This is crying out for a refactor.
persistentDataContainerData.getTagType(keyString).ifPresentOrElse(dataType -> { container.getTagType(keyString).ifPresentOrElse(dataType -> {
switch (dataType) { switch (dataType) {
case BYTE -> persistentDataContainerData.getTagValue(keyString, byte.class).ifPresent( case BYTE -> container.getTagValue(keyString, byte.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.BYTE, value)); PersistentDataType.BYTE, value));
case SHORT -> persistentDataContainerData.getTagValue(keyString, short.class).ifPresent( case SHORT -> container.getTagValue(keyString, short.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.SHORT, value)); PersistentDataType.SHORT, value));
case INTEGER -> persistentDataContainerData.getTagValue(keyString, int.class).ifPresent( case INTEGER -> container.getTagValue(keyString, int.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.INTEGER, value)); PersistentDataType.INTEGER, value));
case LONG -> persistentDataContainerData.getTagValue(keyString, long.class).ifPresent( case LONG -> container.getTagValue(keyString, long.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.LONG, value)); PersistentDataType.LONG, value));
case FLOAT -> persistentDataContainerData.getTagValue(keyString, float.class).ifPresent( case FLOAT -> container.getTagValue(keyString, float.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.FLOAT, value)); PersistentDataType.FLOAT, value));
case DOUBLE -> persistentDataContainerData.getTagValue(keyString, double.class).ifPresent( case DOUBLE -> container.getTagValue(keyString, double.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.DOUBLE, value)); PersistentDataType.DOUBLE, value));
case STRING -> persistentDataContainerData.getTagValue(keyString, String.class).ifPresent( case STRING -> container.getTagValue(keyString, String.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.STRING, value)); PersistentDataType.STRING, value));
case BYTE_ARRAY -> case BYTE_ARRAY -> container.getTagValue(keyString, byte[].class).ifPresent(
persistentDataContainerData.getTagValue(keyString, byte[].class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.BYTE_ARRAY, value)); PersistentDataType.BYTE_ARRAY, value));
case INTEGER_ARRAY -> case INTEGER_ARRAY -> container.getTagValue(keyString, int[].class).ifPresent(
persistentDataContainerData.getTagValue(keyString, int[].class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.INTEGER_ARRAY, value)); PersistentDataType.INTEGER_ARRAY, value));
case LONG_ARRAY -> case LONG_ARRAY -> container.getTagValue(keyString, long[].class).ifPresent(
persistentDataContainerData.getTagValue(keyString, long[].class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.LONG_ARRAY, value)); PersistentDataType.LONG_ARRAY, value));
case TAG_CONTAINER -> case TAG_CONTAINER ->
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer.class).ifPresent( container.getTagValue(keyString, PersistentDataContainer.class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.TAG_CONTAINER, value)); PersistentDataType.TAG_CONTAINER, value));
case TAG_CONTAINER_ARRAY -> case TAG_CONTAINER_ARRAY ->
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer[].class).ifPresent( container.getTagValue(keyString, PersistentDataContainer[].class).ifPresent(
value -> player.getPersistentDataContainer().set(key, value -> player.getPersistentDataContainer().set(key,
PersistentDataType.TAG_CONTAINER_ARRAY, value)); PersistentDataType.TAG_CONTAINER_ARRAY, value));
} }
}, () -> BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING, }, () -> BukkitHuskSync.getInstance().log(Level.WARNING,
"Could not set " + player.getName() + "'s persistent data key " + keyString + "Could not set " + player.getName() + "'s persistent data key " + keyString +
" as it has an invalid type. Skipping!")); " as it has an invalid type. Skipping!"));
} }
}); });
}).exceptionally(throwable -> { }).exceptionally(throwable -> {
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING, BukkitHuskSync.getInstance().log(Level.WARNING,
"Could not write " + player.getName() + "'s persistent data map, skipping!"); "Could not write " + player.getName() + "'s persistent data map, skipping!");
throwable.printStackTrace(); throwable.printStackTrace();
return null; return null;

@ -1,40 +0,0 @@
package net.william278.husksync.util;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Level;
public class BukkitLogger extends Logger {
private final java.util.logging.Logger logger;
public BukkitLogger(@NotNull java.util.logging.Logger logger) {
this.logger = logger;
}
@Override
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable e) {
logger.log(level, message, e);
}
@Override
public void log(@NotNull Level level, @NotNull String message) {
logger.log(level, message);
}
@Override
public void info(@NotNull String message) {
logger.info(message);
}
@Override
public void severe(@NotNull String message) {
logger.severe(message);
}
@Override
public void config(@NotNull String message) {
logger.config(message);
}
}

@ -1,22 +0,0 @@
package net.william278.husksync.util;
import net.william278.husksync.BukkitHuskSync;
import org.jetbrains.annotations.NotNull;
import java.io.InputStream;
import java.util.Objects;
public class BukkitResourceReader implements ResourceReader {
private final BukkitHuskSync plugin;
public BukkitResourceReader(BukkitHuskSync plugin) {
this.plugin = plugin;
}
@Override
public @NotNull InputStream getResource(String fileName) {
return Objects.requireNonNull(plugin.getResource(fileName));
}
}

@ -9,17 +9,17 @@ import net.william278.husksync.event.EventCannon;
import net.william278.husksync.migrator.Migrator; 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.ResourceReader;
import net.william278.desertwell.Version; import net.william278.desertwell.Version;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.util.List; 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;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
/** /**
* Abstract implementation of the HuskSync plugin. * Abstract implementation of the HuskSync plugin.
@ -103,20 +103,33 @@ public interface HuskSync {
Locales getLocales(); Locales getLocales();
/** /**
* Returns the plugin {@link Logger} * Get a resource as an {@link InputStream} from the plugin jar
* *
* @return the {@link Logger} * @param name the path to the resource
* @return the {@link InputStream} of the resource
*/ */
@NotNull InputStream getResource(@NotNull String name);
Logger getLoggingAdapter();
/** /**
* Returns the plugin resource file reader * Log a message to the console
* *
* @return the {@link ResourceReader} * @param level the level of the message
* @param message the message to log
* @param throwable a throwable to log
*/ */
@NotNull void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable);
ResourceReader getResourceReader();
/**
* Send a debug message to the console, if debug logging is enabled
*
* @param message the message to log
* @param throwable a throwable to log
*/
default void debug(@NotNull String message, @NotNull Throwable... throwable) {
if (getSettings().debugLogging) {
log(Level.INFO, "[DEBUG] " + message, throwable);
}
}
/** /**
* Returns the plugin version * Returns the plugin version

@ -72,7 +72,7 @@ public abstract class BaseHuskSyncAPI {
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) { public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
if (user instanceof OnlineUser) { if (user instanceof OnlineUser) {
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join(); return ((OnlineUser) user).getUserData(plugin).join();
} else { } else {
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData); return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
} }
@ -103,7 +103,7 @@ public abstract class BaseHuskSyncAPI {
* @since 2.0 * @since 2.0
*/ */
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) { public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()) return CompletableFuture.runAsync(() -> user.getUserData(plugin)
.thenAccept(optionalUserData -> optionalUserData.ifPresent( .thenAccept(optionalUserData -> optionalUserData.ifPresent(
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join()))); userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
} }

@ -73,7 +73,7 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
.getLocale("ender_chest_viewer_menu_title", dataOwner.username) .getLocale("ender_chest_viewer_menu_title", dataOwner.username)
.orElse(new MineDown("Ender Chest Viewer"))) .orElse(new MineDown("Ender Chest Viewer")))
.exceptionally(throwable -> { .exceptionally(throwable -> {
plugin.getLoggingAdapter().log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable); plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
return Optional.empty(); return Optional.empty();
}) })
.thenAccept(dataOnClose -> { .thenAccept(dataOnClose -> {

@ -88,25 +88,25 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
@Override @Override
public void onConsoleExecute(@NotNull String[] args) { public void onConsoleExecute(@NotNull String[] args) {
if (args.length < 1) { if (args.length < 1) {
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\""); plugin.log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\"");
return; return;
} }
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion -> case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
newestVersion.ifPresentOrElse(newVersion -> plugin.getLoggingAdapter().log(Level.WARNING, newestVersion.ifPresentOrElse(newVersion -> plugin.log(Level.WARNING,
"An update is available for HuskSync, v" + newVersion "An update is available for HuskSync, v" + newVersion
+ " (Running v" + plugin.getPluginVersion() + ")"), + " (Running v" + plugin.getPluginVersion() + ")"),
() -> plugin.getLoggingAdapter().log(Level.INFO, () -> plugin.log(Level.INFO,
"HuskSync is up to date" + "HuskSync is up to date" +
" (Running v" + plugin.getPluginVersion() + ")"))); " (Running v" + plugin.getPluginVersion() + ")")));
case "about", "info" -> aboutMenu.toString().lines().forEach(plugin.getLoggingAdapter()::info); case "about", "info" -> aboutMenu.toString().lines().forEach(line -> plugin.log(Level.INFO, line));
case "reload" -> { case "reload" -> {
plugin.reload(); plugin.reload();
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files."); plugin.log(Level.INFO, "Reloaded config & message files.");
} }
case "migrate" -> { case "migrate" -> {
if (args.length < 2) { if (args.length < 2) {
plugin.getLoggingAdapter().log(Level.INFO, plugin.log(Level.INFO,
"Please choose a migrator, then run \"husksync migrate <migrator>\""); "Please choose a migrator, then run \"husksync migrate <migrator>\"");
logMigratorsList(); logMigratorsList();
return; return;
@ -115,35 +115,35 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst(); availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
selectedMigrator.ifPresentOrElse(migrator -> { selectedMigrator.ifPresentOrElse(migrator -> {
if (args.length < 3) { if (args.length < 3) {
plugin.getLoggingAdapter().log(Level.INFO, migrator.getHelpMenu()); plugin.log(Level.INFO, migrator.getHelpMenu());
return; return;
} }
switch (args[2]) { switch (args[2]) {
case "start" -> migrator.start().thenAccept(succeeded -> { case "start" -> migrator.start().thenAccept(succeeded -> {
if (succeeded) { if (succeeded) {
plugin.getLoggingAdapter().log(Level.INFO, "Migration completed successfully!"); plugin.log(Level.INFO, "Migration completed successfully!");
} else { } else {
plugin.getLoggingAdapter().log(Level.WARNING, "Migration failed!"); plugin.log(Level.WARNING, "Migration failed!");
} }
}); });
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length)); case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
default -> plugin.getLoggingAdapter().log(Level.INFO, default -> plugin.log(Level.INFO,
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + " <start/set>"); "Invalid syntax. Console usage: \"husksync migrate " + args[1] + " <start/set>");
} }
}, () -> { }, () -> {
plugin.getLoggingAdapter().log(Level.INFO, plugin.log(Level.INFO,
"Please specify a valid migrator.\n" + "Please specify a valid migrator.\n" +
"If a migrator is not available, please verify that you meet the prerequisites to use it."); "If a migrator is not available, please verify that you meet the prerequisites to use it.");
logMigratorsList(); logMigratorsList();
}); });
} }
default -> plugin.getLoggingAdapter().log(Level.INFO, default -> plugin.log(Level.INFO,
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\""); "Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
} }
} }
private void logMigratorsList() { private void logMigratorsList() {
plugin.getLoggingAdapter().log(Level.INFO, plugin.log(Level.INFO,
"List of available migrators:\nMigrator ID / Migrator Name:\n" + "List of available migrators:\nMigrator ID / Migrator Name:\n" +
plugin.getAvailableMigrators().stream() plugin.getAvailableMigrators().stream()
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName()) .map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())

@ -73,7 +73,7 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
.getLocale("inventory_viewer_menu_title", dataOwner.username) .getLocale("inventory_viewer_menu_title", dataOwner.username)
.orElse(new MineDown("Inventory Viewer"))) .orElse(new MineDown("Inventory Viewer")))
.exceptionally(throwable -> { .exceptionally(throwable -> {
plugin.getLoggingAdapter().log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable); plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
return Optional.empty(); return Optional.empty();
}) })
.thenAccept(dataOnClose -> { .thenAccept(dataOnClose -> {

@ -278,7 +278,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
.split("-")[0], user.username, result) .split("-")[0], user.username, result)
.ifPresent(player::sendMessage); .ifPresent(player::sendMessage);
} catch (IOException e) { } catch (IOException e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to dump user data", e); plugin.log(Level.SEVERE, "Failed to dump user data", e);
} }
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid") }, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
.ifPresent(player::sendMessage))), .ifPresent(player::sendMessage))),

@ -1,14 +1,12 @@
package net.william278.husksync.database; package net.william278.husksync.database;
import net.william278.husksync.data.DataAdapter; import net.william278.husksync.HuskSync;
import net.william278.husksync.config.Settings;
import net.william278.husksync.data.DataSaveCause; import net.william278.husksync.data.DataSaveCause;
import net.william278.husksync.data.UserData; import net.william278.husksync.data.UserData;
import net.william278.husksync.data.UserDataSnapshot; import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.event.EventCannon;
import net.william278.husksync.migrator.Migrator; import net.william278.husksync.migrator.Migrator;
import net.william278.husksync.player.User; import net.william278.husksync.player.User;
import net.william278.husksync.util.Logger;
import net.william278.husksync.util.ResourceReader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
@ -26,78 +24,10 @@ import java.util.concurrent.CompletableFuture;
*/ */
public abstract class Database { public abstract class Database {
/** protected final HuskSync plugin;
* Name of the table that stores player information
*/
protected final String playerTableName;
/**
* Name of the table that stores data
*/
protected final String dataTableName;
/**
* The maximum number of user records to store in the database at once per user
*/
protected final int maxUserDataRecords;
/**
* {@link DataAdapter} implementation used for adapting {@link UserData} to and from JSON
*/
private final DataAdapter dataAdapter;
/**
* Returns the {@link DataAdapter} used to adapt {@link UserData} to and from JSON
*
* @return instance of the {@link DataAdapter} implementation
*/
protected DataAdapter getDataAdapter() {
return dataAdapter;
}
/**
* {@link EventCannon} implementation used for firing events
*/
private final EventCannon eventCannon;
/**
* Returns the {@link EventCannon} used to fire events
*
* @return instance of the {@link EventCannon} implementation
*/
protected EventCannon getEventCannon() {
return eventCannon;
}
/**
* Logger instance used for database error logging
*/
private final Logger logger;
/**
* Returns the {@link Logger} used to log database errors
*
* @return the {@link Logger} instance
*/
protected Logger getLogger() {
return logger;
}
/**
* The {@link ResourceReader} used to read internal resource files by name
*/
private final ResourceReader resourceReader;
protected Database(@NotNull String playerTableName, @NotNull String dataTableName, final int maxUserDataRecords, protected Database(@NotNull HuskSync plugin) {
@NotNull ResourceReader resourceReader, @NotNull DataAdapter dataAdapter, this.plugin = plugin;
@NotNull EventCannon eventCannon, @NotNull Logger logger) {
this.playerTableName = playerTableName;
this.dataTableName = dataTableName;
this.maxUserDataRecords = maxUserDataRecords;
this.resourceReader = resourceReader;
this.dataAdapter = dataAdapter;
this.eventCannon = eventCannon;
this.logger = logger;
} }
/** /**
@ -109,7 +39,7 @@ public abstract class Database {
*/ */
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException { protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
return formatStatementTables(new String(Objects.requireNonNull(resourceReader.getResource(schemaFileName)) return formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName))
.readAllBytes(), StandardCharsets.UTF_8)).split(";"); .readAllBytes(), StandardCharsets.UTF_8)).split(";");
} }
@ -120,8 +50,8 @@ public abstract class Database {
* @return the formatted statement, with table placeholders replaced with the correct names * @return the formatted statement, with table placeholders replaced with the correct names
*/ */
protected final String formatStatementTables(@NotNull String sql) { protected final String formatStatementTables(@NotNull String sql) {
return sql.replaceAll("%users_table%", playerTableName) return sql.replaceAll("%users_table%", plugin.getSettings().getTableName(Settings.TableName.USERS))
.replaceAll("%user_data_table%", dataTableName); .replaceAll("%user_data_table%", plugin.getSettings().getTableName(Settings.TableName.USER_DATA));
} }
/** /**

@ -1,13 +1,11 @@
package net.william278.husksync.database; package net.william278.husksync.database;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import net.william278.husksync.HuskSync;
import net.william278.husksync.config.Settings; import net.william278.husksync.config.Settings;
import net.william278.husksync.data.*; import net.william278.husksync.data.*;
import net.william278.husksync.event.DataSaveEvent; import net.william278.husksync.event.DataSaveEvent;
import net.william278.husksync.event.EventCannon;
import net.william278.husksync.player.User; import net.william278.husksync.player.User;
import net.william278.husksync.util.Logger;
import net.william278.husksync.util.ResourceReader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -51,12 +49,9 @@ public class MySqlDatabase extends Database {
*/ */
private HikariDataSource connectionPool; private HikariDataSource connectionPool;
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger, public MySqlDatabase(@NotNull HuskSync plugin) {
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) { super(plugin);
super(settings.getTableName(Settings.TableName.USERS), final Settings settings = plugin.getSettings();
settings.getTableName(Settings.TableName.USER_DATA),
Math.max(1, Math.min(20, settings.maxUserDataSnapshots)),
resourceReader, dataAdapter, eventCannon, logger);
this.mySqlHost = settings.mySqlHost; this.mySqlHost = settings.mySqlHost;
this.mySqlPort = settings.mySqlPort; this.mySqlPort = settings.mySqlPort;
this.mySqlDatabaseName = settings.mySqlDatabase; this.mySqlDatabaseName = settings.mySqlDatabase;
@ -111,10 +106,10 @@ public class MySqlDatabase extends Database {
} }
return true; return true;
} catch (SQLException | IOException e) { } catch (SQLException | IOException e) {
getLogger().log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage()); plugin.log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
} }
} catch (Exception e) { } catch (Exception e) {
getLogger().log(Level.SEVERE, "An unhandled exception occurred during database setup!", e); plugin.log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
} }
return false; return false;
} }
@ -135,9 +130,9 @@ public class MySqlDatabase extends Database {
statement.setString(2, existingUser.uuid.toString()); statement.setString(2, existingUser.uuid.toString());
statement.executeUpdate(); statement.executeUpdate();
} }
getLogger().log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")"); plugin.log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")");
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to update a user's name on the database", e); plugin.log(Level.SEVERE, "Failed to update a user's name on the database", e);
} }
} }
}, },
@ -153,7 +148,7 @@ public class MySqlDatabase extends Database {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to insert a user into the database", e); plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
} }
})); }));
} }
@ -176,7 +171,7 @@ public class MySqlDatabase extends Database {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e); plugin.log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e);
} }
return Optional.empty(); return Optional.empty();
}); });
@ -199,7 +194,7 @@ public class MySqlDatabase extends Database {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to fetch a user by name from the database", e); plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
} }
return Optional.empty(); return Optional.empty();
}); });
@ -226,11 +221,11 @@ public class MySqlDatabase extends Database {
Date.from(resultSet.getTimestamp("timestamp").toInstant()), Date.from(resultSet.getTimestamp("timestamp").toInstant()),
DataSaveCause.getCauseByName(resultSet.getString("save_cause")), DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
resultSet.getBoolean("pinned"), resultSet.getBoolean("pinned"),
getDataAdapter().fromBytes(dataByteArray))); plugin.getDataAdapter().fromBytes(dataByteArray)));
} }
} }
} catch (SQLException | DataAdaptionException e) { } catch (SQLException | DataAdaptionException e) {
getLogger().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 current user data from the database", e);
} }
return Optional.empty(); return Optional.empty();
}); });
@ -257,13 +252,13 @@ public class MySqlDatabase extends Database {
Date.from(resultSet.getTimestamp("timestamp").toInstant()), Date.from(resultSet.getTimestamp("timestamp").toInstant()),
DataSaveCause.getCauseByName(resultSet.getString("save_cause")), DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
resultSet.getBoolean("pinned"), resultSet.getBoolean("pinned"),
getDataAdapter().fromBytes(dataByteArray)); plugin.getDataAdapter().fromBytes(dataByteArray));
retrievedData.add(data); retrievedData.add(data);
} }
return retrievedData; return retrievedData;
} }
} catch (SQLException | DataAdaptionException e) { } catch (SQLException | DataAdaptionException e) {
getLogger().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 current user data from the database", e);
} }
return retrievedData; return retrievedData;
}); });
@ -291,11 +286,11 @@ public class MySqlDatabase extends Database {
Date.from(resultSet.getTimestamp("timestamp").toInstant()), Date.from(resultSet.getTimestamp("timestamp").toInstant()),
DataSaveCause.getCauseByName(resultSet.getString("save_cause")), DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
resultSet.getBoolean("pinned"), resultSet.getBoolean("pinned"),
getDataAdapter().fromBytes(dataByteArray))); plugin.getDataAdapter().fromBytes(dataByteArray)));
} }
} }
} catch (SQLException | DataAdaptionException e) { } catch (SQLException | DataAdaptionException e) {
getLogger().log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e); plugin.log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e);
} }
return Optional.empty(); return Optional.empty();
}); });
@ -306,7 +301,7 @@ public class MySqlDatabase extends Database {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream() final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList(); .filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
if (unpinnedUserData.size() > maxUserDataRecords) { if (unpinnedUserData.size() > plugin.getSettings().maxUserDataSnapshots) {
try (Connection connection = getConnection()) { try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
DELETE FROM `%user_data_table%` DELETE FROM `%user_data_table%`
@ -314,12 +309,12 @@ public class MySqlDatabase extends Database {
AND `pinned` IS FALSE AND `pinned` IS FALSE
ORDER BY `timestamp` ASC ORDER BY `timestamp` ASC
LIMIT %entry_count%;""".replace("%entry_count%", LIMIT %entry_count%;""".replace("%entry_count%",
Integer.toString(unpinnedUserData.size() - maxUserDataRecords))))) { Integer.toString(unpinnedUserData.size() - plugin.getSettings().maxUserDataSnapshots))))) {
statement.setString(1, user.uuid.toString()); statement.setString(1, user.uuid.toString());
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e); plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
} }
} }
}); });
@ -338,7 +333,7 @@ public class MySqlDatabase extends Database {
return statement.executeUpdate() > 0; return statement.executeUpdate() > 0;
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to delete specific user data from the database", e); plugin.log(Level.SEVERE, "Failed to delete specific user data from the database", e);
} }
return false; return false;
}); });
@ -348,7 +343,7 @@ public class MySqlDatabase extends Database {
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData, public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
@NotNull DataSaveCause saveCause) { @NotNull DataSaveCause saveCause) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
final DataSaveEvent dataSaveEvent = (DataSaveEvent) getEventCannon().fireDataSaveEvent(user, final DataSaveEvent dataSaveEvent = (DataSaveEvent) plugin.getEventCannon().fireDataSaveEvent(user,
userData, saveCause).join(); userData, saveCause).join();
if (!dataSaveEvent.isCancelled()) { if (!dataSaveEvent.isCancelled()) {
final UserData finalData = dataSaveEvent.getUserData(); final UserData finalData = dataSaveEvent.getUserData();
@ -360,11 +355,11 @@ public class MySqlDatabase extends Database {
statement.setString(1, user.uuid.toString()); statement.setString(1, user.uuid.toString());
statement.setString(2, saveCause.name()); statement.setString(2, saveCause.name());
statement.setBlob(3, new ByteArrayInputStream( statement.setBlob(3, new ByteArrayInputStream(
getDataAdapter().toBytes(finalData))); plugin.getDataAdapter().toBytes(finalData)));
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException | DataAdaptionException e) { } catch (SQLException | DataAdaptionException e) {
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e); plugin.log(Level.SEVERE, "Failed to set user data in the database", e);
} }
} }
}).thenRun(() -> rotateUserData(user).join()); }).thenRun(() -> rotateUserData(user).join());
@ -384,7 +379,7 @@ public class MySqlDatabase extends Database {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to pin user data in the database", e); plugin.log(Level.SEVERE, "Failed to pin user data in the database", e);
} }
}); });
} }
@ -403,7 +398,7 @@ public class MySqlDatabase extends Database {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to unpin user data in the database", e); plugin.log(Level.SEVERE, "Failed to unpin user data in the database", e);
} }
}); });
} }
@ -416,7 +411,7 @@ public class MySqlDatabase extends Database {
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;")); statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
} }
} catch (SQLException e) { } catch (SQLException e) {
getLogger().log(Level.SEVERE, "Failed to wipe the database", e); plugin.log(Level.SEVERE, "Failed to wipe the database", e);
} }
}); });
} }

@ -10,8 +10,8 @@ import com.djrapitops.plan.extension.icon.Family;
import com.djrapitops.plan.extension.icon.Icon; import com.djrapitops.plan.extension.icon.Icon;
import com.djrapitops.plan.extension.table.Table; import com.djrapitops.plan.extension.table.Table;
import com.djrapitops.plan.extension.table.TableColumnFormat; import com.djrapitops.plan.extension.table.TableColumnFormat;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.UserDataSnapshot; import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.database.Database;
import net.william278.husksync.player.User; import net.william278.husksync.player.User;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -43,14 +43,14 @@ import java.util.regex.Pattern;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlanDataExtension implements DataExtension { public class PlanDataExtension implements DataExtension {
private Database database; private HuskSync plugin;
private static final String UNKNOWN_STRING = "N/A"; private static final String UNKNOWN_STRING = "N/A";
private static final String PINNED_HTML_STRING = "&#128205;&nbsp;"; private static final String PINNED_HTML_STRING = "&#128205;&nbsp;";
protected PlanDataExtension(@NotNull Database database) { protected PlanDataExtension(@NotNull HuskSync plugin) {
this.database = database; this.plugin = plugin;
} }
protected PlanDataExtension() { protected PlanDataExtension() {
@ -66,9 +66,9 @@ public class PlanDataExtension implements DataExtension {
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) { private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
final Optional<User> optionalUser = database.getUser(uuid).join(); final Optional<User> optionalUser = plugin.getDatabase().getUser(uuid).join();
if (optionalUser.isPresent()) { if (optionalUser.isPresent()) {
return database.getCurrentUserData(optionalUser.get()).join(); return plugin.getDatabase().getCurrentUserData(optionalUser.get()).join();
} }
return Optional.empty(); return Optional.empty();
}); });
@ -208,8 +208,8 @@ public class PlanDataExtension implements DataExtension {
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE)) .columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE)) .columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE)); .columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
database.getUser(playerUUID).join().ifPresent(user -> plugin.getDatabase().getUser(playerUUID).join().ifPresent(user ->
database.getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow( plugin.getDatabase().getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
versionedUserData.versionTimestamp().getTime(), versionedUserData.versionTimestamp().getTime(),
versionedUserData.versionUUID().toString().split("-")[0], versionedUserData.versionUUID().toString().split("-")[0],
versionedUserData.cause().name().toLowerCase().replaceAll("_", " "), versionedUserData.cause().name().toLowerCase().replaceAll("_", " "),

@ -2,20 +2,17 @@ package net.william278.husksync.hook;
import com.djrapitops.plan.capability.CapabilityService; import com.djrapitops.plan.capability.CapabilityService;
import com.djrapitops.plan.extension.ExtensionService; import com.djrapitops.plan.extension.ExtensionService;
import net.william278.husksync.database.Database; import net.william278.husksync.HuskSync;
import net.william278.husksync.util.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.logging.Level; import java.util.logging.Level;
public class PlanHook { public class PlanHook {
private final Database database; private final HuskSync plugin;
private final Logger logger;
public PlanHook(@NotNull Database database, @NotNull Logger logger) { public PlanHook(@NotNull HuskSync plugin) {
this.database = database; this.plugin = plugin;
this.logger = logger;
} }
public void hookIntoPlan() { public void hookIntoPlan() {
@ -33,13 +30,9 @@ public class PlanHook {
private void registerDataExtension() { private void registerDataExtension() {
try { try {
ExtensionService.getInstance().register(new PlanDataExtension(database)); ExtensionService.getInstance().register(new PlanDataExtension(plugin));
} catch (IllegalStateException planIsNotEnabled) { } catch (IllegalStateException | IllegalArgumentException e) {
logger.log(Level.SEVERE, "Plan extension hook failed to register. Plan is not enabled.", planIsNotEnabled); plugin.log(Level.WARNING, "Failed to register Plan data extension: " + e.getMessage(), e);
// Plan is not enabled, handle exception
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
logger.log(Level.SEVERE, "Plan extension hook failed to register. Data hook implementation is invalid.", dataExtensionImplementationIsInvalid);
// The DataExtension implementation has an implementation error, handle exception
} }
} }

@ -62,7 +62,7 @@ public abstract class EventListener {
// Hold reading data for the network latency threshold, to ensure the source server has set the redis key // Hold reading data for the network latency threshold, to ensure the source server has set the redis key
Thread.sleep(Math.max(0, plugin.getSettings().networkLatencyMilliseconds)); Thread.sleep(Math.max(0, plugin.getSettings().networkLatencyMilliseconds));
} catch (InterruptedException e) { } catch (InterruptedException e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "An exception occurred handling a player join", e); plugin.log(Level.SEVERE, "An exception occurred handling a player join", e);
} finally { } finally {
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> { plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
if (!changingServers) { if (!changingServers) {
@ -88,8 +88,7 @@ public abstract class EventListener {
} }
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData -> plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
redisUserData.ifPresent(redisData -> { redisUserData.ifPresent(redisData -> {
user.setData(redisData, plugin.getSettings(), plugin.getEventCannon(), user.setData(redisData, plugin)
plugin.getLoggingAdapter(), plugin.getMinecraftVersion())
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join(); .thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
executor.shutdown(); executor.shutdown();
})).join(); })).join();
@ -111,8 +110,7 @@ public abstract class EventListener {
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) { private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> { return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
if (databaseUserData.isPresent()) { if (databaseUserData.isPresent()) {
return user.setData(databaseUserData.get().userData(), plugin.getSettings(), plugin.getEventCannon(), return user.setData(databaseUserData.get().userData(), plugin).join();
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).join();
} }
return true; return true;
}); });
@ -163,12 +161,12 @@ public abstract class EventListener {
// Handle asynchronous disconnection // Handle asynchronous disconnection
lockedPlayers.add(user.uuid); lockedPlayers.add(user.uuid);
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user) CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
.thenRun(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).thenAccept( .thenRun(() -> user.getUserData(plugin).thenAccept(
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager() optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
.setUserData(user, userData).thenRun(() -> plugin.getDatabase() .setUserData(user, userData).thenRun(() -> plugin.getDatabase()
.setUserData(user, userData, DataSaveCause.DISCONNECT))))) .setUserData(user, userData, DataSaveCause.DISCONNECT)))))
.exceptionally(throwable -> { .exceptionally(throwable -> {
plugin.getLoggingAdapter().log(Level.SEVERE, plugin.log(Level.SEVERE,
"An exception occurred handling a player disconnection"); "An exception occurred handling a player disconnection");
throwable.printStackTrace(); throwable.printStackTrace();
return null; return null;
@ -186,7 +184,7 @@ public abstract class EventListener {
} }
usersInWorld.stream() usersInWorld.stream()
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc()) .filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
.forEach(user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()) .forEach(user -> user.getUserData(plugin)
.thenAccept(data -> data.ifPresent(userData -> plugin.getDatabase() .thenAccept(data -> data.ifPresent(userData -> plugin.getDatabase()
.setUserData(user, userData, DataSaveCause.WORLD_SAVE)))); .setUserData(user, userData, DataSaveCause.WORLD_SAVE))));
} }
@ -202,7 +200,7 @@ public abstract class EventListener {
return; return;
} }
user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()) user.getUserData(plugin)
.thenAccept(data -> data.ifPresent(userData -> { .thenAccept(data -> data.ifPresent(userData -> {
userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems; userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems;
plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH); plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH);
@ -230,7 +228,7 @@ public abstract class EventListener {
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc()) .filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
.forEach(user -> { .forEach(user -> {
lockedPlayers.add(user.uuid); lockedPlayers.add(user.uuid);
user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join() user.getUserData(plugin).join()
.ifPresent(userData -> plugin.getDatabase() .ifPresent(userData -> plugin.getDatabase()
.setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()); .setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join());
}); });

@ -2,11 +2,10 @@ package net.william278.husksync.player;
import de.themoep.minedown.adventure.MineDown; import de.themoep.minedown.adventure.MineDown;
import net.william278.desertwell.Version; import net.william278.desertwell.Version;
import net.william278.husksync.HuskSync;
import net.william278.husksync.config.Settings; import net.william278.husksync.config.Settings;
import net.william278.husksync.data.*; import net.william278.husksync.data.*;
import net.william278.husksync.event.EventCannon;
import net.william278.husksync.event.PreSyncEvent; import net.william278.husksync.event.PreSyncEvent;
import net.william278.husksync.util.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -243,37 +242,32 @@ public abstract class OnlineUser extends User {
* This will only set data that is enabled as per the enabled settings in the config file. * This will only set data that is enabled as per the enabled settings in the config file.
* Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored. * Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored.
* *
* @param data The {@link UserData} to set to the player * @param plugin The plugin instance
* @param settings The plugin {@link Settings} to determine which data to set
* @param eventCannon The {@link EventCannon} to fire the synchronisation events
* @param logger The {@link Logger} for debug and error logging
* @param serverMinecraftVersion The server's Minecraft version, for validating the format of the {@link UserData}
* @return a future returning a boolean when complete; if the sync was successful, the future will return {@code true}. * @return a future returning a boolean when complete; if the sync was successful, the future will return {@code true}.
*/ */
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull Settings settings, public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull HuskSync plugin) {
@NotNull EventCannon eventCannon, @NotNull Logger logger,
@NotNull Version serverMinecraftVersion) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
// Prevent synchronising user data from newer versions of Minecraft // Prevent synchronising user data from newer versions of Minecraft
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) { if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(plugin.getMinecraftVersion()) > 0) {
logger.log(Level.SEVERE, "Cannot set data for " + username + plugin.log(Level.SEVERE, "Cannot set data for " + username +
" because the Minecraft version of their user data (" + data.getMinecraftVersion() + " because the Minecraft version of their user data (" + data.getMinecraftVersion() +
") is newer than the server's Minecraft version (" + serverMinecraftVersion + ")."); ") is newer than the server's Minecraft version (" + plugin.getMinecraftVersion() + ").");
return false; return false;
} }
// Prevent synchronising user data from newer versions of the plugin // Prevent synchronising user data from newer versions of the plugin
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) { if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
logger.log(Level.SEVERE, "Cannot set data for " + username + plugin.log(Level.SEVERE, "Cannot set data for " + username +
" because the format version of their user data (v" + data.getFormatVersion() + " because the format version of their user data (v" + data.getFormatVersion() +
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ")."); ") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
return false; return false;
} }
// Fire the PreSyncEvent // Fire the PreSyncEvent
final PreSyncEvent preSyncEvent = (PreSyncEvent) eventCannon.firePreSyncEvent(this, data).join(); final PreSyncEvent preSyncEvent = (PreSyncEvent) plugin.getEventCannon().firePreSyncEvent(this, data).join();
final UserData finalData = preSyncEvent.getUserData(); final UserData finalData = preSyncEvent.getUserData();
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{ final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
if (!isOffline() && !preSyncEvent.isCancelled()) { if (!isOffline() && !preSyncEvent.isCancelled()) {
final Settings settings = plugin.getSettings();
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) { if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData))); finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData)));
} }
@ -303,7 +297,7 @@ public abstract class OnlineUser extends User {
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true) return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
.exceptionally(exception -> { .exceptionally(exception -> {
// Handle synchronisation exceptions // Handle synchronisation exceptions
logger.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")"); plugin.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace(); exception.printStackTrace();
return false; return false;
}).join(); }).join();
@ -322,17 +316,17 @@ public abstract class OnlineUser extends User {
* <p> * <p>
* If the user data could not be returned due to an exception, the optional will return empty * If the user data could not be returned due to an exception, the optional will return empty
* *
* @param logger The logger to use for handling exceptions * @param plugin The plugin instance
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
*/ */
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger, @NotNull Settings settings) { public final CompletableFuture<Optional<UserData>> getUserData(@NotNull HuskSync plugin) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
final UserDataBuilder builder = UserData.builder(getMinecraftVersion()); final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{ final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
if (!isOffline()) { if (!isOffline()) {
final Settings settings = plugin.getSettings();
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) { if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
if (isDead() && settings.saveDeadPlayerInventories) { if (isDead() && settings.saveDeadPlayerInventories) {
logger.debug("Player " + username + " is dead, so their inventory will be set to empty."); plugin.debug("Player " + username + " is dead, so their inventory will be set to empty.");
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty()))); add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
} else { } else {
add(getInventory().thenAccept(builder::setInventory)); add(getInventory().thenAccept(builder::setInventory));
@ -363,9 +357,8 @@ public abstract class OnlineUser extends User {
// Apply operations in parallel, join when complete // Apply operations in parallel, join when complete
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join(); CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
return Optional.of(builder.build()); return Optional.of(builder.build());
}) }).exceptionally(exception -> {
.exceptionally(exception -> { plugin.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
logger.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
exception.printStackTrace(); exception.printStackTrace();
return Optional.empty(); return Optional.empty();
}); });

@ -89,8 +89,7 @@ public class RedisManager extends JedisPubSub {
final RedisMessage redisMessage = RedisMessage.fromJson(message); final RedisMessage redisMessage = RedisMessage.fromJson(message);
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> { plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data); final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
user.setData(userData, plugin.getSettings(), plugin.getEventCannon(), user.setData(userData, plugin).thenAccept(succeeded -> {
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).thenAccept(succeeded -> {
if (succeeded) { if (succeeded) {
switch (plugin.getSettings().notificationDisplaySlot) { switch (plugin.getSettings().notificationDisplaySlot) {
case CHAT -> plugin.getLocales().getLocale("data_update_complete") case CHAT -> plugin.getLocales().getLocale("data_update_complete")
@ -140,7 +139,7 @@ public class RedisManager extends JedisPubSub {
plugin.getDataAdapter().toBytes(userData)); plugin.getDataAdapter().toBytes(userData));
// Debug logging // Debug logging
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name() plugin.debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
+ " key to redis at: " + + " key to redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));
} }
@ -156,7 +155,7 @@ public class RedisManager extends JedisPubSub {
try (Jedis jedis = jedisPool.getResource()) { try (Jedis jedis = jedisPool.getResource()) {
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid), jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]); RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name() plugin.debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
+ " key to redis at: " + + " key to redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));
} catch (Exception e) { } catch (Exception e) {
@ -177,12 +176,12 @@ public class RedisManager extends JedisPubSub {
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid); final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
final byte[] dataByteArray = jedis.get(key); final byte[] dataByteArray = jedis.get(key);
if (dataByteArray == null) { if (dataByteArray == null) {
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " + plugin.debug("[" + user.username + "] Could not read " +
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " + RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));
return Optional.empty(); return Optional.empty();
} }
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read " plugin.debug("[" + user.username + "] Successfully read "
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " + + RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));
@ -204,12 +203,12 @@ public class RedisManager extends JedisPubSub {
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid); final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
final byte[] readData = jedis.get(key); final byte[] readData = jedis.get(key);
if (readData == null) { if (readData == null) {
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " + plugin.debug("[" + user.username + "] Could not read " +
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " + RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));
return false; return false;
} }
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read " plugin.debug("[" + user.username + "] Successfully read "
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " + + RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
new SimpleDateFormat("mm:ss.SSS").format(new Date())); new SimpleDateFormat("mm:ss.SSS").format(new Date()));

@ -97,7 +97,7 @@ public class DataDumper {
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")"; return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to upload data to logs site", e); plugin.log(Level.SEVERE, "Failed to upload data to logs site", e);
} }
return "(Failed to upload to logs site)"; return "(Failed to upload to logs site)";
} }

@ -1,34 +0,0 @@
package net.william278.husksync.util;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Level;
/**
* An abstract, cross-platform representation of a logger
*/
public abstract class Logger {
private boolean debug;
public abstract void log(@NotNull Level level, @NotNull String message, @NotNull Throwable e);
public abstract void log(@NotNull Level level, @NotNull String message);
public abstract void info(@NotNull String message);
public abstract void severe(@NotNull String message);
public final void debug(@NotNull String message) {
if (debug) {
log(Level.INFO, "[DEBUG] " + message);
}
}
public abstract void config(@NotNull String message);
public final void showDebugLogs(boolean debug) {
this.debug = debug;
}
}

@ -1,20 +0,0 @@
package net.william278.husksync.util;
import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
/**
* Abstract representation of a reader that reads internal resource files by name
*/
public interface ResourceReader {
/**
* Gets the resource with given filename and reads it as an {@link InputStream}
*
* @param fileName Name of the resource file to read
* @return The resource, read as an {@link InputStream}; or {@code null} if the resource was not found
*/
@Nullable InputStream getResource(String fileName);
}
Loading…
Cancel
Save