package net.knarcraft.minecraftserverlauncher.server; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import net.knarcraft.minecraftserverlauncher.Shared; import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Profile; import net.knarcraft.minecraftserverlauncher.Main; import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.concurrent.TimeUnit; /** * Contains all necessary information to create, run and manage a Minecraft server. * * @author Kristian Knarvik * @version 1.0.0 * @since 1.0.0 */ 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 static final String jarDir = Main.getAppDir() + File.separator + "files" + File.separator + "Jars" + File.separator; 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; private String vanillaVersion; private String snapshotVersion; private String spongeVanillaVersion; private String bungeeVersion; private boolean started; public Server(String name) { this.name = name; this.path = ""; this.enabled = false; this.playerList = new ArrayList<>(); this.type = null; this.serverVersion = ""; this.maxRam = ramList[0]; this.process = null; this.writer = null; this.reader = null; this.vanillaVersion = ""; this.snapshotVersion = ""; this.spongeVanillaVersion = ""; this.bungeeVersion = ""; } public Server( String name, String path, boolean enabled, String typeName, String serverVersion, String maxRam, String vanillaVersion, String snapshotVersion, String spongeVanillaVersion, String bungeeVersion ) { this.name = name; this.path = path; this.enabled = enabled; this.type = ServerType.getByName(typeName); this.serverVersion = serverVersion; this.maxRam = maxRam; this.vanillaVersion = vanillaVersion; this.snapshotVersion = snapshotVersion; this.spongeVanillaVersion = spongeVanillaVersion; this.bungeeVersion = bungeeVersion; this.playerList = new ArrayList<>(); } public String getName() { return this.name; } public boolean isStarted() { return started; } public String getTypeName() { return this.type.getName(); } public String getServerVersion() { return this.serverVersion; } public String getPath() { return this.path; } public Process getProcess() { return this.process; } public String getMaxRam() { return this.maxRam; } public static String[] getRamList() { return ramList; } public String getVanillaVersion() { return this.vanillaVersion; } public String getSnapshotVersion() { return this.snapshotVersion; } public String getSpongeVanillaVersion() { return this.spongeVanillaVersion; } public String getBungeeVersion() { return this.bungeeVersion; } public ArrayList getPlayers() { return this.playerList; } public void stopped() { process = null; writer = null; reader = null; started = 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 boolean isEnabled() { return this.enabled; } public void toggle(boolean value) { this.enabled = value; } public boolean hasPlayer(String name) { for (String player : this.playerList) { if (player.equals(name)) { return true; } } return false; } public void addPlayer(String name) { this.playerList.add(name); Profile.getGUI().addPlayer(name); } /** * Removes a player with the selected name from the playerlist. * * @param name The name of the player to remove */ public void removePlayer(String name) { for (int i = 0; i < playerList.size(); i++) { if (name.equals(playerList.get(i))) { playerList.remove(i); } } Profile.getGUI().removePlayer(name); } public void setPath(String path) { this.path = path; } public void setType(ServerType type) { this.type = type; } public void setMaxRam(String ram) { this.maxRam = ram; } /** * 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 { if (this.type.getName().equals("Custom")) { this.serverVersion = serverVersion; } else { String[] versions = this.type.getVersions(); for (String version : versions) { if (version.equals(serverVersion)) { this.serverVersion = serverVersion; return; } } throw new IllegalArgumentException("Invalid server version."); } } /** * Tries to stop all enabled servers. * * @throws IOException If a writer's process is already closed but not null. */ public static void stop() throws IOException { for (Collection collection : Profile.getCurrent().getCollections()) { Server server = collection.getServer(); if (server.writer != null) { if (server.type.getName().equals("Bungee")) { server.writer.write("end\n"); } else { server.writer.write("stop\n"); } server.writer.flush(); server.writer = null; server.started = false; } } } /** * Runs all enabled servers with their settings. */ public static void startServers() { Profile.getGUI().setStatus("Starting servers"); for (Collection collection : Profile.getCurrent().getCollections()) { if (!collection.getServer().run()) { Profile.getGUI().setStatus("An error occurred. Start aborted"); try { Server.stop(); } catch (IOException e) { e.printStackTrace(); } Profile.getGUI().updateRunning(false); return; } } } /** * Runs the Minecraft server. */ private boolean run() { if (this.enabled) { this.started = true; if (!Profile.getCurrent().getDownloadJars()) { try { Profile.getGUI().setStatus("Downloading jar..."); this.downloadJar(); Profile.getGUI().setStatus("File downloaded"); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); Profile.getGUI().setStatus("Error: Jar file not found"); e.printStackTrace(); this.started = false; return false; } } try { Profile.getGUI().setStatus("Delaying startup"); TimeUnit.SECONDS.sleep(Profile.getCurrent().getDelayStartup()); } catch (InterruptedException e) { e.printStackTrace(); this.started = false; return false; } try { ProcessBuilder builder; String serverPath; if (Profile.getCurrent().getDownloadJars() && !type.getName().equals("Custom")) { serverPath = jarDir + this.getType(); } else { serverPath = this.path + File.separator + this.getType(); } builder = new ProcessBuilder( "java", "-Xmx" + this.maxRam, "-Xms512M", "-Djline.terminal=jline.UnsupportedTerminal", "-Dcom.mojang.eula.agree=true", "-jar", serverPath, "nogui" ); builder.directory(new File(this.path)); builder.redirectErrorStream(true); this.process = builder.start(); this.writer = new BufferedWriter(new OutputStreamWriter(this.process.getOutputStream())); this.reader = new BufferedReader (new InputStreamReader(this.process.getInputStream())); Profile.getGUI().setStatus("Servers are running"); this.started = true; return true; } catch (IOException e) { Profile.getGUI().setStatus("Could not start server"); this.started = false; return false; } } else { this.started = false; return true; } } /** * Reads all available output from the server process. * * @return The server output * @throws IOException If reading from the reader fails */ public String read() throws IOException { String line; StringBuilder text = new StringBuilder(); while (reader.ready() && (line = reader.readLine()) != null) { text.append(line).append("\n"); } return text.toString().trim(); } /** * 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 + File.separator + this.getType()); Path filePath = Paths.get(this.path + File.separator + this.getType()); String versionText, newestVersion, url = this.type.getDownloadURL(), name = this.type.getName(), ver = this.serverVersion; 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() || Shared.downloadFile(url + name + ver + ".jar", filePath))) { 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 = Shared.readFile(type.getVersionURL()); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); String latest = jsonObject.getAsJsonObject("latest").get("release").getAsString(); JsonElement verElem = jsonObject.getAsJsonArray("versions").get(0); String versionFile = verElem.getAsJsonObject().get("url").getAsString(); try { versionText = Shared.readFile(versionFile); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } if (!file.isFile() || !latest.equals(this.getVersion(name))) { this.setVersion(name, latest); jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); String jarFile = jsonObject.getAsJsonObject("downloads").getAsJsonObject("server").get("url").getAsString(); if (!Shared.downloadFile(jarFile, filePath)) { throw new FileNotFoundException("Jar file could not be downloaded."); } } } else { if (!(file.isFile() || Shared.downloadFile(url + ver + type.getDownloadURLPart() + ver + ".jar", filePath))) { throw new FileNotFoundException("Jar file could not be downloaded."); } } break; case "SpongeVanilla": type = (AdvancedServerType) this.type; try { versionText = Shared.readFile(type.getVersionURL() + this.serverVersion); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } newestVersion = Shared.stringBetween(versionText, type.getSrcStart(), type.getSrcEnd()); if (!file.isFile() || !newestVersion.equals(this.getVersion(name))) { this.setVersion(name, newestVersion); if (!Shared.downloadFile(url + newestVersion + type.getDownloadURLPart() + newestVersion + ".jar", filePath)) { throw new FileNotFoundException("Jar file could not be downloaded."); } } break; case "Bungee": type = (AdvancedServerType) this.type; try { versionText = Shared.readFile(type.getVersionURL()); } catch (IOException e) { throw new FileNotFoundException("Version file could not be downloaded."); } newestVersion = Shared.stringBetween(versionText, type.getSrcStart(), type.getSrcEnd()); if (!file.isFile() || !newestVersion.equals(this.getVersion(name))) { this.setVersion(name, newestVersion); if (!Shared.downloadFile(url, filePath)) { throw new FileNotFoundException("Jar file could not be downloaded."); } } } } /** * Returns the current version of a type * * @param type The version type * @return The version string */ private String getVersion(String type) { switch (type) { case "Vanilla": return this.vanillaVersion; case "Snapshot": return this.snapshotVersion; case "SpongeVanilla": return this.spongeVanillaVersion; case "Bungee": return this.bungeeVersion; default: return ""; } } /** * Sets a server type's last downloaded version. * * @param type The version type * @param version The version string */ private void setVersion(String type, String version) { if (!type.equals("")) { switch (type) { case "Vanilla": this.vanillaVersion = version; break; case "Snapshot": this.snapshotVersion = version; break; case "SpongeVanilla": this.spongeVanillaVersion = version; break; case "Bungee": this.bungeeVersion = version; } } } /** * 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(); } } }