package net.knarcraft.serverlauncher.profile; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import net.knarcraft.serverlauncher.server.AdvancedServerType; import net.knarcraft.serverlauncher.server.Server; import net.knarcraft.serverlauncher.server.ServerType; import net.knarcraft.serverlauncher.userinterface.GUI; import net.knarcraft.serverlauncher.userinterface.ServerConsoles; import net.knarcraft.serverlauncher.userinterface.ServerTab; import net.knarcraft.serverlauncher.Main; import javax.swing.*; import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; import java.util.concurrent.Executors; import static net.knarcraft.serverlauncher.Shared.downloadFile; import static net.knarcraft.serverlauncher.Shared.readFile; import static net.knarcraft.serverlauncher.Shared.stringBetween; /** * Contains all user settings, and a list of servers. * * @author Kristian Knarvik <kristian.knarvik@knett.no> * @version 1.0.0 * @since 1.0.0 */ public class Profile { private static final ArrayList<Profile> profiles = new ArrayList<>(); private static Profile current; private static GUI gui; private static final String profilesDir = Main.getAppDir() + File.separator + "files"; private static final String profilesFile = Main.getAppDir() + File.separator + "files" + File.separator + "Profiles.txt"; private static final String jarDir = Main.getAppDir() + File.separator + "files" + File.separator + "Jars" + File.separator; private final ArrayList<Collection> collections; private final String name; private boolean runInBackground; private int delayStartup; private boolean downloadJars; private static String vanillaVersion; private static String snapshotVersion; private static String bungeeVersion; private Profile(String name) { this.collections = new ArrayList<>(); this.name = name; this.runInBackground = false; this.delayStartup = 0; this.downloadJars = false; profiles.add(this); if (current == null) { current = this; } } private Profile(String name, boolean runInBackground, int delayStartup, boolean downloadJars) { this.collections = new ArrayList<>(); this.name = name; this.runInBackground = runInBackground; this.delayStartup = delayStartup; this.downloadJars = downloadJars; profiles.add(this); if (current == null) { current = this; } } public static GUI getGUI() { return gui; } public boolean getRunInBackground() { return this.runInBackground; } public int getDelayStartup() { return this.delayStartup; } public boolean getDownloadJars() { return this.downloadJars; } public static Profile getCurrent() { return current; } public ArrayList<Collection> getCollections() { return this.collections; } public String getName() { return this.name; } /** * Gets a Collection object by name. * * @param name The name of the collection. * @return A collection object. */ public Collection getCollection(String name) { for (Collection collection : this.collections) { if (collection.getName().equals(name)) { return collection; } } return null; } public static Profile getProfile(String name) { for (Profile profile : profiles) { if (profile.name.equals(name)) { return profile; } } return null; } public static ArrayList<Profile> getProfiles() { return profiles; } public void setRunInBackground(boolean value) { this.runInBackground = value; } public void setDelayStartup(int value) { if (value >= 0) { this.delayStartup = value; } } public void setDownloadJars(boolean value) { this.downloadJars = value; } /** * Set the current profile to the profile with a certain name. * * @param name The name of the profile */ public static void setCurrent(String name) { for (Profile profile : profiles) { if (profile.name.equals(name)) { current = profile; break; } } } /** * Adds a collection to the profile if the name is valid. * * @param name The name of the collection and its elements. */ public void addCollection(String name) { if (name == null) { //If a user cancels or crosses out window return; } if (getCollection(name) == null && !name.equals("") && !name.equals("All") && !name.contains("!") && !name.contains("?") && !name.contains(";") ) { collections.add(new Collection(name)); } else { JOptionPane.showMessageDialog( null, "A server name must my unique and not empty or \"All\"." + "It can't contain any of the characters \"!\", \"?\" or \";\".", "Error", JOptionPane.ERROR_MESSAGE ); } } /** * Adds a profile if the name is valid and unique. * * @param name The name of the new profile. */ public static void addProfile(String name) { if (name == null) { //If a user cancels or crosses out window return; } if (name.equals("") && !name.contains("!") && !name.contains("?") && !name.contains(";")) { JOptionPane.showMessageDialog( null, "Profile name can't be blank.", "Error", JOptionPane.ERROR_MESSAGE ); return; } for (Profile profile : profiles) { if (profile.name.equals(name)) { JOptionPane.showMessageDialog( null, "There is already a profile with this name.", "Error", JOptionPane.ERROR_MESSAGE ); return; } } new Profile(name); } public void removeCollection(String name) { for (int i = 0; i < collections.size(); i++) { if (collections.get(i).getName().equals(name)) { this.collections.remove(i); gui.update(); break; } } } public static void removeProfile(String name) { if (profiles.size() > 1) { for (int i = 0; i < profiles.size(); i++) { if (profiles.get(i).name.equals((name))) { profiles.remove(i); } } } } public void updateConsoles() { JTabbedPane consolesTab = ServerConsoles.getTab(); consolesTab.removeAll(); for (Collection collection : collections) { consolesTab.add(collection.getName(), collection.getServerConsole().getPanel()); } } /** * Sends a command to a server, or all servers * * @param serverName The target server * @param command The command to send. */ public void sendCommand(String serverName, String command) { if (serverName.equals("All")) { for (Collection collection : this.collections) { try { collection.getServer().sendCommand(command); } catch (IOException e) { JOptionPane.showMessageDialog( null, "Server " + collection.getName() + " caused an exception.", "Error", JOptionPane.ERROR_MESSAGE ); } } } else { Collection collection = getCollection(serverName); if (collection != null) { Server target = collection.getServer(); try { target.sendCommand(command); } catch (IOException e) { JOptionPane.showMessageDialog( null, "Server " + target.getName() + " caused an exception.", "Error", JOptionPane.ERROR_MESSAGE ); } } else { JOptionPane.showMessageDialog( null, "Server " + serverName + " is invalid.", "Error", JOptionPane.ERROR_MESSAGE ); } } } /** * Reads all server tabs, and saves it to the variables of the corresponding servers. * Saves all profiles and servers to a text file. */ public void save() throws FileNotFoundException { for (Collection collection : this.collections) { Server server = collection.getServer(); ServerTab serverTab = collection.getServerTab(); server.setPath(serverTab.getPath()); server.setMaxRam(serverTab.getMaxRam()); server.setType(ServerType.getByName(serverTab.getType())); try { server.setServerVersion(serverTab.getVersion()); } catch (IllegalArgumentException e) { JOptionPane.showMessageDialog( null, "Invalid server version for " + server.getName(), "Error", JOptionPane.ERROR_MESSAGE ); } server.toggle(serverTab.enabled()); } if (!new File(profilesDir).exists() && !new File(profilesDir).mkdirs()) { JOptionPane.showMessageDialog( null, "Unable to create the folder " + profilesDir, "Error", JOptionPane.ERROR_MESSAGE ); throw new FileNotFoundException("Unable to create the profiles folder: " + profilesDir); } try (PrintWriter file = new PrintWriter(profilesFile)) { file.println(String.format( "%s;%s;%s;%s;%d;%d", current.name, vanillaVersion, snapshotVersion, bungeeVersion, gui.getSize().width, gui.getSize().height )); file.close(); for (Profile profile : profiles) { StringBuilder saveString = new StringBuilder(String.format( "%s;%b;%d;%b?", profile.name, profile.runInBackground, profile.delayStartup, profile.downloadJars) ); for (Collection collection : profile.collections) { Server server = collection.getServer(); saveString.append(String.format( "%s;%s;%b;%s;%s;%s;%s;%s;%s;%s!", server.getName(), server.getPath(), server.isEnabled(), server.getTypeName(), server.getServerVersion(), server.getMaxRam(), server.getVanillaVersion(), server.getSnapshotVersion(), server.getSpongeVanillaVersion(), server.getBungeeVersion() ) ); } saveString = new StringBuilder(saveString.substring(0, saveString.length() - 1)); try (PrintWriter fileAppend = new PrintWriter(new FileWriter( profilesFile, true ))) { fileAppend.println(saveString); } catch (IOException e) { if (gui != null) { JOptionPane.showMessageDialog( null, "Unable to save to file. Try running the software as an administrator.", "Error", JOptionPane.ERROR_MESSAGE ); } else { System.out.println("Unable to save to file. Try running the software as an administrator."); } throw new FileNotFoundException("Unable to save to the profiles file."); } } } catch (IOException e) { if (gui != null) { JOptionPane.showMessageDialog( null, "Unable to save to file. Try running the software as an administrator.", "Error", JOptionPane.ERROR_MESSAGE ); } throw new FileNotFoundException("Unable to create the profiles file"); } } /** * Reads profiles and servers from a text file. */ public static void load() { try (Scanner in = new Scanner(new File(profilesFile))) { try { String[] staticData = in.nextLine().split(";", -1); String profileName = staticData[0]; vanillaVersion = staticData[1]; snapshotVersion = staticData[2]; bungeeVersion = staticData[3]; int guiWidth = Integer.parseInt(staticData[4]); int guiHeight = Integer.parseInt(staticData[5]); gui = new GUI(guiWidth, guiHeight); while (in.hasNextLine()) { String line = in.nextLine(); if (line.contains("?")) { String[] data = line.split("\\?"); String[] profileData = data[0].split(";", -1); Profile profile = parseProfile(profileData); if (data[1].contains("!")) { String[] servers = data[1].split("!", -1); for (String server : servers) { String[] serverData = server.split(";", -1); parseServer(profile, serverData); } } else { String[] serverData = data[1].split(";", -1); parseServer(profile, serverData); } } else { String[] profileData = line.split(";", -1); parseProfile(profileData); } } current = getProfile(profileName); } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) { e.printStackTrace(); JOptionPane.showMessageDialog( null, "Invalid Profile.txt file. Profiles could not be loaded. If this error persists, please manually delete the file.", "Error", JOptionPane.ERROR_MESSAGE ); System.exit(1); } if (profiles.size() == 0) { addProfile("Default"); } } catch (FileNotFoundException e) { JOptionPane.showMessageDialog( null, "A profiles file was not found. Default profile was created.", "Info", JOptionPane.INFORMATION_MESSAGE ); gui = new GUI(); addProfile("Default"); } gui.update(); gui.updateProfiles(); current.updateConsoles(); if (current.downloadJars) { Executors.newSingleThreadExecutor().execute(() -> { try { downloadJars(); } catch (IOException e) { e.printStackTrace(); } }); } if (current.runInBackground) { Executors.newSingleThreadExecutor().execute(Server::startServers); gui.hide(); } } /** * Parses a profile, and creates a profile with the data. * * @param profileData The data of the new profile * @return The new profile */ private static Profile parseProfile(String[] profileData) { return new Profile( profileData[0], Boolean.parseBoolean(profileData[1]), Integer.parseInt(profileData[2]), Boolean.parseBoolean(profileData[3]) ); } /** * Parses a server, and creates a new collection. * * @param profile The profile which to add the collection * @param serverData The data to parse */ private static void parseServer(Profile profile, String[] serverData) { profile.collections.add(new Collection( serverData[0], serverData[1], Boolean.parseBoolean(serverData[2]), serverData[3], serverData[4], serverData[5], serverData[6], serverData[7], serverData[8], serverData[9]) ); } /** * Downloads all jars to the program directory. * * @throws IOException On version file failure or folder creation failure */ public static void downloadJars() throws IOException { if (!new File(jarDir).exists() && !new File(jarDir).mkdirs()) { JOptionPane.showMessageDialog( null, "Could not create the Jars folder. Please run the program with admin permissions, or move it to a writable directory.", "Error", JOptionPane.ERROR_MESSAGE ); throw new FileNotFoundException("Unable to create jars folder"); } try { downloadAll(); printToGui("Finished downloading jars"); } catch (FileNotFoundException e) { printToGui("One or more downloads failed: " + e.getMessage()); } } private static void printToGui(String str) { if (gui != null) { gui.setStatus(str); } else { System.out.println(str); } } /** * Downloads jar files for all possible server versions. * * @throws IOException If a jar fails to download. */ private static void downloadAll() throws IOException { for (ServerType type : ServerType.getServerTypes()) { String url = Objects.requireNonNull(type).getDownloadURL(), name = type.getName(), newestVersion; AdvancedServerType advType = type instanceof AdvancedServerType ? (AdvancedServerType) type : null; for (String version : type.getVersions()) { Boolean success; printToGui("Downloading: " + name + version + ".jar"); File file = new File(jarDir + type.getName() + version + ".jar"); Path filePath = Paths.get(jarDir + type.getName() + version + ".jar"); switch (type.getName()) { case "Vanilla": case "Snapshot": if (version.equals("Latest")) { String versionText = readFile(Objects.requireNonNull(advType).getVersionURL()); JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); String latest = jsonObject.getAsJsonObject("latest").get("release").getAsString(); JsonElement ver = jsonObject.getAsJsonArray("versions").get(0); String versionFile = ver.getAsJsonObject().get("url").getAsString(); versionText = readFile(versionFile); jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); String jarFile = jsonObject.getAsJsonObject("downloads").getAsJsonObject("server").get("url").getAsString(); setVersion(name, latest); success = (file.isFile() && latest.equals(getVersion(name))) || downloadFile(jarFile, filePath); } else { success = file.isFile() || downloadFile(url + version + Objects.requireNonNull(advType).getDownloadURLPart() + version + ".jar", filePath); } break; case "Spigot": case "Craftbukkit": case "MCPCplus": success = file.isFile() || downloadFile(url + name + version + ".jar", filePath); break; case "SpongeVanilla": newestVersion = stringBetween(readFile(Objects.requireNonNull(advType).getVersionURL() + version), advType.getSrcStart(), advType.getSrcEnd()); success = file.isFile() || downloadFile(url + newestVersion + advType.getDownloadURLPart() + newestVersion + ".jar", filePath); break; case "Bungee": newestVersion = stringBetween(readFile(Objects.requireNonNull(advType).getVersionURL()), advType.getSrcStart(), advType.getSrcEnd()); setVersion(name, newestVersion); success = (file.isFile() && newestVersion.equals(getVersion(name))) || downloadFile(url, filePath); break; default: success = true; } if (!success) { printToGui("Error downloading: " + name + version + ".jar"); throw new FileNotFoundException("Error downloading: " + name + version + ".jar"); } } } } /** * Returns the current version of a type * * @param type The version type * @return The version string */ private static String getVersion(String type) { switch (type) { case "Vanilla": return vanillaVersion; case "Snapshot": return snapshotVersion; case "Bungee": return bungeeVersion; default: return ""; } } /** * Sets a server type's last downloaded version. * * @param type The version type * @param version The version string */ private static void setVersion(String type, String version) { if (!type.equals("")) { switch (type) { case "Vanilla": vanillaVersion = version; break; case "Snapshot": snapshotVersion = version; break; case "Bungee": bungeeVersion = version; } } } }