From 06d3aeb9d18673adff4b7c3e373944933a061d2b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 6 Apr 2023 14:23:26 +0200 Subject: [PATCH] Basic SchemSearch --- src/de/steamwar/bungeecore/BungeeCore.java | 2 + .../commands/SchemSearchTestCommand.java | 40 +++ .../listeners/SchematicSearchListener.java | 22 ++ .../bungeecore/util/SchematicSearch.java | 252 ++++++++++++++++++ .../steamwar/messages/BungeeCore.properties | 11 + 5 files changed, 327 insertions(+) create mode 100644 src/de/steamwar/bungeecore/commands/SchemSearchTestCommand.java create mode 100644 src/de/steamwar/bungeecore/listeners/SchematicSearchListener.java create mode 100644 src/de/steamwar/bungeecore/util/SchematicSearch.java diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index ec81983..e08e461 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -122,6 +122,7 @@ public class BungeeCore extends Plugin { new Fabric(); new SubserverProtocolFixer(); new PingListener(); + new SchematicSearchListener(); local = new Node.LocalNode(); if(MAIN_SERVER) { @@ -170,6 +171,7 @@ public class BungeeCore extends Plugin { new CalendarListener(); new ModCommand(); + new SchemSearchTestCommand(); // Punishment Commands: new PunishmentCommand("ban", Punishment.PunishmentType.Ban); diff --git a/src/de/steamwar/bungeecore/commands/SchemSearchTestCommand.java b/src/de/steamwar/bungeecore/commands/SchemSearchTestCommand.java new file mode 100644 index 0000000..595f97d --- /dev/null +++ b/src/de/steamwar/bungeecore/commands/SchemSearchTestCommand.java @@ -0,0 +1,40 @@ +package de.steamwar.bungeecore.commands; + +import de.steamwar.bungeecore.util.SchematicSearch; +import de.steamwar.command.PreviousArguments; +import de.steamwar.command.SWCommand; +import de.steamwar.command.TypeMapper; +import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SteamwarUser; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.Collection; + +public class SchemSearchTestCommand extends SWCommand { + public SchemSearchTestCommand() { + super("schemsearch"); + } + + + @Register + public void genericCommand(ProxiedPlayer player, SchematicNode node) { + SchematicSearch.queueSearch(player, node); + } + + @ClassMapper(SchematicNode.class) + public TypeMapper getSchematicNodeMapper() { + return new TypeMapper() { + + @Override + public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { + return SchematicNode.getNodeFromPath(SteamwarUser.get(commandSender.getName()), s); + } + + @Override + public Collection tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) { + return SchematicNode.getNodeTabcomplete(SteamwarUser.get(sender.getName()), s); + } + }; + } +} diff --git a/src/de/steamwar/bungeecore/listeners/SchematicSearchListener.java b/src/de/steamwar/bungeecore/listeners/SchematicSearchListener.java new file mode 100644 index 0000000..103ebe0 --- /dev/null +++ b/src/de/steamwar/bungeecore/listeners/SchematicSearchListener.java @@ -0,0 +1,22 @@ +package de.steamwar.bungeecore.listeners; + +import de.steamwar.bungeecore.Message; +import de.steamwar.bungeecore.util.SchematicSearch; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.ServerSwitchEvent; +import net.md_5.bungee.event.EventHandler; + +public class SchematicSearchListener extends BasicListener { + + @EventHandler + public void onServerSwitch(ServerSwitchEvent event) { + if(SchematicSearch.removeFromQueue(event.getPlayer())) { + Message.send("SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE", event.getPlayer()); + } + } + + @EventHandler + public void onQuit(PlayerDisconnectEvent event) { + SchematicSearch.removeFromQueue(event.getPlayer()); + } +} diff --git a/src/de/steamwar/bungeecore/util/SchematicSearch.java b/src/de/steamwar/bungeecore/util/SchematicSearch.java new file mode 100644 index 0000000..0f46753 --- /dev/null +++ b/src/de/steamwar/bungeecore/util/SchematicSearch.java @@ -0,0 +1,252 @@ +package de.steamwar.bungeecore.util; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import de.steamwar.bungeecore.BungeeCore; +import de.steamwar.bungeecore.Message; +import de.steamwar.sql.NodeData; +import de.steamwar.sql.SchematicNode; +import de.steamwar.sql.SteamwarUser; +import lombok.AllArgsConstructor; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import org.apache.commons.lang3.time.DurationFormatUtils; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +public class SchematicSearch { + + private static final Gson gson = new Gson(); + private static final String searchBinary = "/home/chaoscaot/schemsearch/target/release/schemsearch-cli"; + private static final List searchQueue = new ArrayList<>(); + private static SchematicSearch currentSearch; + private static ScheduledTask watchdog; + + private static void startQueueWatchdog() { + watchdog = BungeeCore.get().getProxy().getScheduler().schedule(BungeeCore.get(), () -> { + synchronized (searchQueue) { + if(currentSearch == null) { + if(!searchQueue.isEmpty()) { + currentSearch = searchQueue.remove(0); + currentSearch.start(); + } else { + watchdog.cancel(); + watchdog = null; + } + } + } + }, 0, 1, TimeUnit.SECONDS); + } + + public static void queueSearch(ProxiedPlayer player, SchematicNode node ) { + SteamwarUser user = SteamwarUser.get(player.getUniqueId()); + synchronized (searchQueue) { + if(user.getUserGroup().isAdminGroup()) { + searchQueue.add(0, new SchematicSearch(player, node)); + } else { + searchQueue.add(new SchematicSearch(player, node)); + } + } + if(watchdog == null) { + startQueueWatchdog(); + } else { + Message.send("SCHEMATIC_SEARCH_QUEUED", player); + } + } + + private static List constructArguments(SteamwarUser user, File pattern) { + return Arrays.asList(searchBinary, "-T", "1", "-s", "-u", String.valueOf(user.getId()), "-o", "json:std", "-m", "50", pattern.getAbsolutePath()); + } + + public static boolean removeFromQueue(ProxiedPlayer player) { + boolean removed = false; + synchronized (searchQueue) { + removed = searchQueue.removeIf(search -> search.player.equals(player)); + } + + if (currentSearch != null && currentSearch.player.equals(player)) { + currentSearch.end(); + removed = true; + } + + return removed; + } + + private static File schematicNodeToTempFile(NodeData node) { + try { + File f = File.createTempFile("schemsearch", ".schem"); + f.deleteOnExit(); + OutputStream os = new GZIPOutputStream(Files.newOutputStream(f.toPath())); + InputStream is = node.schemData(); + + byte[] buffer = new byte[1024]; + int read; + while((read = is.read(buffer)) != -1) { + os.write(buffer, 0, read); + } + + os.close(); + is.close(); + return f; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final ProxiedPlayer player; + private final SchematicNode node; + private File pattern; + private ScheduledTask task; + private Process process; + + private SchematicSearch(ProxiedPlayer player, SchematicNode node) { + this.player = player; + this.node = node; + } + + private static String readInputStream(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(); + byte[] buffer = new byte[1024]; + long total = 0; + while (is.available() > 0) { + int read = is.read(buffer); + total += read; + if (read > 0) { + sb.append(new String(buffer, 0, read)); + } + if (total > 1024 * 128) { + break; + } + } + return sb.toString(); + } + + private void start() { + task = BungeeCore.get().getProxy().getScheduler().runAsync(BungeeCore.get(), () -> { + try { + NodeData data = NodeData.get(node); + if(!data.getNodeFormat()) { + Message.send("SCHEMATIC_SEARCH_NOT_SUPPORTED", player); + end(); + return; + } + Message.send("SCHEMATIC_SEARCH_STARTED", player, node.getName()); + pattern = schematicNodeToTempFile(data); + ProcessBuilder builder = new ProcessBuilder(constructArguments(SteamwarUser.get(player.getUniqueId()), pattern)); + process = builder.start(); + InputStream stdout = process.getInputStream(); + InputStream stderr = process.getErrorStream(); + + while (!process.waitFor(100, TimeUnit.MILLISECONDS)) { + String s = readInputStream(stderr); + if(s.length() > 0) { + if(s.contains("s[")) { + s = s.substring(s.lastIndexOf("s[") + 1); + } + BungeeCore.send(player, ChatMessageType.ACTION_BAR, "§7" + s.replace("█", "§e█§7")); + } + } + + + String s = readInputStream(stderr); + if(s.contains("s[")) { + s = s.substring(s.lastIndexOf("s[") + 1); + } + BungeeCore.send(player, ChatMessageType.ACTION_BAR, "§7" + s.replace("█", "§e█§7")); + s = readInputStream(stdout); + String[] outputs = s.split("\n"); + + List elements = Arrays.stream(outputs).map(JsonParser::parseString).map(JsonElement::getAsJsonObject).collect(Collectors.toList()); + + int searchCount = 0; + long searchTime = 0; + List matches = new ArrayList<>(); + + for (JsonObject element : elements) { + switch (element.get("event").getAsString()) { + case "Init": + searchCount = element.get("total").getAsInt(); + break; + case "Found": + matches.add(new Match(element.get("x").getAsInt(), element.get("y").getAsInt(), element.get("z").getAsInt(), element.get("percent").getAsFloat() * 100, element.get("name").getAsString())); + break; + case "End": + searchTime = element.get("end_time").getAsLong(); + break; + default: + break; + } + } + + Message.send("SCHEMATIC_SEARCH_RESULT_HEADER", player, searchCount, DurationFormatUtils.formatDuration(searchTime, "' 'm'm 's's 'S'ms'") + .replace(" 0ms", "") + .replace(" 0s", "") + .replace(" 0m", "")); + + if (matches.size() <= 1) { + Message.send("SCHEMATIC_SEARCH_NO_RESULTS", player); + end(); + return; + } + + SteamwarUser user = SteamwarUser.get(player.getUniqueId()); + for (Match match : matches) { + String[] nameSplit = match.name.split(" "); + String name = nameSplit[0]; + int id = Integer.parseInt(nameSplit[1].substring(1, nameSplit[1].length() - 1)); + if (id == node.getId()) continue; + Message.sendPrefixless("SCHEMATIC_SEARCH_RESULT", player, Message.parse("SCHEMATIC_SEARCH_RESULT_HOVER", player, name), + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/schem info " + SchematicNode.byIdAndUser(user, id).generateBreadcrumbs()), + name, match.percent, match.x + 1, match.y + 1, match.z + 1); + } + + if (matches.size() >= 49) { + Message.send("SCHEMATIC_SEARCH_TOO_MANY_RESULTS", player); + } + + end(); + } catch (Exception e) { + end(); + throw new SecurityException(e); + } + }); + } + + public void end() { + if(process != null && process.isAlive()) { + process.destroy(); + } + if(pattern != null) { + pattern.delete(); + } + if(task != null) { + task.cancel(); + } + + synchronized (searchQueue) { + if(currentSearch == this) { + currentSearch = null; + } + } + } + + @AllArgsConstructor + private static class Match { + int x; + int y; + int z; + float percent; + String name; + } +} diff --git a/src/de/steamwar/messages/BungeeCore.properties b/src/de/steamwar/messages/BungeeCore.properties index 1845f38..e06383e 100644 --- a/src/de/steamwar/messages/BungeeCore.properties +++ b/src/de/steamwar/messages/BungeeCore.properties @@ -680,3 +680,14 @@ MOD_FORBIDDEN=§eForbidden MOD_AUTOBAN=§cAutoban MOD_YT=§5YT Only MOD_ITEM_BACK=§7Back + +#Schematic Search +SCHEMATIC_SEARCH_QUEUED=§7Your search has been queued and will be executed shortly. +SCHEMATIC_SEARCH_STARTED=§7Your search for "§e{0}§7" has started. +SCHEMATIC_SEARCH_NO_RESULTS=§cNo results found. +SCHEMATIC_SEARCH_RESULT_HEADER=§7Searched in §e{0} §7schematics in§e{1}. +SCHEMATIC_SEARCH_RESULT=§7{0}: §e{1}§7% §8(§e{2}§7,§e{3},§e{4}§8) +SCHEMATIC_SEARCH_RESULT_HOVER=§7Click to get more info about {0}. +SCHEMATIC_SEARCH_NOT_SUPPORTED=§cThis schematic is not supported by the schematic search. +SCHEMATIC_SEARCH_REMOVED_FROM_QUEUE=§cYour search has been removed from the queue because you switched servers. +SCHEMATIC_SEARCH_TOO_MANY_RESULTS=§cToo many results found. Please be more specific. \ No newline at end of file