diff options
| author | LMBishop <13875753+LMBishop@users.noreply.github.com> | 2022-04-22 20:19:18 +0100 |
|---|---|---|
| committer | LMBishop <13875753+LMBishop@users.noreply.github.com> | 2022-04-22 20:19:18 +0100 |
| commit | 894f1c2ed9356556954889f82c0a473354f83808 (patch) | |
| tree | 8ca8a47de37e2ebc7dfe33e5cce68ce423c1e13e /bukkit/src/main/java/com | |
| parent | 43e647990b611205676bb42623a3814a904fe0e8 (diff) | |
Add quests data migration tool
Diffstat (limited to 'bukkit/src/main/java/com')
5 files changed, 272 insertions, 1 deletions
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java index aa202b61..e013cf7f 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java @@ -526,7 +526,7 @@ public class BukkitQuestsPlugin extends JavaPlugin implements Quests { } } - private void writeResourceToFile(String resource, File file) { + public void writeResourceToFile(String resource, File file) { try { file.createNewFile(); try (InputStream in = BukkitQuestsPlugin.class.getClassLoader().getResourceAsStream(resource); diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java index 9afffdf3..be86e6df 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java @@ -20,6 +20,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { super.subcommands.put("reload", new AdminReloadCommandHandler(plugin)); super.subcommands.put("items", new AdminItemsCommandHandler(plugin)); super.subcommands.put("config", new AdminConfigCommandHandler(plugin)); + super.subcommands.put("migratedata", new AdminMigrateCommandHandler(plugin)); super.subcommands.put("update", new AdminUpdateCommandHandler(plugin)); super.subcommands.put("wiki", new AdminWikiCommandHandler(plugin)); super.subcommands.put("about", new AdminAboutCommandHandler(plugin)); @@ -37,6 +38,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a items [import <id>] " + ChatColor.DARK_GRAY + ": view registered quest items"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a reload " + ChatColor.DARK_GRAY + ": reload Quests configuration"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a config " + ChatColor.DARK_GRAY + ": see detected problems in config"); + sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a migratedata " + ChatColor.DARK_GRAY + ": migrate quests data"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a update " + ChatColor.DARK_GRAY + ": check for updates"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a wiki " + ChatColor.DARK_GRAY + ": get a link to the Quests wiki"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a about " + ChatColor.DARK_GRAY + ": get information about Quests"); diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminMigrateCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminMigrateCommandHandler.java new file mode 100644 index 00000000..39be3ea8 --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminMigrateCommandHandler.java @@ -0,0 +1,172 @@ +package com.leonardobishop.quests.bukkit.command; + +import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.bukkit.storage.MySqlStorageProvider; +import com.leonardobishop.quests.bukkit.storage.YamlStorageProvider; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.storage.StorageProvider; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class AdminMigrateCommandHandler implements CommandHandler { + + private final BukkitQuestsPlugin plugin; + + private final AtomicBoolean migrationInProgress; + + public AdminMigrateCommandHandler(BukkitQuestsPlugin plugin) { + this.plugin = plugin; + this.migrationInProgress = new AtomicBoolean(false); + } + + @Override + public void handle(CommandSender sender, String[] args) { + File dataMigrateFile = new File(plugin.getDataFolder(), "migrate_data.yml"); + + if (migrationInProgress.get()) { + sender.sendMessage(ChatColor.RED + "A migration is already in progress."); + } + + if (args.length == 3 && args[2].equalsIgnoreCase("execute")) { + if (!dataMigrateFile.exists()) { + sender.sendMessage(ChatColor.RED + "Please run '/quests admin migratedata' first."); + return; + } + + YamlConfiguration configuration; + try { + configuration = YamlConfiguration.loadConfiguration(dataMigrateFile); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "An error occurred while loading the data migration file."); + e.printStackTrace(); + sender.sendMessage(ChatColor.RED + "See server console for more details."); + return; + } + + if (!configuration.getBoolean("ready")) { + sender.sendMessage(ChatColor.RED + "The 'ready' flag has not been set."); + sender.sendMessage(ChatColor.RED + "Please see the migrate_data.yml file, or the wiki, for instructions."); + return; + } + + ConfigurationSection fromConfiguration = configuration.getConfigurationSection("from"); + ConfigurationSection toConfiguration = configuration.getConfigurationSection("to"); + + if (fromConfiguration == null || toConfiguration == null) { + sender.sendMessage(ChatColor.RED + "The 'from' and 'to' sections have not been configured."); + sender.sendMessage(ChatColor.RED + "Please see the migrate_data.yml file, or the wiki, for instructions."); + return; + } + + StorageProvider fromProvider = getStorageProvider(fromConfiguration); + StorageProvider toProvider = getStorageProvider(toConfiguration); + + if (fromProvider.getName().equals("yaml") && toProvider.getName().equals("yaml")) { + //TODO check mysql databases aren't the same as well + sender.sendMessage(ChatColor.RED + "Refusing to migrate from 'yaml' to 'yaml'."); + sender.sendMessage(ChatColor.RED + "Please see the migrate_data.yml file, or the wiki, for instructions."); + return; + } + + long startTime = System.currentTimeMillis(); + sender.sendMessage(ChatColor.GRAY + "Performing migration..."); + migrationInProgress.set(true); + plugin.getScheduler().doAsync(() -> { + try { + sender.sendMessage(ChatColor.GRAY + "Initialising storage provider '" + fromProvider.getName() + "'..."); + fromProvider.init(); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "An error occurred while initializing '" + fromProvider.getName() + "' storage provider."); + return; + } + + try { + sender.sendMessage(ChatColor.GRAY + "Initialising storage provider '" + toProvider.getName() + "'..."); + toProvider.init(); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "An error occurred while initializing '" + toProvider.getName() + "' storage provider."); + return; + } + + sender.sendMessage(ChatColor.GRAY + "Loading quest progress files from '" + fromProvider.getName() + "'..."); + List<QuestProgressFile> files = fromProvider.loadAllProgressFiles(); + sender.sendMessage(ChatColor.GRAY.toString() + files.size() + " files loaded."); + + for (QuestProgressFile file : files) { + file.setModified(true); + } + + sender.sendMessage(ChatColor.GRAY + "Writing quest progress files to '" + toProvider.getName() + "'..."); + toProvider.saveAllProgressFiles(files); + sender.sendMessage(ChatColor.GRAY + "Done."); + + try { + sender.sendMessage(ChatColor.GRAY + "Shutting down storage provider '" + fromProvider.getName() + "'..."); + fromProvider.shutdown(); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "An error occurred while shutting down '" + fromProvider.getName() + "' storage provider."); + } + + try { + sender.sendMessage(ChatColor.GRAY + "Shutting down storage provider '" + toProvider.getName() + "'..."); + toProvider.shutdown(); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "An error occurred while shutting down '" + toProvider.getName() + "' storage provider."); + } + + long endTime = System.currentTimeMillis(); + sender.sendMessage(ChatColor.GREEN + "Migration complete. Took " + String.format("%.3f", (endTime - startTime) / 1000f) + "s."); + + configuration.set("ready", false); + try { + configuration.save(dataMigrateFile); + } catch (IOException ignored) { } + migrationInProgress.set(false); + }); + return; + } + + if (!dataMigrateFile.exists()) { + plugin.writeResourceToFile("resources/bukkit/migrate_data.yml", dataMigrateFile); + } + sender.sendMessage(ChatColor.GRAY + "A file has been generated at /plugins/Quests/migrate_data.yml."); + sender.sendMessage(ChatColor.GRAY + "Please see this file, or the wiki, for further instructions."); + } + + private StorageProvider getStorageProvider(ConfigurationSection configurationSection) { + String configuredProvider = configurationSection.getString("provider", "yaml"); + StorageProvider storageProvider; + switch (configuredProvider.toLowerCase()) { + default: + case "yaml": + storageProvider = new YamlStorageProvider(plugin); + break; + case "mysql": + ConfigurationSection section = configurationSection.getConfigurationSection("database-settings"); + storageProvider = new MySqlStorageProvider(plugin, section); + } + return storageProvider; + } + + @Override + public List<String> tabComplete(CommandSender sender, String[] args) { + if (args.length == 3) { + return TabHelper.matchTabComplete(args[2], Collections.singletonList("execute")); + } + return Collections.emptyList(); + } + + @Override + public @Nullable String getPermission() { + return "quests.admin"; + } +} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java index 13b0ac7d..5400afb0 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java @@ -41,6 +41,8 @@ public class MySqlStorageProvider implements StorageProvider { "SELECT quest_id, started, completed, completed_before, completion_date FROM `{prefix}quest_progress` WHERE uuid=?;"; private static final String SELECT_PLAYER_TASK_PROGRESS = "SELECT quest_id, task_id, completed, progress, data_type FROM `{prefix}task_progress` WHERE uuid=?;"; + private static final String SELECT_UUID_LIST = + "SELECT DISTINCT uuid FROM `{prefix}quest_progress`;"; private static final String SELECT_KNOWN_PLAYER_QUEST_PROGRESS = "SELECT quest_id FROM `{prefix}quest_progress` WHERE uuid=?;"; private static final String SELECT_KNOWN_PLAYER_TASK_PROGRESS = @@ -67,6 +69,11 @@ public class MySqlStorageProvider implements StorageProvider { } @Override + public String getName() { + return "mysql"; + } + + @Override public void init() { String address = configuration.getString("network.address", "localhost:3306"); String database = configuration.getString("network.database", "minecraft"); @@ -285,4 +292,47 @@ public class MySqlStorageProvider implements StorageProvider { e.printStackTrace(); } } + + @Override + public @NotNull List<QuestProgressFile> loadAllProgressFiles() { + if (fault) return Collections.emptyList(); + + Set<UUID> uuids = new HashSet<>(); + + try (Connection connection = hikari.getConnection()) { + try (PreparedStatement ps = connection.prepareStatement(this.statementProcessor.apply(SELECT_UUID_LIST))) { + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + String uuidString = rs.getString(1); + try { + UUID uuid = UUID.fromString(uuidString); + uuids.add(uuid); + } catch (IllegalArgumentException ignored) { } + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + return Collections.emptyList(); + } + + List<QuestProgressFile> files = new ArrayList<>(); + for (UUID uuid : uuids) { + QuestProgressFile file = loadProgressFile(uuid); + if (file != null) { + files.add(file); + } + } + + return files; + } + + @Override + public void saveAllProgressFiles(List<QuestProgressFile> files) { + if (fault) return; + + for (QuestProgressFile file : files) { + saveProgressFile(file.getPlayerUUID(), file); + } + } } diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java index 0a20f3cf..e6848e06 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java @@ -12,6 +12,8 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; @@ -33,6 +35,11 @@ public class YamlStorageProvider implements StorageProvider { } @Override + public String getName() { + return "yaml"; + } + + @Override public void init() { File directory = new File(plugin.getDataFolder() + File.separator + "playerdata"); directory.mkdirs(); @@ -147,4 +154,44 @@ public class YamlStorageProvider implements StorageProvider { lock.unlock(); } } + + public @NotNull List<QuestProgressFile> loadAllProgressFiles() { + List<QuestProgressFile> files = new ArrayList<>(); + + File directory = new File(plugin.getDataFolder() + File.separator + "playerdata"); + FileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) { + if (path.toString().endsWith(".yml")) { + UUID uuid; + try { + uuid = UUID.fromString(path.getFileName().toString().replace(".yml", "")); + } catch (IllegalArgumentException e) { + return FileVisitResult.CONTINUE; + } + + QuestProgressFile file = loadProgressFile(uuid); + if (file != null) { + files.add(file); + } + } + return FileVisitResult.CONTINUE; + } + }; + + try { + Files.walkFileTree(directory.toPath(), fileVisitor); + } catch (IOException e) { + e.printStackTrace(); + } + + return files; + } + + @Override + public void saveAllProgressFiles(List<QuestProgressFile> files) { + for (QuestProgressFile file : files) { + saveProgressFile(file.getPlayerUUID(), file); + } + } } |
