From 2b6315a914aa8da92457738dc5d5de2e7b77ce4a Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Tue, 22 Sep 2020 15:12:07 +0200 Subject: [PATCH] Rewrites console output. Closes #4 Makes the servers be responsible for their own consoles Starts reading from console once a server starts, and stops once the server stops Makes reading from a buffered reader read character by character rather than line by line to fix reading from streams Makes sure each server's console is read on different threads to prevent lockup Prevents possible console bugs caused by only reading servers already enabled on startup --- .../minecraftserverlauncher/Main.java | 108 ++++-------------- .../server/Server.java | 90 ++++++++++++++- .../utility/CommonFunctions.java | 9 +- 3 files changed, 111 insertions(+), 96 deletions(-) diff --git a/src/main/java/net/knarcraft/minecraftserverlauncher/Main.java b/src/main/java/net/knarcraft/minecraftserverlauncher/Main.java index 101c326..60a588e 100644 --- a/src/main/java/net/knarcraft/minecraftserverlauncher/Main.java +++ b/src/main/java/net/knarcraft/minecraftserverlauncher/Main.java @@ -2,10 +2,8 @@ package net.knarcraft.minecraftserverlauncher; import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Controller; -import net.knarcraft.minecraftserverlauncher.server.Server; import net.knarcraft.minecraftserverlauncher.userinterface.ServerConsoles; import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI; -import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.Updater; import java.awt.*; @@ -16,8 +14,6 @@ import java.net.URISyntaxException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; - -import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween; //Java 8 required. /** @@ -29,12 +25,12 @@ import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stri */ public class Main { - private static String applicationWorkDirectory; - private static boolean serversAreRunning = false; private static final String updateChannel = "beta"; private static final String updateURL = "https://api.knarcraft.net/minecraftserverlauncher"; - private static ServerLauncherGUI gui; private static final Controller controller = Controller.getInstance(); + private static String applicationWorkDirectory; + private static boolean serversAreRunning = false; + private static ServerLauncherGUI gui; public static void main(String[] args) throws IOException { Updater.checkForUpdate(updateURL, updateChannel); @@ -49,7 +45,7 @@ public class Main { controller.loadState(); gui = controller.getGUI(); ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); - exec.scheduleAtFixedRate(Main::updateConsoles, 10, 500, TimeUnit.MILLISECONDS); + exec.scheduleAtFixedRate(Main::updateServersRunningState, 10, 500, TimeUnit.MILLISECONDS); } catch (Exception e) { e.printStackTrace(); } @@ -83,98 +79,34 @@ public class Main { } /** - * Reads from server processes, and writes the output to consoles. + * Updates the software state if the servers' running state has changed */ - private static void updateConsoles() { - try { - for (Collection collection : controller.getCurrentProfile().getCollections()) { - Server server = collection.getServer(); - if (server.isEnabled() && server.getProcess() != null) { - try { - String readText = CommonFunctions.readBufferedReader(server.getReader()); - if (!readText.equals("")) { - collection.getServerConsole().output(readText); - updatePlayerList(readText, server); - } - } catch (IOException e) { - e.printStackTrace(); - } - if (!server.getProcess().isAlive()) { - server.stopped(); - } - } - } - boolean runningNew = serversRunning(); - if (!runningNew && serversAreRunning) { - gui.updateGUIElementsWhenServersStartOrStop(false); - gui.setStatus("Servers are stopped"); - } else if (runningNew && !serversAreRunning) { - gui.updateGUIElementsWhenServersStartOrStop(true); - } - serversAreRunning = runningNew; - } catch (Exception e) { - e.printStackTrace(); + private static void updateServersRunningState() { + boolean runningNew = serversRunning(); + if (serversAreRunning && !runningNew) { + //Servers stopped running + gui.updateGUIElementsWhenServersStartOrStop(false); + gui.setStatus("Servers are stopped"); + } else if (!serversAreRunning && runningNew) { + //Servers started running + gui.updateGUIElementsWhenServersStartOrStop(true); } + serversAreRunning = runningNew; } /** - * Goes through all servers and looks for any running servers. + * Goes through all servers and looks for any running servers * - * @return Is at least one server running? + * @return

Whether at least one server is running

*/ private static boolean serversRunning() { - int num = 0; + int serversRunning = 0; for (Collection collection : controller.getCurrentProfile().getCollections()) { if (collection.getServer().isStarted() || (collection.getServer().getProcess() != null && collection.getServer().getProcess().isAlive())) { - num++; + serversRunning++; } } - return num > 0; - } - - /** - * Looks for strings implying a player has joined or left, and updates the appropriate lists - * - * @param text

The text to search

- * @param server

The server which sent the text

- */ - private static void updatePlayerList(String text, Server server) { - if (!server.getType().isProxy()) { - String joinedPlayer = getPlayer(text, true); - String leftPlayer = getPlayer(text, false); - if (!joinedPlayer.equals("")) { - if (!server.hasPlayer(joinedPlayer)) { - server.addPlayer(joinedPlayer); - } - } else if (!leftPlayer.equals("")) { - if (server.hasPlayer(leftPlayer)) { - server.removePlayer(leftPlayer); - } - } - } - } - - /** - * Searches a string for players joining or leaving - * - * @param text

The text string to search through

- * @param joined

Whether to search for a joining player

- * @return

The name of a player, or an empty string

- */ - private static String getPlayer(String text, boolean joined) { - String playerName; - if (joined) { - playerName = stringBetween(text, "[Server thread/INFO]: ", " joined the game"); - if (playerName.equals("")) { - playerName = stringBetween(text, "UUID of player ", " is "); - } - } else { - playerName = stringBetween(text, "INFO]: ", " lost connection"); - if (playerName.equals("")) { - playerName = stringBetween(text, "[Server thread/INFO]: ", " left the game"); - } - } - return playerName; + return serversRunning > 0; } } \ No newline at end of file diff --git a/src/main/java/net/knarcraft/minecraftserverlauncher/server/Server.java b/src/main/java/net/knarcraft/minecraftserverlauncher/server/Server.java index dbc77d5..31c014c 100644 --- a/src/main/java/net/knarcraft/minecraftserverlauncher/server/Server.java +++ b/src/main/java/net/knarcraft/minecraftserverlauncher/server/Server.java @@ -4,6 +4,7 @@ import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Controller; import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType; +import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import javax.naming.ConfigurationException; import java.io.BufferedReader; @@ -15,8 +16,12 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween; + /** * Contains all necessary information to create, runServer and manage a Minecraft server. @@ -45,6 +50,7 @@ public class Server { private BufferedWriter writer; private BufferedReader reader; private boolean started; + private ScheduledExecutorService consoleOutputExecutor; /** * Initializes a new server with default values @@ -98,7 +104,7 @@ public class Server { * * @return

The buffered reader used to read from this server

*/ - public BufferedReader getReader() { + private BufferedReader getReader() { return this.reader; } @@ -276,7 +282,8 @@ public class Server { /** * Checks whether this server is fully stopped */ - public void stopped() { + private void stopped() { + consoleOutputExecutor.shutdown(); process = null; writer = null; reader = null; @@ -307,7 +314,7 @@ public class Server { * @param name

The name of the player to check

* @return

True if the player is connected

*/ - public boolean hasPlayer(String name) { + private boolean hasPlayer(String name) { for (String player : this.playerList) { if (player.equals(name)) { return true; @@ -321,7 +328,7 @@ public class Server { * * @param name

The name of the player to add

*/ - public void addPlayer(String name) { + private void addPlayer(String name) { this.playerList.add(name); Main.getController().getGUI().getServerControlTab().addPlayer(name); } @@ -331,7 +338,7 @@ public class Server { * * @param name

The name of the player to remove

*/ - public void removePlayer(String name) { + private void removePlayer(String name) { playerList.removeIf(player -> player.equals(name)); Main.getController().getGUI().getServerControlTab().removePlayer(name); } @@ -396,6 +403,79 @@ public class Server { this.process = builder.start(); this.writer = new BufferedWriter(new OutputStreamWriter(this.process.getOutputStream())); this.reader = new BufferedReader(new InputStreamReader(this.process.getInputStream())); + + //Start the process for reading from the server's console + consoleOutputExecutor = Executors.newSingleThreadScheduledExecutor(); + consoleOutputExecutor.scheduleAtFixedRate(() -> { + try { + updateConsole(); + } catch (IOException e) { + e.printStackTrace(); + } + }, 10, 500, TimeUnit.MILLISECONDS); + } + + /** + * Updates a single console with process output + * + * @throws IOException

If unable to read from the server's buffered reader

+ */ + private void updateConsole() throws IOException { + String readText = CommonFunctions.readBufferedReader(getReader()); + if (!readText.equals("")) { + Main.getController().getCurrentProfile().getCollection(getName()).getServerConsole().output(readText); + updatePlayerList(readText); + } + if (!getProcess().isAlive()) { + stopped(); + } + } + + /** + * Looks for strings implying a player has joined or left, and updates the appropriate lists + * + * @param text

The text to search

+ */ + private void updatePlayerList(String text) { + if (!getType().isProxy()) { + String joinedPlayer = getPlayer(text, true); + String leftPlayer = getPlayer(text, false); + if (!joinedPlayer.equals("")) { + if (!hasPlayer(joinedPlayer)) { + addPlayer(joinedPlayer); + } + } else if (!leftPlayer.equals("")) { + if (hasPlayer(leftPlayer)) { + removePlayer(leftPlayer); + } + } + } + } + + /** + * Searches a string for players joining or leaving + * + * @param text

The text string to search through

+ * @param joined

Whether to search for a joining player

+ * @return

The name of a player, or an empty string

+ */ + private String getPlayer(String text, boolean joined) { + String playerName; + if (joined) { + playerName = stringBetween(text, "[Server thread/INFO]: ", " joined the game"); + if (playerName.equals("")) { + playerName = stringBetween(text, "UUID of player ", " is "); + } + } else { + playerName = stringBetween(text, "INFO]: ", " lost connection"); + if (playerName.equals("")) { + playerName = stringBetween(text, "[Server thread/INFO]: ", " left the game"); + } + if (playerName.equals("")) { + playerName = stringBetween(text, "INFO]: ", " left the game"); + } + } + return playerName; } /** diff --git a/src/main/java/net/knarcraft/minecraftserverlauncher/utility/CommonFunctions.java b/src/main/java/net/knarcraft/minecraftserverlauncher/utility/CommonFunctions.java index 801d18c..2df3127 100644 --- a/src/main/java/net/knarcraft/minecraftserverlauncher/utility/CommonFunctions.java +++ b/src/main/java/net/knarcraft/minecraftserverlauncher/utility/CommonFunctions.java @@ -195,10 +195,13 @@ public final class CommonFunctions { * @throws IOException

If unable to read from the buffered reader

*/ public static String readBufferedReader(BufferedReader reader) throws IOException { - String line; + //String line; StringBuilder text = new StringBuilder(); - while (reader.ready() && (line = reader.readLine()) != null) { - text.append(line).append("\n"); + char[] readCharacters = new char[1000]; + while (reader.ready()) { + reader.read(readCharacters); + text.append(readCharacters); + readCharacters = new char[1000]; } return text.toString().trim(); }