diff options
Diffstat (limited to 'bukkit/src/main/java')
6 files changed, 435 insertions, 19 deletions
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java index d50752fd..da8c791c 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java @@ -24,22 +24,15 @@ public class BukkitQuestsLogger implements QuestsLogger { @Override public void log(String str, LoggingLevel level) { + plugin.getLogHistory().record(level, () -> str); if (serverLoggingLevel.getNumericVerbosity() < level.getNumericVerbosity()) { return; } switch (level) { - case DEBUG: - plugin.getLogger().info("Debug: " + str); - break; - case INFO: - plugin.getLogger().info(str); - break; - case ERROR: - plugin.getLogger().severe(str); - break; - case WARNING: - plugin.getLogger().warning(str); - break; + case DEBUG -> plugin.getLogger().info("DEBUG: " + str); + case INFO -> plugin.getLogger().info(str); + case ERROR -> plugin.getLogger().severe(str); + case WARNING -> plugin.getLogger().warning(str); } } 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 c6e3e260..2006f609 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java @@ -33,6 +33,7 @@ import com.leonardobishop.quests.bukkit.storage.YamlStorageProvider; import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskTypeManager; import com.leonardobishop.quests.bukkit.tasktype.type.*; import com.leonardobishop.quests.bukkit.tasktype.type.dependent.*; +import com.leonardobishop.quests.bukkit.util.LogHistory; import com.leonardobishop.quests.common.config.ConfigProblem; import com.leonardobishop.quests.common.config.ConfigProblemDescriptions; import com.leonardobishop.quests.common.config.QuestsConfig; @@ -92,6 +93,7 @@ public class BukkitQuestsPlugin extends JavaPlugin implements Quests { private Title titleHandle; private VersionSpecificHandler versionSpecificHandler; + private LogHistory logHistory; private BukkitTask questAutoSaveTask; private BukkitTask questQueuePollTask; private BiFunction<Player, String, String> placeholderAPIProcessor; @@ -150,6 +152,7 @@ public class BukkitQuestsPlugin extends JavaPlugin implements Quests { public void onEnable() { // Initial module initialization this.questsLogger = new BukkitQuestsLogger(this); + this.logHistory = new LogHistory(true); this.generateConfigurations(); this.questsConfig = new BukkitQuestsConfig(new File(super.getDataFolder() + File.separator + "config.yml")); this.questManager = new QuestManager(this); @@ -602,6 +605,10 @@ public class BukkitQuestsPlugin extends JavaPlugin implements Quests { return questsConfig.getConfig(); } + public LogHistory getLogHistory() { + return logHistory; + } + @Override public void reloadConfig() { this.reloadBaseConfiguration(); 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 be86e6df..797ebcd7 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 @@ -24,6 +24,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { super.subcommands.put("update", new AdminUpdateCommandHandler(plugin)); super.subcommands.put("wiki", new AdminWikiCommandHandler(plugin)); super.subcommands.put("about", new AdminAboutCommandHandler(plugin)); + super.subcommands.put("debug", new AdminDebugCommandHandler(plugin)); } @Override @@ -42,6 +43,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { 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"); + sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a debug " + ChatColor.DARK_GRAY + ": generate a debug report"); } @Override diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java new file mode 100644 index 00000000..9e222327 --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java @@ -0,0 +1,334 @@ +package com.leonardobishop.quests.bukkit.command; + +import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.bukkit.item.QuestItem; +import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter; +import com.leonardobishop.quests.bukkit.util.LogHistory; +import com.leonardobishop.quests.common.player.QPlayer; +import com.leonardobishop.quests.common.player.QPlayerPreferences; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress; +import com.leonardobishop.quests.common.quest.Category; +import com.leonardobishop.quests.common.quest.Quest; +import com.leonardobishop.quests.common.quest.Task; +import com.leonardobishop.quests.common.tasktype.TaskType; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.util.*; +import java.util.function.Function; + +public class AdminDebugCommandHandler implements CommandHandler { + + private final BukkitQuestsPlugin plugin; + + private List<String> errors; + private CommandSender currentReportGenerator; + + public AdminDebugCommandHandler(BukkitQuestsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void handle(CommandSender sender, String[] args) { + if (currentReportGenerator != null) { + sender.sendMessage(ChatColor.RED + "A report is already being generated. Please wait until it is finished."); + return; + } + + errors = new ArrayList<>(); + currentReportGenerator = sender; + + try { + sender.sendMessage(ChatColor.GRAY + "Generating Quests debug report..."); + if (!plugin.getLogHistory().isEnabled()) { + sender.sendMessage(ChatColor.RED + "Log history is not enabled. Quests will only generate a basic debug report. " + + "If you were asked to generate one with log history, this must be turned on in your configuration."); + } + long start = System.currentTimeMillis(); + Path path = new File(plugin.getDataFolder() + File.separator + "debug" + File.separator + "debug_" + start + ".txt").toPath(); + try { + Path directory = new File(plugin.getDataFolder() + File.separator + "debug").toPath(); + Files.createDirectories(directory); + Files.createFile(path); + } catch (IOException e) { + sender.sendMessage(ChatColor.DARK_RED + "Failed to create debug file!"); + e.printStackTrace(); + return; + } + + List<String> lines = new ArrayList<>(); + lines.add("################################"); + lines.add("# Server Information #"); + lines.add("################################"); + lines.add(""); + lines.add("Server name: " + plugin.getServer().getName()); + lines.add("Server version: " + plugin.getServer().getVersion()); + lines.add("Bukkit version: " + plugin.getServer().getBukkitVersion()); + lines.add("Minecraft version: " + plugin.getServer().getMinecraftVersion()); + lines.add("Player count: " + plugin.getServer().getOnlinePlayers().size()); + lines.add(""); + + lines.add("################################"); + lines.add("# Quests Information #"); + lines.add("################################"); + lines.add(""); + lines.add("Quests version: " + plugin.getDescription().getVersion()); + lines.add(""); + printList(lines, 0, "Task types available", plugin.getTaskTypeManager().getTaskTypes(), TaskType::getType); + lines.add(""); + printList(lines, 0, "Quests", plugin.getQuestManager().getQuests().values(), Quest::getId); + lines.add(""); + printList(lines, 0, "Categories", plugin.getQuestManager().getCategories(), Category::getId); + lines.add(""); + BukkitQuestCompleter completer = (BukkitQuestCompleter) plugin.getQuestCompleter(); + printList(lines, 0, "Completion queue", completer.getCompletionQueue(), questProgress -> questProgress.getPlayer().toString()); + lines.add(""); + printList(lines, 0, "Full check queue", completer.getFullCheckQueue(), questProgressFile -> questProgressFile.getPlayerUUID().toString()); + lines.add(""); + printList(lines, 0, "Expired check queue", completer.getExpiredCheckQueue(), UUID::toString); + lines.add(""); + + lines.add("################################"); + lines.add("# Storage #"); + lines.add("################################"); + lines.add(""); + lines.add("Storage provider: " + plugin.getStorageProvider().getName()); + lines.add(""); + + lines.add("################################"); + lines.add("# Hook #"); + lines.add("################################"); + lines.add(""); + lines.add("Core protect hook: " + (plugin.getCoreProtectHook() != null)); + lines.add("Essentials hook: " + (plugin.getEssentialsHook() != null)); + lines.add("PlaceholderAPI hook: " + (plugin.getPlaceholderAPIHook() != null)); + lines.add("Item getter: " + plugin.getItemGetter().getClass().getSimpleName()); + lines.add("Title handler: " + plugin.getTitleHandle().getClass().getSimpleName()); + lines.add("Version specific handler: " + plugin.getVersionSpecificHandler().getClass().getSimpleName()); + lines.add(""); + + lines.add("################################"); + lines.add("# Items #"); + lines.add("################################"); + lines.add(""); + lines.add("Number of items: " + plugin.getQuestItemRegistry().getAllItems().size()); + lines.add(""); + //TODO this + + lines.add("################################"); + lines.add("# Quests #"); + lines.add("################################"); + lines.add(""); + lines.add("Number of quests: " + plugin.getQuestManager().getQuests().size()); + lines.add(""); + for (Quest quest : plugin.getQuestManager().getQuests().values()) { + Map<String, Object> questValues = getFieldValues(quest, "tasks", "tasksByType"); + try { + Field tasksField = quest.getClass().getDeclaredField("tasks"); + tasksField.setAccessible(true); + Map<String, Task> tasksMap = (Map<String, Task>) tasksField.get(quest); + Map<String, Object> tasksValues = new HashMap<>(); + for (Map.Entry<String, Task> taskEntry : tasksMap.entrySet()) { + Task task = taskEntry.getValue(); + tasksValues.put(task.getId(), getFieldValues(task)); + } + questValues.put("tasks", tasksValues); + } catch (NoSuchFieldException | IllegalAccessException e) { + error("Failed to get tasks for quest " + quest.getId() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + + printMap(lines, 0, "Quest '" + quest.getId() + "'", questValues); + lines.add(""); + } + + lines.add("################################"); + lines.add("# Players #"); + lines.add("################################"); + lines.add(""); + printList(lines, 0, "Players online", Bukkit.getOnlinePlayers(), player -> player.getUniqueId().toString()); + lines.add(""); + printList(lines, 0, "QPlayers loaded", plugin.getPlayerManager().getQPlayers(), qPlayer -> qPlayer.getPlayerUUID().toString()); + lines.add(""); + for (QPlayer qPlayer : plugin.getPlayerManager().getQPlayers()) { + lines.add("QPlayer " + qPlayer.getPlayerUUID() + ":"); + QPlayerPreferences preferences = qPlayer.getPlayerPreferences(); + printMap(lines, 1, "Preferences", getFieldValues(preferences)); + + QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile(); + try { + Field questProgressField = questProgressFile.getClass().getDeclaredField("questProgress"); + questProgressField.setAccessible(true); + Map<String, QuestProgress> questProgressMap = (Map<String, QuestProgress>) questProgressField.get(questProgressFile); + Map<String, Object> questProgressValues = new LinkedHashMap<>(); + for (Map.Entry<String, QuestProgress> entry : questProgressMap.entrySet()) { + QuestProgress questProgress = entry.getValue(); + Map<String, Object> questProgressValue = getFieldValues(questProgress, "plugin", "taskProgress"); + + Field taskProgressField = questProgress.getClass().getDeclaredField("taskProgress"); + taskProgressField.setAccessible(true); + Map<String, TaskProgress> taskProgressMap = (Map<String, TaskProgress>) taskProgressField.get(questProgress); + Map<String, Object> taskProgressValues = new LinkedHashMap<>(); + for (Map.Entry<String, TaskProgress> taskEntry : taskProgressMap.entrySet()) { + TaskProgress taskProgress = taskEntry.getValue(); + taskProgressValues.put(taskEntry.getKey(), getFieldValues(taskProgress, "plugin", "linkedQuestProgress")); + } + questProgressValue.put("taskProgress", taskProgressValues); + + questProgressValues.put(entry.getKey(), questProgressValue); + } + printMap(lines, 1, "Quest progress", questProgressValues); + } catch (NoSuchFieldException | IllegalAccessException e) { + error("Failed to get quest progress for QPlayer " + qPlayer.getPlayerUUID() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + lines.add(" Quest controller: " + qPlayer.getQuestController().getName()); + lines.add(""); + } + + lines.add("################################"); + lines.add("# Log History #"); + lines.add("################################"); + lines.add(""); + for (LogHistory.LogEntry line : plugin.getLogHistory().getEntries()) { + lines.add(String.format("[%s/%s/%s] %s", line.getTime(), line.getType().toString(), line.getThread(), line.getEntry())); + } + + List<String> errors = new ArrayList<>(); + lines.add(0, ""); + printList(errors, 0, "Errors generating report", this.errors, String::valueOf); + lines.addAll(0, errors); + lines.add(0, "Time taken: " + (System.currentTimeMillis() - start) + "ms"); + lines.add(0, "Generated at: " + new Date(start) + " (" + start + ")"); + lines.add(0, ""); + lines.add(0, "################################"); + lines.add(0, "# Report Information #"); + lines.add(0, "################################"); + try { + Files.write(path, lines, StandardCharsets.UTF_8); + } catch (IOException e) { + error("Failed to write report to " + path + "!"); + e.printStackTrace(); + return; + } + + sender.sendMessage(ChatColor.GREEN + "Debug file created at " + path + ". (Took " + (System.currentTimeMillis() - start) + "ms.)"); + } finally { + currentReportGenerator = null; + errors = new ArrayList<>(); + } + } + + private Map<String, Object> getFieldValues(Object object, String... excludeFields) { + Field[] fields = object.getClass().getDeclaredFields(); + Map<String, Object> values = new LinkedHashMap<>(); + for (Field field : fields) { + if (Arrays.asList(excludeFields).contains(field.getName())) { + continue; + } + field.setAccessible(true); + try { + values.put(field.getName(), field.get(object)); + } catch (IllegalAccessException e) { + error("Failed to get field value for " + object.getClass().getSimpleName() + "." + field.getName() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + } + return values; + } + + private <E> void printList(List<String> lines, int depth, String title, Collection<E> list) { + printList(lines, depth, title, list, Object::toString); + } + + private <E> void printList(List<String> lines, int depth, String title, Collection<E> list, Function<E, String> getter) { + depth = depth * 4; + if (list.size() == 0) { + lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); + return; + } + lines.add(String.format("%s%s (%d):", " ".repeat(depth), title, list.size())); + for (E element : list) { + lines.add(String.format("%s - %s", " ".repeat(depth), getter.apply(element))); + } + } + + private <K, V> void printMap(List<String> lines, int depth, String title, Map<K, V> map) { + depth = depth * 4; + if (map.size() == 0) { + lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); + return; + } + lines.add(String.format("%s%s:", " ".repeat(depth), title)); + int keyMaxLength = 1; + int valueMaxLength = 1; + Map<String, String> stringifiedValues = new HashMap<>(); + for (Map.Entry<K, V> entry : map.entrySet()) { + String key = String.valueOf(entry.getKey()); + if (key.length() > keyMaxLength) { + keyMaxLength = key.length(); + } + } + for (Map.Entry<K, V> entry : map.entrySet()) { + String value; + if (entry.getValue() instanceof Map) { + List<String> subLines = new ArrayList<>(); + printMap(subLines, 0, "Map", (Map<?, ?>) entry.getValue()); + value = String.join("\n", subLines); + } else if (entry.getValue() instanceof List) { + List<String> subLines = new ArrayList<>(); + printList(subLines, 0, "List", (Collection<?>) entry.getValue()); + value = String.join("\n", subLines); + } else { + value = String.valueOf(entry.getValue()); + } + + for (String line : value.split("\n")) { + if (line.length() > valueMaxLength) { + valueMaxLength = line.length(); + } + } + stringifiedValues.put(String.valueOf(entry.getKey()), value); + } + String separator = String.format("%s|-%-" + keyMaxLength + "s-+-%-" + valueMaxLength + "s-|", " ".repeat(depth), "-".repeat(keyMaxLength), "-".repeat(valueMaxLength)); + lines.add(separator); + for (Map.Entry<String, String> entry : stringifiedValues.entrySet()) { + String value = entry.getValue(); + boolean firstLine = true; + for (String line : value.split("\n")) { + lines.add(String.format("%s| %-" + keyMaxLength + "s | %-" + valueMaxLength + "s |", " ".repeat(depth), firstLine ? entry.getKey() : "", line)); + firstLine = false; + } + lines.add(separator); + } + } + + private void error(String error) { + errors.add(error); + currentReportGenerator.sendMessage(ChatColor.RED + error); + } + + @Override + public List<String> tabComplete(CommandSender sender, String[] args) { + return Collections.emptyList(); + } + + @Override + public @Nullable String getPermission() { + return "quests.admin"; + } +} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java index 12342bd2..bf264a69 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java @@ -13,17 +13,14 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.LinkedList; -import java.util.Objects; -import java.util.Queue; -import java.util.UUID; +import java.util.*; //TODO move complete effects here ? public class BukkitQuestCompleter implements QuestCompleter, Runnable { - private final Queue<QuestProgress> completionQueue = new LinkedList<>(); - private final Queue<QuestProgressFile> fullCheckQueue = new LinkedList<>(); - private final Queue<UUID> expiredCheckQueue = new LinkedList<>(); + private final LinkedList<QuestProgress> completionQueue = new LinkedList<>(); + private final LinkedList<QuestProgressFile> fullCheckQueue = new LinkedList<>(); + private final LinkedList<UUID> expiredCheckQueue = new LinkedList<>(); private final BukkitQuestsPlugin plugin; private int expiredQuestsCheckCountdown; @@ -147,4 +144,16 @@ public class BukkitQuestCompleter implements QuestCompleter, Runnable { fullCheckQueue.add(questProgressFile); } + + public List<QuestProgress> getCompletionQueue() { + return Collections.unmodifiableList(completionQueue); + } + + public List<QuestProgressFile> getFullCheckQueue() { + return Collections.unmodifiableList(fullCheckQueue); + } + + public List<UUID> getExpiredCheckQueue() { + return Collections.unmodifiableList(expiredCheckQueue); + } } diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/LogHistory.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/LogHistory.java new file mode 100644 index 00000000..293f7c1f --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/LogHistory.java @@ -0,0 +1,71 @@ +package com.leonardobishop.quests.bukkit.util; + +import com.leonardobishop.quests.common.logger.QuestsLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class LogHistory { + + private final boolean enabled; + + private final List<LogEntry> entries = new ArrayList<>(); + + public LogHistory(boolean enabled) { + this.enabled = enabled; + } + + public void record(QuestsLogger.LoggingLevel type, Supplier<String> supplier) { + if (enabled) { + String entry = supplier.get(); + String thread = Thread.currentThread().getName(); + long time = System.currentTimeMillis(); + + LogEntry logEntry = new LogEntry(entry, type, thread, time); + + synchronized (this) { + entries.add(logEntry); + } + } + } + + public synchronized List<LogEntry> getEntries() { + return Collections.unmodifiableList(entries); + } + + public boolean isEnabled() { + return enabled; + } + + public static class LogEntry { + private final String entry; + private final QuestsLogger.LoggingLevel type; + private final String thread; + private final long time; + + public LogEntry(String entry, QuestsLogger.LoggingLevel type, String thread, long time) { + this.entry = entry; + this.type = type; + this.thread = thread; + this.time = time; + } + + public String getEntry() { + return entry; + } + + public QuestsLogger.LoggingLevel getType() { + return type; + } + + public String getThread() { + return thread; + } + + public long getTime() { + return time; + } + } +} |
