diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/rpg/admin/ExportDataTreeNode.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/rpg/admin/ExportDataTreeNode.java index a8e9ce1b..b591c44e 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/rpg/admin/ExportDataTreeNode.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/rpg/admin/ExportDataTreeNode.java @@ -5,10 +5,11 @@ import io.lumine.mythic.lib.command.api.CommandTreeNode; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.manager.data.mysql.MySQLDataProvider; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; +import java.text.DecimalFormat; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -25,9 +26,27 @@ public class ExportDataTreeNode extends CommandTreeNode { super(parent, "exportdata"); } + /** + * Amount of requests generated every batch + */ + private static final int BATCH_AMOUNT = 50; + + /** + * Period in ticks + */ + private static final int BATCH_PERIOD = 20; + + private static final DecimalFormat decFormat = new DecimalFormat("0.#"); + @Override public CommandResult execute(CommandSender sender, String[] strings) { + if (!MMOCore.plugin.dataProvider.getDataManager().getLoaded().isEmpty()) { + sender.sendMessage("Please make sure no players are logged in when using this command. " + + "If you are still seeing this message, restart your server and execute this command before any player logs in."); + return CommandResult.FAILURE; + } + final List playerIds = Arrays.stream(new File(MMOCore.plugin.getDataFolder() + "/userdata").listFiles()) .map(file -> UUID.fromString(file.getName().replace(".yml", ""))).collect(Collectors.toList()); @@ -40,31 +59,48 @@ public class ExportDataTreeNode extends CommandTreeNode { return CommandResult.FAILURE; } + final double timeEstimation = (double) playerIds.size() / BATCH_AMOUNT * BATCH_PERIOD / 20; sender.sendMessage("Exporting " + playerIds.size() + " player data(s).. See console for details"); + sender.sendMessage("Minimum expected time: " + decFormat.format(timeEstimation) + "s"); // Save player data - Bukkit.getScheduler().runTaskAsynchronously(MMOCore.plugin, () -> { - - // Loop first for garbage collection + new BukkitRunnable() { int errorCount = 0; - for (UUID playerId : playerIds) - try { - final PlayerData offlinePlayerData = new PlayerData(new MMOPlayerData(playerId)); - MMOCore.plugin.dataProvider.getDataManager().loadData(offlinePlayerData); + int batchCounter = 0; - // Player data is loaded, now it gets saved through SQL - sqlProvider.getDataManager().saveData(offlinePlayerData); - } catch (RuntimeException exception) { - errorCount++; - exception.printStackTrace(); + @Override + public void run() { + for (int i = 0; i < BATCH_AMOUNT; i++) { + final int index = BATCH_AMOUNT * batchCounter + i; + + /* + * Saving is done. Close connection to avoid memory + * leaks and ouput the results to the command executor + */ + if (index >= playerIds.size()) { + cancel(); + + sqlProvider.close(); + MMOCore.plugin.getLogger().log(Level.WARNING, "Exported " + playerIds.size() + " player datas to SQL database. Total errors: " + errorCount); + return; + } + + final UUID playerId = playerIds.get(index); + try { + final PlayerData offlinePlayerData = new PlayerData(new MMOPlayerData(playerId)); + MMOCore.plugin.dataProvider.getDataManager().loadData(offlinePlayerData); + + // Player data is loaded, now it gets saved through SQL + sqlProvider.getDataManager().saveData(offlinePlayerData); + } catch (RuntimeException exception) { + errorCount++; + exception.printStackTrace(); + } } - MMOCore.plugin.getLogger().log(Level.WARNING, "Exported player data to SQL database. Total errors: " + errorCount); - - // Close connection to avoid memory leaks - sqlProvider.close(); - - }); + batchCounter++; + } + }.runTaskTimerAsynchronously(MMOCore.plugin, 0, BATCH_PERIOD); return CommandResult.SUCCESS; }