package net.knarcraft.serverlauncher.server; import net.knarcraft.serverlauncher.profile.Collection; import net.knarcraft.serverlauncher.profile.Profile; import net.knarcraft.serverlauncher.userinterface.GUI; import net.knarcraft.serverlauncher.userinterface.ServerTab; import java.io.*; import java.net.URL; import java.util.Scanner; import java.nio.file.*; import java.util.ArrayList; /** * Contains all necessary information to create, run and manage a Minecraft server. * * @author Kristian Knarvik * @version 0.0.0.1 * @since 0.0.0.1 */ public class Server { /** Available ram sizes. For GUI dropdown */ private static final String[] ramList = { "512M", "1G", "2G", "3G", "4G", "5G", "6G", "7G", "8G", "9G", "10G","11G", "12G", "13G", "14G", "15G", "16G" }; private final String name; private String path; private boolean enabled; private final ArrayList playerList; private ServerType type; private String serverVersion; private String maxRam; private Process process; private BufferedWriter writer; private BufferedReader reader; public Server(String name, GUI window, ServerTab serverTab) { this.name = name; this.path = ""; this.enabled = false; this.playerList = new ArrayList<>(); this.type = null; this.serverVersion = null; this.maxRam = ramList[0]; this.process = null; window.addServer(serverTab); } public void addPlayer(String name) { this.playerList.add(name); GUI.getGUI().addPlayer(name); } public void removePlayer(String name) { for (int i = 0; i < playerList.size(); i++) { if (name.equals(playerList.get(i))) { playerList.remove(i); } } GUI.getGUI().removePlayer(name); } public ArrayList getPlayers() { return this.playerList; } public boolean hasPlayer(String name) { for (String player : this.playerList) { if (player.equals(name)) { return true; } } return false; } /** * @return A representation of the name of a jarfile. */ private String getType() { if (this.type.getName().equals("Custom")) { return this.serverVersion; } else { return this.type.getName() + this.serverVersion + ".jar"; } } public String getName() { return this.name; } public String getPath() { return this.path; } public Process getProcess() { return this.process; } public void toggle(boolean value) { this.enabled = value; } public void setPath(String path) { this.path = path; } public void setType(ServerType type) { this.type = type; } public static String[] getRamList() { return ramList; } public boolean isEnabled() { return this.enabled; } public void setMaxRam(String ram) { this.maxRam = ram; } public static void stop() throws IOException { for (Collection collection : GUI.getGUI().currentProfile().getCollections()) { if (collection.getServer().writer != null) { if (collection.getServer().type.getName().equals("Bungee")) { collection.getServer().writer.write("end\n"); } else { collection.getServer().writer.write("stop\n"); } collection.getServer().writer.flush(); collection.getServer().writer = null; } } } public String typeName() { return this.type.getName(); } /** * Sets the server's server version to a valid version, or ignores the request. * * @param serverVersion Version number. */ public void setServerVersion(String serverVersion) throws IllegalArgumentException { String[] versions = this.type.getVersions(); for (String version : versions) { if (version.equals(serverVersion)) { this.serverVersion = serverVersion; return; } } throw new IllegalArgumentException("Invalid server version."); } /** * Runs all enabled servers with their settings. */ public static void startServers() { GUI.getGUI().setStatus("Starting servers"); for (Collection collection : GUI.getGUI().currentProfile().getCollections()) { if (!collection.getServer().run()) { GUI.getGUI().setStatus("An error occurred. Start aborted"); } } } /** * Runs the Minecraft server. */ private boolean run() { if (this.enabled) { try { GUI.getGUI().setStatus("Downloading jar..."); this.downloadJar(); GUI.getGUI().setStatus("File downloaded"); } catch (FileNotFoundException e) { GUI.getGUI().setStatus("Error: Jar file not found"); return false; } try { ProcessBuilder builder = new ProcessBuilder( "java", "-Xmx" + this.maxRam, "-Xms512M", "-Djline.terminal=jline.UnsupportedTerminal", "-Dcom.mojang.eula.agree=true", "-jar", "\"" + this.path + "\\" + this.getType() + "\"", "nogui" ); builder.directory(new File(this.path)); builder.redirectErrorStream(true); this.process = builder.start(); OutputStream stdin = this.process.getOutputStream(); this.writer = new BufferedWriter(new OutputStreamWriter(stdin)); InputStream stdout = this.process.getInputStream(); this.reader = new BufferedReader (new InputStreamReader(stdout)); GUI.getGUI().setStatus("Servers are running"); return true; } catch (IOException e) { GUI.getGUI().setStatus("Could not start server"); return false; } } else { return true; } } public String read() throws IOException { String line; if ((line = reader.readLine()) != null) { return line; } else {return "";} } /** * Downloads necessary .jar file for the server. * This is unfortunately hardcoded since there is no golden standard, and we only host some jars ourselves. * * @throws FileNotFoundException if the file was not found and could not be acquired. */ private void downloadJar() throws FileNotFoundException { AdvancedServerType type; File file = new File(this.path + "\\" + this.getType()); Path filePath = Paths.get(this.path + "\\" + this.getType()); Profile currentProfile = GUI.getGUI().currentProfile(); String versionText; String newestVersion; String url = this.type.getDownloadURL(); String name = this.type.getName(); String ver = this.serverVersion; boolean success; switch (this.type.getName()) { case "Custom": if (!file.isFile()) { throw new FileNotFoundException("Specified custom jar was not found."); } break; case "Spigot": case "Craftbukkit": case "MCPCplus": if (!file.isFile()) { success = downloadFile(url + name + ver + ".jar", filePath); System.out.println(url + name + ver + ".jar"); if (!success) { throw new FileNotFoundException("Jar file could not be downloaded."); } } break; case "Vanilla": case "Snapshot": type = (AdvancedServerType) this.type; if (this.serverVersion.equals("Latest")) { try { versionText = readFile(type.getVersionURL()); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } newestVersion = stringBetween(versionText, type.getSrcStart(), type.getSrcEnd()); if (!file.isFile() || !newestVersion.equals(currentProfile.getVersion(name))) { success = downloadFile(url + newestVersion + type.getDownloadURLPart() + newestVersion + ".jar", filePath); GUI.getGUI().currentProfile().setVersion(this.type.getName(), newestVersion); if (!success) { throw new FileNotFoundException("Jar file could not be downloaded."); } } } else { if (!file.isFile()) { success = downloadFile(url + ver + type.getDownloadURLPart() + ver + ".jar", filePath); if (!success) { throw new FileNotFoundException("Jar file could not be downloaded."); } } } break; case "SpongeVanilla": type = (AdvancedServerType) this.type; try { versionText = readFile(type.getVersionURL() + this.serverVersion); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } newestVersion = stringBetween(versionText, type.getSrcStart(), type.getSrcEnd()); if (!file.isFile() || !newestVersion.equals(currentProfile.getVersion(name))) { success = downloadFile(url + newestVersion + type.getDownloadURLPart() + newestVersion + ".jar", filePath); currentProfile.setVersion(name, newestVersion); if (!success) { throw new FileNotFoundException("Jar file could not be downloaded."); } } break; case "Bungee": type = (AdvancedServerType) this.type; try { versionText = readFile(type.getVersionURL()); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } newestVersion = stringBetween(versionText, type.getSrcStart(), type.getSrcEnd()); if (!file.isFile() || !newestVersion.equals(currentProfile.getVersion(name))) { success = downloadFile(url, filePath); currentProfile.setVersion(name, newestVersion); if (!success) { throw new FileNotFoundException("Jar file could not be downloaded."); } } } } /** * Reads a file from a website. * This is used to find the newest version of the software. * * @param path The full url of the file to read * @return True if successful. False otherwise */ private static String readFile(String path) throws IOException { URL url = new URL(path); return new Scanner(url.openStream()).useDelimiter("\\Z").next(); } /** * Downloads a file from a website. * * @param path The full url of the file to download. * @param outfile The file to save to * @return True if successful. False otherwise */ private static boolean downloadFile(String path, Path outfile) { try { URL url = new URL(path); InputStream in = url.openStream(); Files.copy(in, outfile, StandardCopyOption.REPLACE_EXISTING); return true; } catch (IOException e) { return false; } } /** * Finds a substring between two substrings in a string. * * @param string The string containing the substrings * @param start The substring before the wanted substring * @param end The substring after the wanted substring * @return The wanted substring. */ private String stringBetween(String string, String start, String end) { int startPos = string.indexOf(start) + start.length(); return string.substring(startPos, string.indexOf(end, startPos)); } /** * Sends a command to this server through its writer. * * @param command Command to send to the server * @throws IOException If write fails */ public void sendCommand(String command) throws IOException { if (this.process != null && this.writer != null) { this.writer.write(command + "\n"); this.writer.flush(); } } }