30 Commits

Author SHA1 Message Date
545e7bacc6 Updates Paper, Vanilla and Waterfall verisions to 1.18
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-11-30 23:28:46 +01:00
b142b6d05e Small fix to use Paper/Travertine/Waterfall's updated API for downloading .jar files
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Additionally fixes some formatting mistakes
Updates version to 1.4.1
2021-11-24 19:54:59 +01:00
166d63f1b2 Moves the show consoles button to the main control panel
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Also makes the control servers tab hidden if the servers are not running
2021-10-01 13:56:16 +02:00
8f25fa2796 Fixes the font of the status label
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-10-01 01:26:28 +02:00
3467c900f0 Changes some variable names 2021-10-01 01:09:34 +02:00
9f092a73fe Extracts the control panel tab to its own class, and makes the add server button into a tab button 2021-10-01 00:58:00 +02:00
50b9188229 Makes sure all GUI windows have the same icon
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-09-30 17:47:12 +02:00
b021de5594 Replaces the "Remove server" button with an (X) on the tab itself
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-09-29 13:16:57 +02:00
a1ae162b07 Updates software version to 1.4.0
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-09-28 02:32:00 +02:00
b2ee22eb7b Disable synchronous behavior to prevent odd behavior when the backup button is spam clicked
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-09-27 23:23:06 +02:00
65ede11ab5 Improves behavior when an error occurs during backup 2021-09-27 23:19:52 +02:00
c26a3bc3b5 Adds some backup fixes
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Fixes a potential NullPointerException
Adds a check to make sure the backup location has enough available space
Adds a message when calculating backup size to make it clear that the software is not frozen
2021-09-27 22:24:06 +02:00
71e47acbb0 Improves the readability of the backup code a bit
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-09-27 22:06:56 +02:00
60fdcf5ddc Adds a proper GUI to display backup progress
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Extracts backup code to its own class
Adds a new GUI to display progress and the file copied
2021-09-27 21:39:15 +02:00
bf77c13072 Fixes some behavior regarding backups
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Makes sure to only proceed with backups if a valid path is given
Makes the backup run in its own thread to prevent the software from locking up
Makes sure only one backup can be running at once
2021-09-27 18:06:33 +02:00
849655bfc6 Fixes a bug causing newlines to disappear when a console is truncated
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-26 14:19:40 +02:00
e8ecee1cd0 Updates the README as it was starting to get quite outdated 2021-08-25 23:32:13 +02:00
5e24d5daa8 Improves checking for whether a server should be delayed before starting
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-24 16:08:02 +02:00
9532683301 Fixes a NumberFormatException when running custom versions without a version number
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-23 16:43:35 +02:00
365d08f2e2 Changes download URLs of Paper to papermc.io
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Updates software version
Stores version of downloaded Paper .jar files
Completes #8
2021-08-23 14:53:39 +02:00
9d8531535d Updates software version to 1.3.4
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-21 15:28:28 +02:00
30de4b2139 Clarifies an error message 2021-08-21 14:01:22 +02:00
133e335905 Fixes a bug causing the software to be stuck in the Stopping Servers state
Fixes the case where the software tries to stop servers while no servers are actually running.
This also fixes #10
2021-08-21 13:53:41 +02:00
44d8d6ee6b Extracts a ServerHandler from the Server class to reduce complexity 2021-08-21 13:43:27 +02:00
cd396eee6b Fixes a NullPointerException caused by cancelling an input prompt 2021-08-21 13:09:18 +02:00
c67f3bdd2c Closes issue #9
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Adds a catch to prevent the software from crashing when checking for an update fails
2021-08-17 19:42:18 +02:00
8476c78589 Makes sure never to add a newline when writing an empty string to a file 2021-08-17 19:32:47 +02:00
a79300732d Splits the code for reading the software version into its own function 2021-08-17 19:32:11 +02:00
b6baa9ad6f Updates software version to 1.3.3
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-17 01:08:14 +02:00
c2f12a1a49 Adds the underscore character to regexes for player detection
This fixes a bug where players with underscores in their names won't be added to the player list
2021-08-17 01:07:20 +02:00
32 changed files with 1138 additions and 510 deletions

View File

@ -1,6 +1,14 @@
# Minecraft-Server-Launcher
I originally created this software in 2013 using AutoIt. Since I am now learning Java, I have recreated it in Java.
The original version can be found at: https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncher/
Its goal is to do everything the original does, just better. The original is 1595 lines of code repetition and dirty code. I had no prior programming experience when I first started working on the original, which is why it became such a mess.
The code is now in a fully working state. It is still missing an auto update feature, but it can be used, and is fully functional.
I originally created this software in 2013 using AutoIt. After learning Java, I recreated it in Java. The original
version can be found at: https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncher/
This version's goal is to do everything the original does, just better. The original is 1595 lines of code repetition
and dirty code. I had no prior programming experience when I first started working on the original, which is why it
became such a mess.
The software is in a fully working stage with a functional updater. The software is still in beta because there are
still a lot of things I want to change and small annoyances. The software is fully portable. Make sure to put it in its
own folder as it will create lots of files.
The updater will move the new version to minecraft-server-launcher.jar, so you should rename the downloaded file to this
if it's named something else to avoid confusion.

View File

@ -1,3 +1,3 @@
As this is a personal one-man project, I prefer to stay in charge of the code.
I appreciate code fixing mistakes, or code improvements, but the software's functionality or purpose, should not be changed.
I might change anything at any time, so you might want to keep that in mind before doing big changes yourself.
As this is a personal one-man project, I prefer to stay in charge of the code. I appreciate code fixing mistakes, or
code improvements, but the software's functionality or purpose, should not be changed. I might change anything at any
time, so you might want to keep that in mind before doing big changes yourself.

View File

@ -1,6 +1,7 @@
Fixes # .
Proposed changes:
-
-
-

View File

@ -2,7 +2,7 @@ package net.knarcraft.minecraftserverlauncher;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerConsoles;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit;
* A software for managing Minecraft servers.
*
* @author Kristian Knarvik <kristian.knarvik@knett.no>
* @version 1.0.0
* @version b1.3.4
* @since 1.0.0
*/
@ -34,8 +34,14 @@ public class Main {
private static ServerLauncherGUI gui;
public static void main(String[] args) throws IOException {
String logFile = Main.getApplicationWorkDirectory() + File.separator + "latestrun.log";
CommonFunctions.writeFile(logFile, "[Info]: Starting Minecraft Server Launcher v." +
Updater.getCurrentVersion()[1]);
try {
Updater.checkForUpdate(updateURL, updateChannel);
CommonFunctions.writeFile(Main.getApplicationWorkDirectory() + File.separator + "latestrun.log", "");
} catch (IOException e) {
CommonFunctions.appendFile(logFile, "[Warning]: Unable to complete update procedure: " + e.getMessage());
}
EventQueue.invokeLater(() -> {
try {
ServerConsoles.instantiate();
@ -82,7 +88,7 @@ public class Main {
boolean runningNew = serversRunning();
if (serversAreRunning && !runningNew) {
//Servers stopped running
Server.serversStopped();
ServerHandler.serversStopped();
gui.updateGUIElementsWhenServersStartOrStop(false);
gui.setStatus("Servers are stopped");
} else if (!serversAreRunning && runningNew) {

View File

@ -2,6 +2,7 @@ package net.knarcraft.minecraftserverlauncher.profile;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerConsoles;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
@ -10,6 +11,7 @@ import javax.naming.ConfigurationException;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Keeps track of a set of servers and some user settings
@ -21,7 +23,7 @@ import java.util.ArrayList;
public class Profile {
private static final ServerLauncherGUI serverLauncherGui = Main.getController().getGUI();
private final ArrayList<Collection> collections;
private final List<Collection> collections;
private final String name;
private boolean runInBackground;
private int delayStartup;
@ -105,7 +107,7 @@ public class Profile {
*
* @return <p>All collections stored by this profile</p>
*/
public ArrayList<Collection> getCollections() {
public List<Collection> getCollections() {
return this.collections;
}
@ -139,14 +141,16 @@ public class Profile {
* @param name <p>The name of the collection and its elements</p>
*/
public void addCollection(String name) throws ConfigurationException {
if (name == null) { //If a user cancels or crosses out window
//Skip if no name was given
if (name == null) {
return;
}
if (getCollection(name) == null && !name.equals("All") && CommonFunctions.nameIsValid(name)) {
collections.add(new Collection(name));
} else {
serverLauncherGui.showError("A server name must my unique and not empty or \"All\"." +
"It can't contain any of the characters \"!\", \"?\" or \";\".");
serverLauncherGui.showError("A server name must be unique and not empty. " +
"In addition, a server cannot be named: \"All\". " +
"It cannot contain any of the characters \"!\", \"?\" or \";\".");
}
}
@ -239,10 +243,10 @@ public class Profile {
if (data[1].contains("!")) {
String[] servers = data[1].split("!", -1);
for (String server : servers) {
profile.collections.add(new Collection(Server.fromString(server)));
profile.collections.add(new Collection(ServerHandler.fromString(server)));
}
} else {
profile.collections.add(new Collection(Server.fromString(data[1])));
profile.collections.add(new Collection(ServerHandler.fromString(data[1])));
}
} else {
profile = parseProfile(profileString.split(";", -1));

View File

@ -2,6 +2,7 @@ package net.knarcraft.minecraftserverlauncher.profile;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerTab;
@ -378,7 +379,7 @@ public class ServerLauncherController {
* Updates the GUI as necessary to display loaded data
*/
private void executeGUILoadingTasks() {
this.serverLauncherGUI.updateProfiles();
this.serverLauncherGUI.getControlPanelTab().updateProfiles();
this.serverLauncherGUI.updateWithSavedProfileData();
this.currentProfile.updateConsoles();
if (this.downloadAllJars) {
@ -392,7 +393,7 @@ public class ServerLauncherController {
});
}
if (this.currentProfile.getRunInBackground()) {
Executors.newSingleThreadExecutor().execute(Server::startServers);
Executors.newSingleThreadExecutor().execute(ServerHandler::startServers);
this.serverLauncherGUI.hideToTray();
}
}

View File

@ -1,10 +1,10 @@
package net.knarcraft.minecraftserverlauncher.utility;
package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.ServerVersionContainer;
import net.knarcraft.minecraftserverlauncher.userinterface.GUI;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import java.io.BufferedReader;
import java.io.BufferedWriter;

View File

@ -1,9 +1,9 @@
package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.naming.ConfigurationException;
@ -31,14 +31,9 @@ import java.util.regex.Pattern;
* @since 1.0.0
*/
public class Server {
/**
* Available ram sizes. For ServerLauncherGUI combo
*/
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 jarDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" +
File.separator + "Jars" + File.separator;
private final String name;
private final ArrayList<String> playerList;
private String path;
@ -51,7 +46,7 @@ public class Server {
private BufferedReader reader;
private boolean started;
private ScheduledExecutorService consoleOutputExecutor;
private static boolean stoppingServers = false;
private final ServerLauncherGUI gui = Main.getController().getGUI();
/**
* Initializes a new server with default values
@ -65,7 +60,7 @@ public class Server {
this.playerList = new ArrayList<>();
this.type = null;
this.serverVersion = "";
this.maxRam = ramList[0];
this.maxRam = ServerHandler.getRamList()[0];
this.process = null;
this.writer = null;
this.reader = null;
@ -91,15 +86,6 @@ public class Server {
this.playerList = new ArrayList<>();
}
/**
* Gets the list of available RAM choices allowed
*
* @return <p>All available RAM choices</p>
*/
public static String[] getRamList() {
return ramList;
}
/**
* Gets the buffered reader used to read from this server
*
@ -109,101 +95,6 @@ public class Server {
return this.reader;
}
/**
* Marks the servers as finished stopping when a stop is confirmed
*/
public static void serversStopped() {
if (stoppingServers) {
stoppingServers = false;
}
}
/**
* Tries to stop all enabled servers
*
* @throws IOException <p>If a writer's process is already closed but not null</p>
*/
public static void stop() throws IOException, InterruptedException {
if (stoppingServers) {
killServers();
return;
}
stoppingServers = true;
for (Collection collection : Main.getController().getCurrentProfile().getCollections()) {
Server server = collection.getServer();
if (server.writer != null) {
if (server.type.isProxy()) {
server.writer.write("end\n");
} else {
server.writer.write("stop\n");
}
server.writer.flush();
server.writer = null;
server.started = false;
}
}
}
/**
* Kills all server processes
*
* @throws InterruptedException <p>If interrupted waiting for any of the servers to stop</p>
*/
private static void killServers() throws InterruptedException {
for (Collection collection : Main.getController().getCurrentProfile().getCollections()) {
Server server = collection.getServer();
killServer(server);
}
}
/**
* Kills the given server after waiting 30 seconds for it to terminate normally
*
* @param server <p>The server to kill</p>
* @throws InterruptedException <p>If interrupted waiting for the server to stop</p>
*/
private static void killServer(Server server) throws InterruptedException {
if (server.process != null) {
if (!server.process.waitFor(30, TimeUnit.SECONDS)) {
server.process.destroyForcibly();
server.process.waitFor();
}
}
}
/**
* Runs all enabled servers with their settings
*/
public static void startServers() {
ServerLauncherController controller = Main.getController();
controller.getGUI().setStatus("Starting servers");
int serverNum = 0;
for (Collection collection : controller.getCurrentProfile().getCollections()) {
if (!collection.getServer().runServer(serverNum++ == 0)) {
controller.getGUI().showError("An error occurred. Start aborted. Please check the BuildTools log.");
try {
Server.stop();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
controller.getGUI().updateGUIElementsWhenServersStartOrStop(false);
return;
}
}
}
/**
* Gets a server object from a server save string
*
* @param saveString <p>The string containing necessary data regarding the server</p>
* @return <p>A server in the same state it was saved in</p>
*/
public static Server fromString(String saveString) throws ConfigurationException {
String[] data = saveString.split(";");
return new Server(data[0], data[1], Boolean.parseBoolean(data[2]), data[3], data[4], data[5]);
}
/**
* Gets the name of the server
*
@ -269,6 +160,42 @@ public class Server {
}
}
/**
* Gets whether this server is a proxy server
*
* <p>A proxy server is a server running BungeeCord, Waterfall or Travertine.</p>
*
* @return <p>True if this server is a proxy server</p>
*/
public boolean isProxy() {
return this.type.isProxy();
}
/**
* Marks this server as stopped
*/
public void setStopped() {
this.started = false;
}
/**
* Gets the writer used to write to this server
*
* @return <p>The writer used.</p>
*/
public BufferedWriter getWriter() {
return this.writer;
}
/**
* Sets the writer used to write to this server
*
* @param writer <p>The new writer to use.</p>
*/
public void setWriter(BufferedWriter writer) {
this.writer = writer;
}
/**
* Gets the path for this server's files
*
@ -374,7 +301,7 @@ public class Server {
*/
private void addPlayer(String name) {
this.playerList.add(name);
Main.getController().getGUI().getServerControlTab().addPlayer(name);
gui.getServerControlTab().addPlayer(name);
}
/**
@ -384,7 +311,7 @@ public class Server {
*/
private void removePlayer(String name) {
playerList.removeIf(player -> player.equals(name));
Main.getController().getGUI().getServerControlTab().removePlayer(name);
gui.getServerControlTab().removePlayer(name);
}
/**
@ -399,10 +326,12 @@ public class Server {
/**
* Runs a Minecraft server
*
* @param skipDelay <p>Whether to skip the startup delay for this server</p>
* @return <p>True if nothing went wrong</p>
*/
private boolean runServer(boolean isFirstServer) {
if (stoppingServers) {
public boolean runServer(boolean skipDelay) {
if (ServerHandler.stoppingServers()) {
gui.logMessage("Stopping servers. Cannot start yet.");
return false;
}
//Ignore a disabled server
@ -411,21 +340,23 @@ public class Server {
return true;
}
//Tries to do necessary pre-start work
if (!initializeJarDownload() || (!isFirstServer && !delayStartup())) {
if (!initializeJarDownload() || (!skipDelay && !delayStartup())) {
gui.logError("Failed to perform startup tasks.");
this.started = false;
return false;
}
if (stoppingServers) {
if (ServerHandler.stoppingServers()) {
gui.logMessage("Stopping servers. Cannot start yet.");
return false;
}
//Starts the server if possible
try {
startServerProcess();
Main.getController().getGUI().setStatus("Servers are running");
gui.setStatus("Servers are running");
this.started = true;
return true;
} catch (IOException e) {
Main.getController().getGUI().setStatus("Could not start server");
gui.setStatus("Could not start server");
this.started = false;
return false;
}
@ -438,14 +369,18 @@ public class Server {
*/
private String getJavaCommand() {
ServerLauncherController controller = ServerLauncherController.getInstance();
if (serverVersion.toLowerCase().contains("latest")) {
return controller.getJavaCommand();
} else if (serverVersion.contains(".") && serverVersion.split("\\.").length >= 2 &&
Integer.parseInt(serverVersion.split("\\.")[1]) >= 17) {
} else if (serverVersion.contains(".") && serverVersion.split("\\.").length >= 2) {
try {
if (Integer.parseInt(serverVersion.split("\\.")[1]) >= 17) {
return controller.getJavaCommand();
} else {
return controller.getOldJavaCommand();
}
} catch (NumberFormatException ignored) {
}
}
return controller.getOldJavaCommand();
}
/**
@ -529,11 +464,11 @@ public class Server {
private String getPlayer(String text, boolean joined) {
String playerName;
String loginPattern1 = " ([A-Z0-9a-z]+)\\[/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+] logged in";
String loginPattern2 = "UUID of player ([A-Z0-9a-z]+) is";
String loginPattern1 = " ([A-Z0-9a-z_]+)\\[/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+] logged in";
String loginPattern2 = "UUID of player ([A-Z0-9a-z_]+) is";
String logoutPattern1 = "INFO]: ([A-Z0-9a-z]+) lost connection";
String logoutPattern2 = " ([A-Z0-9a-z]+) left the game";
String logoutPattern1 = "INFO]: ([A-Z0-9a-z_]+) lost connection";
String logoutPattern2 = " ([A-Z0-9a-z_]+) left the game";
if (joined) {
playerName = getFirstRegexCaptureGroup(loginPattern1, text);
@ -573,7 +508,7 @@ public class Server {
*/
private boolean delayStartup() {
try {
Main.getController().getGUI().setStatus("Delaying startup");
gui.setStatus("Delaying startup");
TimeUnit.SECONDS.sleep(Main.getController().getCurrentProfile().getDelayStartup());
return true;
} catch (InterruptedException e) {
@ -589,19 +524,16 @@ public class Server {
* @return <p>True if nothing went wrong</p>
*/
private boolean initializeJarDownload() {
ServerLauncherController controller = Main.getController();
if (!controller.getDownloadAllJars()) {
try {
controller.getGUI().setStatus("Downloading jar...");
gui.setStatus("Downloading jar...");
this.downloadJar();
controller.getGUI().setStatus("File downloaded");
gui.setStatus("File downloaded");
} catch (IOException e) {
controller.getGUI().setStatus("Error: Jar file not found");
e.printStackTrace();
gui.setStatus("Error: Jar file could not be found, downloaded or built.");
gui.logError("Unable to get required .jar file: " + e.getMessage());
this.started = false;
return false;
}
}
return true;
}

View File

@ -0,0 +1,151 @@
package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import javax.naming.ConfigurationException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class ServerHandler {
/**
* Available ram sizes. For ServerLauncherGUI combo
*/
private static final String[] ramList = {"512M", "1G", "2G", "3G", "4G", "5G", "6G", "7G", "8G", "9G", "10G",
"11G", "12G", "13G", "14G", "15G", "16G"};
private static boolean stoppingServers = false;
private static final ServerLauncherGUI gui = Main.getController().getGUI();
/**
* Gets the list of available RAM choices allowed
*
* @return <p>All available RAM choices</p>
*/
public static String[] getRamList() {
return ramList;
}
/**
* Gets whether the servers are currently in the process of stopping
*
* @return <p>True if the servers are in the process of stopping.</p>
*/
public static boolean stoppingServers() {
return stoppingServers;
}
/**
* Marks the servers as finished stopping when a stop is confirmed
*/
public static void serversStopped() {
if (stoppingServers) {
stoppingServers = false;
}
}
/**
* Tries to stop all enabled servers
*
* @throws IOException <p>If a writer's process is already closed but not null</p>
*/
public static void stop() throws IOException, InterruptedException {
if (stoppingServers) {
killServers();
return;
}
stoppingServers = true;
int serversRunning = 0;
for (Collection collection : Main.getController().getCurrentProfile().getCollections()) {
Server server = collection.getServer();
BufferedWriter writer = server.getWriter();
if (writer != null) {
serversRunning++;
if (server.isProxy()) {
writer.write("end\n");
} else {
writer.write("stop\n");
}
writer.flush();
server.setWriter(null);
server.setStopped();
}
}
if (serversRunning == 0) {
stoppingServers = false;
}
}
/**
* Kills all server processes
*
* @throws InterruptedException <p>If interrupted waiting for any of the servers to stop</p>
*/
private static void killServers() throws InterruptedException {
for (Collection collection : Main.getController().getCurrentProfile().getCollections()) {
Server server = collection.getServer();
killServer(server);
}
}
/**
* Kills the given server after waiting 30 seconds for it to terminate normally
*
* @param server <p>The server to kill</p>
* @throws InterruptedException <p>If interrupted waiting for the server to stop</p>
*/
private static void killServer(Server server) throws InterruptedException {
Process serverProcess = server.getProcess();
if (serverProcess != null) {
if (!serverProcess.waitFor(30, TimeUnit.SECONDS)) {
serverProcess.destroyForcibly();
serverProcess.waitFor();
}
}
}
/**
* Runs all enabled servers with their settings
*/
public static void startServers() {
ServerLauncherController controller = Main.getController();
gui.setStatus("Starting servers");
Server previouslyStartedServer = null;
for (Collection collection : controller.getCurrentProfile().getCollections()) {
Server server = collection.getServer();
if (!server.runServer(previouslyStartedServer == null || previouslyStartedServer.isProxy())) {
gui.showError("An error occurred. Start aborted. Please check relevant log files.");
try {
stop();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
gui.updateGUIElementsWhenServersStartOrStop(false);
return;
}
if (server.isEnabled()) {
previouslyStartedServer = server;
}
}
}
/**
* Gets a server object from a server save string
*
* @param saveString <p>The string containing necessary data regarding the server</p>
* @return <p>A server in the same state it was saved in</p>
*/
public static Server fromString(String saveString) throws ConfigurationException {
String[] data = saveString.split(";");
return new Server(data[0], data[1], Boolean.parseBoolean(data[2]), data[3], data[4], data[5]);
}
}

View File

@ -134,7 +134,7 @@ public class ServerTypeHandler {
break;
case "Paper":
newType = new Paper("Paper", false, serverVersions, serverTypeInfo[2],
serverTypeInfo[3]);
serverTypeInfo[3], serverTypeInfo[4], serverTypeInfo[5]);
break;
case "Bungee":
newType = new BungeeCord("Bungee", true, serverVersions, serverTypeInfo[2],

View File

@ -22,6 +22,7 @@ public class ServerVersionContainer {
private String bungeeVersion;
private Map<String, String> waterfallVersions;
private Map<String, String> travertineVersions;
private Map<String, String> paperVersions;
private Map<String, String> spongeVanillaVersions;
private Map<String, String> spongeForgeVersions;
private String downloadedBuildToolsVersion;
@ -32,6 +33,7 @@ public class ServerVersionContainer {
private ServerVersionContainer() {
this.waterfallVersions = new HashMap<>();
this.travertineVersions = new HashMap<>();
this.paperVersions = new HashMap<>();
this.spongeVanillaVersions = new HashMap<>();
this.spongeForgeVersions = new HashMap<>();
loadState();
@ -58,6 +60,7 @@ public class ServerVersionContainer {
this.bungeeVersion = null;
this.waterfallVersions = new HashMap<>();
this.travertineVersions = new HashMap<>();
this.paperVersions = new HashMap<>();
this.spongeVanillaVersions = new HashMap<>();
this.spongeForgeVersions = new HashMap<>();
this.downloadedBuildToolsVersion = null;
@ -70,6 +73,7 @@ public class ServerVersionContainer {
"bungeeVersion;" + bungeeVersion + "\n" +
"waterfallVersions;" + mapToString(waterfallVersions) + "\n" +
"travertineVersions;" + mapToString(travertineVersions) + "\n" +
"paperVersions;" + mapToString(paperVersions) + "\n" +
"spongeVanillaVersions;" + mapToString(spongeVanillaVersions) + "\n" +
"spongeForgeVersions;" + mapToString(spongeForgeVersions) + "\n" +
"downloadedBuildToolsVersion;" + downloadedBuildToolsVersion;
@ -163,6 +167,9 @@ public class ServerVersionContainer {
case "travertineVersions":
parseVersionsToMap(travertineVersions, variableValue);
break;
case "paperVersions":
parseVersionsToMap(paperVersions, variableValue);
break;
case "spongeVanillaVersions":
parseVersionsToMap(spongeVanillaVersions, variableValue);
break;
@ -291,6 +298,27 @@ public class ServerVersionContainer {
saveState();
}
/**
* Gets a specific paper version
*
* @param versionKey <p>The version to check the current version of</p>
* @return <p>The current paper version</p>
*/
public String getPaperVersion(String versionKey) {
return this.paperVersions.get(versionKey);
}
/**
* Sets the current version for a given paper version
*
* @param mapKey <p>The version key to set version for</p>
* @param newValue <p>The new current version</p>
*/
public void setPaperVersion(String mapKey, String newValue) {
this.paperVersions.put(mapKey, newValue);
saveState();
}
/**
* Gets a specific sponge vanilla version
*

View File

@ -2,8 +2,8 @@ package net.knarcraft.minecraftserverlauncher.server.servertypes;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.JarBuilder;
import net.knarcraft.minecraftserverlauncher.userinterface.GUI;
import net.knarcraft.minecraftserverlauncher.utility.JarBuilder;
import java.io.File;
import java.nio.file.Paths;

View File

@ -3,19 +3,25 @@ package net.knarcraft.minecraftserverlauncher.server.servertypes;
/**
* This class represents the Paper Minecraft server type
*/
public class Paper extends Spigot {
public class Paper extends Waterfall {
/**
* Instantiates a new Paper server type
*
* @param typeName <p>The name of the server type</p>
* @param isProxy <p>Whether this server type is a proxy server</p>
* @param versions <p>A list of one or more server versions for the type</p>
* @param downloadURL <p>The URL used for downloading .jar files</p>
* @param downloadURLPart <p>A string used after the download url as an additional part of the URL</p>
* @param versions <p>The available versions for the server type</p>
* @param versionURL <p>The URL used to finding the newest version</p>
* @param srcStart <p>The string after which the version id starts</p>
* @param srcEnd <p>The string marking the end of the version id</p>
* @param downloadURL <p>The URL used for downloading the latest version</p>
*/
public Paper(String typeName, boolean isProxy, String[] versions, String downloadURL, String downloadURLPart) {
super(typeName, isProxy, versions, downloadURL, downloadURLPart);
public Paper(String typeName, boolean isProxy, String[] versions, String versionURL, String srcStart,
String srcEnd, String downloadURL) {
super(typeName, isProxy, versions, versionURL, srcStart, srcEnd, downloadURL);
this.oldVersionFunction = serverVersionContainer::getPaperVersion;
this.versionUpdateFunction = serverVersionContainer::setPaperVersion;
}
}

View File

@ -50,13 +50,21 @@ public class Waterfall extends AbstractServerType {
public boolean downloadJar(String folder, String version) throws IOException {
String file = this.getName() + version + ".jar";
File filePath = new File(folder + file);
String newestVersion = stringBetween(readRemoteFile(versionURL + version), srcStart, srcEnd);
String fullURL = downloadURL + version + "/" + newestVersion + "/download";
String versionFileContents = readRemoteFile(versionURL + version);
String[] versions = stringBetween(versionFileContents, srcStart, srcEnd).split(",");
String newestVersion = versions[versions.length - 1];
String oldVersion = oldVersionFunction.apply(version);
//The file is already the newest version
if (filePath.isFile() && newestVersion.equals(oldVersion)) {
return true;
}
//Get necessary information for downloading the latest version
String newestVersionInfoURL = versionURL + version + "/builds/" + newestVersion;
String fileName = stringBetween(readRemoteFile(newestVersionInfoURL), "\"name\":\"", "\"");
String fullURL = downloadURL + version + "/builds/" + newestVersion + "/downloads/" + fileName;
//The new jar file could not be downloaded
if (!downloadFile(fullURL, Paths.get(filePath.toURI()))) {
return false;
@ -64,4 +72,5 @@ public class Waterfall extends AbstractServerType {
versionUpdateFunction.accept(version, newestVersion);
return true;
}
}

View File

@ -0,0 +1,106 @@
package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.utility.BackupUtil;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsStream;
/**
* The Backup GUI is used to display backup progress
*/
public class BackupGUI implements ActionListener {
private static JFrame frame;
private static JTextArea progressTextArea;
private static JProgressBar progressBar;
private static JButton cancelButton;
/**
* Instantiates a new GUI
*/
public BackupGUI() {
instantiate();
}
/**
* Initializes the server consoles frame
*/
public void instantiate() {
if (frame != null) {
return;
}
frame = new JFrame("Running backup...");
frame.setBounds(100, 100, 500, 140);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
ImageIcon img;
try {
img = new ImageIcon(ImageIO.read(getResourceAsStream("GUIIcon.png")));
frame.setIconImage(img.getImage());
} catch (IOException ignored) {
}
progressTextArea = new JTextArea();
progressTextArea.setEditable(false);
progressBar = new JProgressBar();
cancelButton = new JButton("Cancel");
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.add(progressTextArea);
panel.add(Box.createRigidArea(new Dimension(0, 5)));
panel.add(progressBar);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(cancelButton);
cancelButton.addActionListener(BackupGUI.this);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
Container contentPane = frame.getContentPane();
contentPane.add(panel, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
frame.setVisible(true);
}
/**
* Updates information about the backup progress
*
* @param infoText <p>The text to display</p>
* @param progressPercent <p>The new percent of the progress bar</p>
*/
public static void updateProgress(String infoText, int progressPercent) {
if (progressTextArea != null) {
progressTextArea.setText(infoText);
}
if (progressBar != null) {
progressBar.setValue(progressPercent);
}
}
/**
* Destroys the backup GUI
*/
public static void destroy() {
if (frame != null) {
frame.dispose();
frame = null;
progressBar = null;
progressTextArea = null;
cancelButton = null;
}
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == cancelButton) {
BackupUtil.abortBackup();
destroy();
}
}
}

View File

@ -0,0 +1,48 @@
package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Listener for clicking the close button of a server tab
*/
public class CloseTabActionListener implements ActionListener {
private final String tabName;
private final JTabbedPane tabPane;
/**
* Instantiates a new close tab action listener
*
* @param tabPane <p>The tab pane containing all tabs</p>
* @param tabName <p>The name of the tab connected to this action listener</p>
*/
public CloseTabActionListener(JTabbedPane tabPane, String tabName) {
this.tabName = tabName;
this.tabPane = tabPane;
}
@Override
public void actionPerformed(ActionEvent evt) {
int index = tabPane.indexOfTab(tabName);
if (index >= 0) {
//Abort if the user just mis-clicked
int answer = JOptionPane.showConfirmDialog(null,
String.format("Do you really want to remove the server %s?", tabName), "Remove server",
JOptionPane.YES_NO_OPTION
);
if (answer == JOptionPane.YES_NO_OPTION) {
//Remove the server
ServerLauncherController controller = Main.getController();
controller.getCurrentProfile().removeCollection(tabName);
controller.getGUI().updateWithSavedProfileData();
controller.getCurrentProfile().updateConsoles();
}
}
}
}

View File

@ -93,7 +93,7 @@ public class Console extends KeyAdapter implements ActionListener, KeyListener {
StringBuilder newTextBuilder = new StringBuilder();
for (String line : oldTextList) {
if (!line.equals("")) {
newTextBuilder.append(line);
newTextBuilder.append(line).append("\n");
}
}
this.textOutput.setText(newTextBuilder.toString());

View File

@ -0,0 +1,218 @@
package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.utility.BackupUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.concurrent.Executors;
public class ControlPanelTab implements ActionListener {
private JButton startServerButton;
private JButton stopServerButton;
private JButton backupButton;
private JButton addProfileButton;
private JButton showConsolesButton;
private JButton deleteProfileButton;
private JComboBox<String> profiles;
private final JPanel controlPanelPanel;
private final ServerLauncherController controller;
private final JLabel statusLabel = new JLabel("Servers are stopped");
public ControlPanelTab(JPanel controlPanelPanel) {
this.controlPanelPanel = controlPanelPanel;
this.controller = Main.getController();
initialize();
}
/**
* Sets the text of the status label
*
* @param text <p>The new text of the status label</p>
*/
public void setStatusText(String text) {
this.statusLabel.setText(text);
}
/**
* Updates the ServerLauncherGUI components to block a user from doing illegal actions
*
* @param running <p>Whether the servers are currently running</p>
*/
public void updateGUIElementsWhenServersStartOrStop(boolean running) {
boolean stopped = !running; //Most gui is only enabled when the server is stopped rather than running.
profiles.setEnabled(stopped);
addProfileButton.setEnabled(stopped);
deleteProfileButton.setEnabled(stopped);
startServerButton.setEnabled(stopped);
stopServerButton.setEnabled(running);
}
/**
* Stops all servers
*/
public void stopServers() {
GUI gui = Main.getController().getGUI();
try {
gui.setStatus("Servers are stopping...");
ServerHandler.stop();
} catch (IOException e1) {
gui.showError("Could not stop server.");
e1.printStackTrace();
} catch (InterruptedException e) {
gui.showError("Could not kill server.");
e.printStackTrace();
}
}
/**
* Gets the currently selected profile
*
* @return <p>The currently selected profile or null</p>
*/
public String getSelectedProfile() {
Object selectedProfile = profiles.getSelectedItem();
if (selectedProfile != null) {
return selectedProfile.toString();
} else {
return null;
}
}
/**
* Updates the profiles combo
*/
public void updateProfiles() {
String selectedProfile = Main.getController().getCurrentProfile().getName();
this.profiles.removeAllItems();
for (String profile : Main.getController().getProfileNames()) {
this.profiles.addItem(profile);
}
this.profiles.setSelectedItem(selectedProfile);
}
/**
* Deletes the selected profile
*/
private void deleteProfile() {
Object selected = profiles.getSelectedItem();
int answer = JOptionPane.showConfirmDialog(null,
String.format("Do you really want to remove the profile %s?", selected), "Remove profile",
JOptionPane.YES_NO_OPTION
);
if (answer == JOptionPane.YES_NO_OPTION) {
if (selected != null) {
controller.removeProfile(selected.toString());
updateProfiles();
}
}
}
/**
* Saves the previous profile and loads data from the new profile
*/
private void changeProfile() {
controller.saveState();
Object current = this.profiles.getSelectedItem();
if (current != null) {
controller.setCurrentProfile(current.toString());
}
controller.getGUI().updateWithSavedProfileData();
controller.getCurrentProfile().updateConsoles();
}
private void initialize() {
SpringLayout springLayout = new SpringLayout();
controlPanelPanel.setLayout(springLayout);
JLabel lblBasicControls = new JLabel("Basic controls");
springLayout.putConstraint(SpringLayout.NORTH, lblBasicControls, 10, SpringLayout.NORTH, controlPanelPanel);
controlPanelPanel.add(lblBasicControls);
startServerButton = new JButton("Start servers");
springLayout.putConstraint(SpringLayout.WEST, lblBasicControls, 0, SpringLayout.WEST, startServerButton);
springLayout.putConstraint(SpringLayout.NORTH, startServerButton, 6, SpringLayout.SOUTH, lblBasicControls);
springLayout.putConstraint(SpringLayout.WEST, startServerButton, 10, SpringLayout.WEST, controlPanelPanel);
controlPanelPanel.add(startServerButton);
startServerButton.addActionListener(this);
stopServerButton = new JButton("Stop servers");
springLayout.putConstraint(SpringLayout.NORTH, stopServerButton, 0, SpringLayout.NORTH, startServerButton);
springLayout.putConstraint(SpringLayout.WEST, stopServerButton, 6, SpringLayout.EAST, startServerButton);
controlPanelPanel.add(stopServerButton);
stopServerButton.addActionListener(this);
showConsolesButton = new JButton("View server consoles");
springLayout.putConstraint(SpringLayout.NORTH, showConsolesButton, 0, SpringLayout.NORTH, stopServerButton);
springLayout.putConstraint(SpringLayout.WEST, showConsolesButton, 6, SpringLayout.EAST, stopServerButton);
controlPanelPanel.add(showConsolesButton);
showConsolesButton.addActionListener(this);
backupButton = new JButton("Backup");
springLayout.putConstraint(SpringLayout.NORTH, backupButton, 0, SpringLayout.NORTH, showConsolesButton);
springLayout.putConstraint(SpringLayout.WEST, backupButton, 6, SpringLayout.EAST, showConsolesButton);
controlPanelPanel.add(backupButton);
backupButton.addActionListener(this);
JLabel profileLabel = new JLabel("Profile");
springLayout.putConstraint(SpringLayout.NORTH, profileLabel, 6, SpringLayout.SOUTH, startServerButton);
springLayout.putConstraint(SpringLayout.WEST, profileLabel, 10, SpringLayout.WEST, controlPanelPanel);
controlPanelPanel.add(profileLabel);
addProfileButton = new JButton("+");
springLayout.putConstraint(SpringLayout.NORTH, addProfileButton, 6, SpringLayout.SOUTH, profileLabel);
springLayout.putConstraint(SpringLayout.WEST, addProfileButton, 10, SpringLayout.WEST, controlPanelPanel);
controlPanelPanel.add(addProfileButton);
addProfileButton.addActionListener(this);
deleteProfileButton = new JButton("-");
springLayout.putConstraint(SpringLayout.NORTH, deleteProfileButton, 0, SpringLayout.NORTH, addProfileButton);
springLayout.putConstraint(SpringLayout.WEST, deleteProfileButton, 6, SpringLayout.EAST, addProfileButton);
controlPanelPanel.add(deleteProfileButton);
deleteProfileButton.addActionListener(this);
profiles = new JComboBox<>();
springLayout.putConstraint(SpringLayout.NORTH, profiles, 0, SpringLayout.NORTH, addProfileButton);
springLayout.putConstraint(SpringLayout.WEST, profiles, 6, SpringLayout.EAST, deleteProfileButton);
springLayout.putConstraint(SpringLayout.EAST, profiles, 124, SpringLayout.EAST, deleteProfileButton);
controlPanelPanel.add(profiles);
profiles.addActionListener(this);
springLayout.putConstraint(SpringLayout.NORTH, statusLabel, 6, SpringLayout.SOUTH, addProfileButton);
springLayout.putConstraint(SpringLayout.SOUTH, statusLabel, -10, SpringLayout.SOUTH, controlPanelPanel);
springLayout.putConstraint(SpringLayout.WEST, statusLabel, 10, SpringLayout.WEST, controlPanelPanel);
springLayout.putConstraint(SpringLayout.EAST, statusLabel, -10, SpringLayout.EAST, controlPanelPanel);
statusLabel.setFont(new Font("", Font.BOLD, 12));
controlPanelPanel.add(statusLabel);
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
Object actionSource = actionEvent.getSource();
if (actionSource == startServerButton) {
controller.saveState();
Executors.newSingleThreadExecutor().execute(ServerHandler::startServers);
} else if (actionSource == stopServerButton) {
stopServers();
} else if (actionSource == backupButton) {
//Run backup in its own thread to prevent locking up
Executors.newSingleThreadExecutor().execute(() -> BackupUtil.backup(controller.getGUI()));
} else if (actionSource == addProfileButton) {
controller.addProfile(JOptionPane.showInputDialog("Profile name: "));
updateProfiles();
} else if (actionSource == deleteProfileButton) {
deleteProfile();
} else if (actionSource == profiles) {
changeProfile();
} else if (actionSource == showConsolesButton) {
ServerConsoles.setAsVisible();
}
}
}

View File

@ -1,7 +1,11 @@
package net.knarcraft.minecraftserverlauncher.userinterface;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsStream;
/**
* This class keeps track of all consoles
@ -31,6 +35,12 @@ public class ServerConsoles {
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
consolesTabbedPane = new JTabbedPane(JTabbedPane.TOP);
frame.getContentPane().add(consolesTabbedPane, BorderLayout.CENTER);
ImageIcon img;
try {
img = new ImageIcon(ImageIO.read(getResourceAsStream("GUIIcon.png")));
frame.setIconImage(img.getImage());
} catch (IOException ignored) {
}
}
}

View File

@ -22,7 +22,6 @@ public class ServerControlTab implements ActionListener {
private JButton customCommandButton;
private JButton saveServerButton;
private JButton reloadButton;
private JButton showConsolesButton;
private JTextField customCommandTextField;
private final ServerLauncherController controller = ServerLauncherController.getInstance();
@ -134,13 +133,6 @@ public class ServerControlTab implements ActionListener {
springLayout.putConstraint(SpringLayout.EAST, reloadButton, 0, SpringLayout.EAST, opButton);
controlServers.add(reloadButton);
reloadButton.addActionListener(this);
showConsolesButton = new JButton("View server consoles");
springLayout.putConstraint(SpringLayout.NORTH, showConsolesButton, 0, SpringLayout.NORTH, saveServerButton);
springLayout.putConstraint(SpringLayout.WEST, showConsolesButton, 0, SpringLayout.WEST, lblTargetServer);
springLayout.putConstraint(SpringLayout.EAST, showConsolesButton, 0, SpringLayout.EAST, targetServerCombo);
controlServers.add(showConsolesButton);
showConsolesButton.addActionListener(this);
}
/**
@ -181,9 +173,7 @@ public class ServerControlTab implements ActionListener {
//Registers actions on all commands executed on a specific server
handleServerCommands(actionSource, selectedServerValue);
if (actionSource == showConsolesButton) {
ServerConsoles.setAsVisible();
} else if (actionSource == targetServerCombo) {
if (actionSource == targetServerCombo) {
updatePlayers();
}
}

View File

@ -3,12 +3,11 @@ package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.imageio.ImageIO;
import javax.naming.ConfigurationException;
import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -18,7 +17,6 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executors;
import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsScanner;
@ -33,25 +31,19 @@ import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getR
*/
public class ServerLauncherGUI extends MessageHandler implements ActionListener, GUI {
private final JLabel lblStatuslabel = new JLabel("Servers are stopped");
private final ServerLauncherController controller;
private Map<String, String> textStrings;
private Tray applicationTray;
private JFrame frame;
private JTabbedPane tabbedPane;
private JTabbedPane mainTabbedPane;
private JTabbedPane serversPane;
private ServerControlTab serverControlTab;
private ControlPanelTab controlPanelTab;
private ServerLauncherMenu serverLauncherMenu;
//Basic controls
private JButton startServerButton;
private JButton stopServerButton;
private JButton addServerButton;
private JButton backupButton;
private JButton addProfileButton;
private JButton deleteProfileButton;
private JComboBox<String> profiles;
private JButton addServerTabButton;
private JButton addServerPaneButton;
/**
* Creates the application window
@ -99,9 +91,18 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
return this.serversPane;
}
/**
* Gets this GUI's control panel tab
*
* @return <p>The control panel tab for this GUI</p>
*/
public ControlPanelTab getControlPanelTab() {
return this.controlPanelTab;
}
@Override
public void setStatus(String text) {
this.lblStatuslabel.setText(text);
controlPanelTab.setStatusText(text);
this.logMessage(text);
}
@ -120,18 +121,6 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
return chooser.getSelectedFile();
}
/**
* Updates the profiles combo
*/
public void updateProfiles() {
String selectedProfile = Main.getController().getCurrentProfile().getName();
this.profiles.removeAllItems();
for (String profile : Main.getController().getProfileNames()) {
this.profiles.addItem(profile);
}
this.profiles.setSelectedItem(selectedProfile);
}
/**
* Gets the server control tab used by this GUI
*
@ -160,7 +149,82 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
serverControlTab.update();
for (Collection collection : controller.getCurrentProfile().getCollections()) {
serversPane.addTab(collection.getName(), collection.getServerTab().getPanel());
addCloseButtonToServerTab(collection.getName());
}
addAddButtonToServerTab();
}
/**
* Adds an add button to the servers tab's tabs
*/
private void addAddButtonToServerTab() {
JPanel tabPanel = new JPanel();
tabPanel.setLayout(new GridLayout());
tabPanel.setOpaque(false);
JPanel tabContentsPanel = new JPanel(new SpringLayout());
serversPane.addTab("Add tab", tabContentsPanel);
addServerTabButton = getAddServerButton(true);
addServerTabButton.addActionListener(this);
addServerPaneButton = getAddServerButton(false);
addServerPaneButton.addActionListener(this);
tabContentsPanel.add(addServerTabButton);
tabPanel.add(addServerPaneButton);
serversPane.setTabComponentAt(serversPane.getTabCount() - 1, tabPanel);
}
/**
* Gets a button for adding a new server
*
* @param displayButtonStyle <p>Whether to show or hide the button's style</p>
* @return <p>A new add server button</p>
*/
private JButton getAddServerButton(boolean displayButtonStyle) {
JButton addButton = new JButton("+ Add server");
if (!displayButtonStyle) {
addButton.setBorder(BorderFactory.createEtchedBorder());
addButton.setFocusable(false);
addButton.setBorderPainted(false);
addButton.setContentAreaFilled(false);
addButton.setRolloverEnabled(true);
addButton.setUI(new BasicButtonUI());
}
return addButton;
}
/**
* @param serverName <p>The name of the server/tab to add a close button to</p>
*/
private void addCloseButtonToServerTab(String serverName) {
int index = serversPane.indexOfTab(serverName);
JPanel tabPanel = new JPanel(new GridBagLayout());
tabPanel.setOpaque(false);
JLabel serverTitleLabel = new JLabel(serverName);
JButton removeServerButton = new JButton("(X)");
removeServerButton.setBorder(BorderFactory.createEtchedBorder());
removeServerButton.setFocusable(false);
removeServerButton.setBorderPainted(false);
removeServerButton.setContentAreaFilled(false);
removeServerButton.setRolloverEnabled(true);
removeServerButton.setPreferredSize(new Dimension(18, 17));
removeServerButton.setUI(new BasicButtonUI());
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.weightx = 1;
tabPanel.add(serverTitleLabel, gridBagConstraints);
gridBagConstraints.gridx++;
gridBagConstraints.weightx = 0;
tabPanel.add(removeServerButton, gridBagConstraints);
serversPane.setTabComponentAt(index, tabPanel);
removeServerButton.addActionListener(new CloseTabActionListener(serversPane, serverName));
}
/**
@ -194,93 +258,30 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
this.serverLauncherMenu = new ServerLauncherMenu(menuBar, this);
tabbedPane = new JTabbedPane(JTabbedPane.TOP);
frame.getContentPane().add(tabbedPane);
mainTabbedPane = new JTabbedPane(JTabbedPane.TOP);
frame.getContentPane().add(mainTabbedPane);
JPanel panelBasic = new JPanel();
tabbedPane.addTab("Control panel", null, panelBasic, null);
SpringLayout sl_panel = new SpringLayout();
panelBasic.setLayout(sl_panel);
JPanel controlPanelPanel = new JPanel();
mainTabbedPane.addTab("Control panel", null, controlPanelPanel, null);
controlPanelTab = new ControlPanelTab(controlPanelPanel);
JLabel lblBasicControls = new JLabel("Basic controls");
sl_panel.putConstraint(SpringLayout.NORTH, lblBasicControls, 10, SpringLayout.NORTH, panelBasic);
panelBasic.add(lblBasicControls);
JPanel controlServersPanel = new JPanel();
mainTabbedPane.addTab("Control servers", null, controlServersPanel, null);
serverControlTab = new ServerControlTab(frame, controlServersPanel);
startServerButton = new JButton("Start servers");
sl_panel.putConstraint(SpringLayout.WEST, lblBasicControls, 0, SpringLayout.WEST, startServerButton);
sl_panel.putConstraint(SpringLayout.NORTH, startServerButton, 6, SpringLayout.SOUTH, lblBasicControls);
sl_panel.putConstraint(SpringLayout.WEST, startServerButton, 10, SpringLayout.WEST, panelBasic);
panelBasic.add(startServerButton);
startServerButton.addActionListener(this);
JPanel serversPanel = new JPanel();
mainTabbedPane.addTab("Servers", null, serversPanel, null);
SpringLayout serversPanelSpringLayout = new SpringLayout();
serversPanel.setLayout(serversPanelSpringLayout);
stopServerButton = new JButton("Stop servers");
sl_panel.putConstraint(SpringLayout.NORTH, stopServerButton, 0, SpringLayout.NORTH, startServerButton);
sl_panel.putConstraint(SpringLayout.WEST, stopServerButton, 6, SpringLayout.EAST, startServerButton);
panelBasic.add(stopServerButton);
stopServerButton.addActionListener(this);
serversPane = new JTabbedPane(JTabbedPane.TOP);
serversPanelSpringLayout.putConstraint(SpringLayout.NORTH, serversPane, 0, SpringLayout.NORTH, serversPanel);
serversPanelSpringLayout.putConstraint(SpringLayout.WEST, serversPane, 0, SpringLayout.WEST, serversPanel);
serversPanelSpringLayout.putConstraint(SpringLayout.SOUTH, serversPane, 0, SpringLayout.SOUTH, serversPanel);
serversPanelSpringLayout.putConstraint(SpringLayout.EAST, serversPane, 0, SpringLayout.EAST, serversPanel);
serversPanel.add(serversPane);
JLabel lblProfile = new JLabel("Profile");
sl_panel.putConstraint(SpringLayout.NORTH, lblProfile, 6, SpringLayout.SOUTH, startServerButton);
sl_panel.putConstraint(SpringLayout.WEST, lblProfile, 10, SpringLayout.WEST, panelBasic);
panelBasic.add(lblProfile);
addProfileButton = new JButton("+");
sl_panel.putConstraint(SpringLayout.NORTH, addProfileButton, 6, SpringLayout.SOUTH, lblProfile);
sl_panel.putConstraint(SpringLayout.WEST, addProfileButton, 10, SpringLayout.WEST, panelBasic);
panelBasic.add(addProfileButton);
addProfileButton.addActionListener(this);
deleteProfileButton = new JButton("-");
sl_panel.putConstraint(SpringLayout.NORTH, deleteProfileButton, 0, SpringLayout.NORTH, addProfileButton);
sl_panel.putConstraint(SpringLayout.WEST, deleteProfileButton, 6, SpringLayout.EAST, addProfileButton);
panelBasic.add(deleteProfileButton);
deleteProfileButton.addActionListener(this);
profiles = new JComboBox<>();
sl_panel.putConstraint(SpringLayout.NORTH, profiles, 0, SpringLayout.NORTH, addProfileButton);
sl_panel.putConstraint(SpringLayout.WEST, profiles, 6, SpringLayout.EAST, deleteProfileButton);
sl_panel.putConstraint(SpringLayout.EAST, profiles, 124, SpringLayout.EAST, deleteProfileButton);
panelBasic.add(profiles);
profiles.addActionListener(this);
sl_panel.putConstraint(SpringLayout.NORTH, lblStatuslabel, 6, SpringLayout.SOUTH, addProfileButton);
sl_panel.putConstraint(SpringLayout.SOUTH, lblStatuslabel, -10, SpringLayout.SOUTH, panelBasic);
sl_panel.putConstraint(SpringLayout.WEST, lblStatuslabel, 10, SpringLayout.WEST, panelBasic);
sl_panel.putConstraint(SpringLayout.EAST, lblStatuslabel, -10, SpringLayout.EAST, panelBasic);
panelBasic.add(lblStatuslabel);
addServerButton = new JButton("Add server");
sl_panel.putConstraint(SpringLayout.NORTH, addServerButton, 0, SpringLayout.NORTH, startServerButton);
sl_panel.putConstraint(SpringLayout.WEST, addServerButton, 6, SpringLayout.EAST, stopServerButton);
panelBasic.add(addServerButton);
addServerButton.addActionListener(this);
backupButton = new JButton("Backup");
sl_panel.putConstraint(SpringLayout.NORTH, backupButton, 0, SpringLayout.NORTH, startServerButton);
sl_panel.putConstraint(SpringLayout.WEST, backupButton, 6, SpringLayout.EAST, addServerButton);
panelBasic.add(backupButton);
backupButton.addActionListener(this);
JPanel controlServers = new JPanel();
tabbedPane.addTab("Control servers", null, controlServers, null);
serverControlTab = new ServerControlTab(frame, controlServers);
JPanel panel_2 = new JPanel();
tabbedPane.addTab("Servers", null, panel_2, null);
SpringLayout sl_panel_2 = new SpringLayout();
panel_2.setLayout(sl_panel_2);
JTabbedPane tabbedPane_1 = new JTabbedPane(JTabbedPane.TOP);
sl_panel_2.putConstraint(SpringLayout.NORTH, tabbedPane_1, 0, SpringLayout.NORTH, panel_2);
sl_panel_2.putConstraint(SpringLayout.WEST, tabbedPane_1, 0, SpringLayout.WEST, panel_2);
sl_panel_2.putConstraint(SpringLayout.SOUTH, tabbedPane_1, 0, SpringLayout.SOUTH, panel_2);
sl_panel_2.putConstraint(SpringLayout.EAST, tabbedPane_1, 0, SpringLayout.EAST, panel_2);
panel_2.add(tabbedPane_1);
this.serversPane = tabbedPane_1;
tabbedPane_1.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
serversPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
frame.validate();
frame.pack();
@ -296,25 +297,10 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
applicationTray.hideToTray();
}
/**
* Gets the currently selected profile
*
* @return <p>The currently selected profile or null</p>
*/
public String getSelectedProfile() {
Object selectedProfile = profiles.getSelectedItem();
if (selectedProfile != null) {
return selectedProfile.toString();
} else {
return null;
}
}
@Override
public void actionPerformed(ActionEvent e) {
Object actionSource = e.getSource();
//Registers actions on the main tab
//Register actions on the main tab
handleMainTabButtons(actionSource);
}
@ -325,33 +311,8 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
* @param actionSource <p>The object being interacted with</p>
*/
private void handleMainTabButtons(Object actionSource) {
if (actionSource == startServerButton) {
controller.saveState();
Executors.newSingleThreadExecutor().execute(Server::startServers);
} else if (actionSource == stopServerButton) {
stopServers();
} else if (actionSource == addServerButton) {
if (actionSource == addServerTabButton || actionSource == addServerPaneButton) {
addServer();
} else if (actionSource == backupButton) {
CommonFunctions.backup(this);
} else if (actionSource == addProfileButton) {
controller.addProfile(JOptionPane.showInputDialog("Profile name: "));
updateProfiles();
} else if (actionSource == deleteProfileButton) {
deleteProfile();
} else if (actionSource == profiles) {
changeProfile();
}
}
/**
* Deletes the selected profile
*/
private void deleteProfile() {
Object selected = profiles.getSelectedItem();
if (selected != null) {
controller.removeProfile(selected.toString());
updateProfiles();
}
}
@ -376,42 +337,9 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
*/
public void updateGUIElementsWhenServersStartOrStop(boolean running) {
boolean stopped = !running; //Most gui is only enabled when the server is stopped rather than running.
profiles.setEnabled(stopped);
addProfileButton.setEnabled(stopped);
deleteProfileButton.setEnabled(stopped);
startServerButton.setEnabled(stopped);
addServerButton.setEnabled(stopped);
tabbedPane.setEnabledAt(2, stopped);
stopServerButton.setEnabled(running);
}
/**
* Saves the previous profile and loads data from the new profile
*/
private void changeProfile() {
controller.saveState();
Object current = this.profiles.getSelectedItem();
if (current != null) {
controller.setCurrentProfile(current.toString());
}
this.updateWithSavedProfileData();
controller.getCurrentProfile().updateConsoles();
}
/**
* Stops all servers
*/
public void stopServers() {
try {
setStatus("Servers are stopping...");
Server.stop();
} catch (IOException e1) {
showError("Could not stop server.");
e1.printStackTrace();
} catch (InterruptedException e) {
showError("Could not kill server.");
e.printStackTrace();
}
mainTabbedPane.setEnabledAt(1, !stopped);
mainTabbedPane.setEnabledAt(2, stopped);
controlPanelTab.updateGUIElementsWhenServersStartOrStop(running);
}
/**

View File

@ -190,6 +190,7 @@ public class ServerLauncherMenu implements ActionListener {
/**
* Asks the user for the new Java path
*
* @param old <p>Whether asking for the path to the old java version</p>
*/
private void configureJava(boolean old) {
@ -237,7 +238,7 @@ public class ServerLauncherMenu implements ActionListener {
* Asks the user for a delay if checked, and sets the value to the current profile
*/
private void delay() {
String selectedProfile = serverLauncherGUI.getSelectedProfile();
String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) {
Profile profile = controller.getProfileByName(selectedProfile);
if (delayStartupCheckBoxMenuItem.isSelected()) {
@ -260,7 +261,7 @@ public class ServerLauncherMenu implements ActionListener {
* Saves the runInBackground setting to the current profile
*/
private void background() {
String selectedProfile = serverLauncherGUI.getSelectedProfile();
String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) {
Profile profile = controller.getProfileByName(selectedProfile);
Objects.requireNonNull(profile).setRunInBackground(runInBackgroundCheckBoxMenuItem.isSelected());
@ -273,7 +274,7 @@ public class ServerLauncherMenu implements ActionListener {
* Saves the downloadJars setting to the current profile
*/
private void downloadJars() {
String selectedProfile = serverLauncherGUI.getSelectedProfile();
String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) {
controller.setDownloadAllJars(downloadJarsCheckBoxMenuItem.isSelected());
} else {

View File

@ -1,7 +1,7 @@
package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
@ -23,11 +23,9 @@ public class ServerTab implements ActionListener {
private final JComboBox<String> serverVersions;
private final JComboBox<String> maxRam;
private final JCheckBox enabledCheckbox;
private final JButton removeServerButton;
private final JButton browseButton;
private final JTextField directory;
private final JPanel panel;
private final String name;
/**
* Initializes a new server tab with the given name
@ -36,7 +34,6 @@ public class ServerTab implements ActionListener {
* @throws ConfigurationException <p>If unable to create the new tab</p>
*/
public ServerTab(String name) throws ConfigurationException {
this.name = name;
panel = new JPanel();
Main.getController().getGUI().getPane().addTab(name, null, panel, null);
SpringLayout sl_panel_3 = new SpringLayout();
@ -76,7 +73,7 @@ public class ServerTab implements ActionListener {
sl_panel_3.putConstraint(SpringLayout.WEST, lblMaxRam, 6, SpringLayout.EAST, serverTypes);
panel.add(lblMaxRam);
maxRam = new JComboBox<>(Server.getRamList());
maxRam = new JComboBox<>(ServerHandler.getRamList());
sl_panel_3.putConstraint(SpringLayout.NORTH, maxRam, 0, SpringLayout.NORTH, serverTypes);
sl_panel_3.putConstraint(SpringLayout.WEST, maxRam, 6, SpringLayout.EAST, lblMaxRam);
sl_panel_3.putConstraint(SpringLayout.EAST, maxRam, -10, SpringLayout.EAST, panel);
@ -88,14 +85,6 @@ public class ServerTab implements ActionListener {
panel.add(enabledCheckbox);
enabledCheckbox.addActionListener(this);
removeServerButton = new JButton("Remove server");
sl_panel_3.putConstraint(SpringLayout.NORTH, removeServerButton, 0, SpringLayout.NORTH, serverVersions);
sl_panel_3.putConstraint(SpringLayout.SOUTH, removeServerButton, 0, SpringLayout.SOUTH, serverVersions);
sl_panel_3.putConstraint(SpringLayout.WEST, removeServerButton, 6, SpringLayout.EAST, serverVersions);
sl_panel_3.putConstraint(SpringLayout.EAST, removeServerButton, -10, SpringLayout.EAST, panel);
panel.add(removeServerButton);
removeServerButton.addActionListener(this);
JLabel lblDirectory = new JLabel("Directory");
sl_panel_3.putConstraint(SpringLayout.WEST, lblDirectory, 6, SpringLayout.EAST, enabledCheckbox);
panel.add(lblDirectory);
@ -112,7 +101,7 @@ public class ServerTab implements ActionListener {
browseButton = new JButton("Browse");
sl_panel_3.putConstraint(SpringLayout.EAST, directory, -6, SpringLayout.WEST, browseButton);
sl_panel_3.putConstraint(SpringLayout.NORTH, browseButton, 3, SpringLayout.SOUTH, removeServerButton);
sl_panel_3.putConstraint(SpringLayout.NORTH, browseButton, 3, SpringLayout.SOUTH, serverVersions);
sl_panel_3.putConstraint(SpringLayout.EAST, browseButton, -10, SpringLayout.EAST, panel);
sl_panel_3.putConstraint(SpringLayout.SOUTH, directory, 0, SpringLayout.SOUTH, browseButton);
sl_panel_3.putConstraint(SpringLayout.NORTH, directory, 0, SpringLayout.NORTH, browseButton);
@ -209,9 +198,7 @@ public class ServerTab implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == removeServerButton) {
remove();
} else if (e.getSource() == browseButton) {
if (e.getSource() == browseButton) {
browse();
} else if (e.getSource() == serverTypes) {
try {
@ -222,15 +209,6 @@ public class ServerTab implements ActionListener {
}
}
/**
* Removes the collection containing this ServerTab, and updates everything necessary
*/
private void remove() {
Main.getController().getCurrentProfile().removeCollection(this.name);
Main.getController().getGUI().updateWithSavedProfileData();
Main.getController().getCurrentProfile().updateConsoles();
}
/**
* Asks the user for server location and updates the GUI if given a valid value
*/

View File

@ -59,7 +59,7 @@ public class Tray {
trayIcon = new TrayIcon(trayImage, "Minecraft Server Launcher", popup);
trayIcon.setImageAutoSize(true);
ActionListener exitListener = e -> {
serverLauncherGUI.stopServers();
serverLauncherGUI.getControlPanelTab().stopServers();
controller.saveState();
System.exit(0);
};
@ -92,7 +92,7 @@ public class Tray {
e1.printStackTrace();
}
} else {
serverLauncherGUI.stopServers();
serverLauncherGUI.getControlPanelTab().stopServers();
controller.saveState();
System.exit(0);
}
@ -114,7 +114,7 @@ public class Tray {
@Override
public void windowClosing(WindowEvent e) {
controller.saveState();
serverLauncherGUI.stopServers();
serverLauncherGUI.getControlPanelTab().stopServers();
System.exit(0);
}
});

View File

@ -0,0 +1,269 @@
package net.knarcraft.minecraftserverlauncher.utility;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.userinterface.BackupGUI;
import net.knarcraft.minecraftserverlauncher.userinterface.GUI;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* A helper class for performing server backup
*/
public final class BackupUtil {
private static boolean backupAborted;
private static boolean backupRunning = false;
private BackupUtil() {
}
/**
* Aborts the currently running backup
*/
public static void abortBackup() {
backupAborted = true;
}
/**
* Recursively copies a folder to another location
*
* @param source <p>The folder to copy</p>
* @param destination <p>Target destination</p>
* @param backupFileSize <p>The total file size of the backup in progress</p>
* @param alreadyCopied <p>The amount of bytes already copied</p>
* @throws IOException <p>If we can't start a file stream</p>
*/
private static long backupFolder(File source, File destination, long backupFileSize,
long alreadyCopied) throws IOException {
if (backupAborted) {
return 0L;
}
if (!source.isDirectory()) {
long copiedFileSize = copyFile(source, destination);
BackupGUI.updateProgress("Copying " + source + "\n to " + destination,
(int) ((alreadyCopied + copiedFileSize) * 100 / backupFileSize));
return copiedFileSize;
} else {
if (!destination.exists() && !destination.mkdir()) {
return 0L;
}
String[] files = source.list();
long copiedFilesSize = 0;
if (files != null) {
for (String file : files) {
File srcFile = new File(source, file);
File destinationFile = new File(destination, file);
copiedFilesSize += backupFolder(srcFile, destinationFile, backupFileSize,
alreadyCopied + copiedFilesSize);
BackupGUI.updateProgress("Copying " + source + "\n to " + destination,
(int) ((alreadyCopied + copiedFilesSize) * 100 / backupFileSize));
}
}
return copiedFilesSize;
}
}
/**
* Copies a file from one location to another
*
* @param source <p>The file to copy</p>
* @param destination <p>The location of the copied file</p>
* @throws IOException <p>If reading or writing fails</p>
*/
private static long copyFile(File source, File destination) throws IOException {
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(destination);
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
return Files.size(source.toPath());
}
/**
* Copies all server directories to a folder specified by the user
*
* @param gui <p>The GUI to use for informing the user</p>
*/
public static void backup(GUI gui) {
backupAborted = false;
if (backupRunning) {
gui.setStatus("A backup is already running");
return;
} else {
backupRunning = true;
}
//Get the folder to save the backed up files in
File path = gui.askForDirectory("Backup folder");
if (path == null || !path.isDirectory()) {
backupRunning = false;
return;
}
gui.setStatus("Backup running...");
List<List<File>> serverFolders = getFoldersOfEnabledServers(path);
gui.setStatus("Calculating backup size...");
long backupFileSize = getFolderSize(gui, serverFolders);
long locationFreeSpace = path.getFreeSpace();
if (locationFreeSpace < (backupFileSize + 2048000000)) {
gui.setStatus("Not enough available space. " + (backupFileSize / 1000000) + "MB necessary, but only " +
(locationFreeSpace / 1000000) + "MB available");
backupRunning = false;
backupAborted = true;
return;
}
gui.setStatus("Backing up " + (backupFileSize / 1000000) + "MB");
performBackup(gui, serverFolders, backupFileSize);
}
/**
* Performs the actual backup after checks have passed and necessary info is available
*
* @param gui <p>The GUI to use for informing the user</p>
* @param serverFolders <p>The folders of the servers to backup</p>
* @param backupFileSize <p>The total size of the folders to backup</p>
*/
private static void performBackup(GUI gui, List<List<File>> serverFolders, long backupFileSize) {
new BackupGUI();
BackupGUI.updateProgress("Backup starting...", 0);
long alreadyCopied = 0;
for (List<File> serverFolder : serverFolders) {
if (backupAborted || !backupRunning) {
gui.setStatus("Backup aborted");
backupRunning = false;
return;
}
alreadyCopied = backupServerFiles(gui, serverFolder, backupFileSize, alreadyCopied);
}
backupRunning = false;
if (backupAborted) {
gui.setStatus("Backup aborted");
} else {
BackupGUI.destroy();
gui.setStatus("Backup finished");
}
}
/**
* Backs up the files for a single server
*
* @param gui <p>The GUI to use for informing the user</p>
* @param serverFolder <p>The server's input and output folders</p>
* @param backupFileSize <p>The total size of the files to backup</p>
* @param alreadyCopied <p>The amount of bytes already copied</p>
* @return <p>The new amount of bytes copied</p>
*/
private static long backupServerFiles(GUI gui, List<File> serverFolder, long backupFileSize, long alreadyCopied) {
File srcFolder = serverFolder.get(0);
File destinationFolder = serverFolder.get(1);
//Create child folders
if (!destinationFolder.exists() && !destinationFolder.mkdirs()) {
backupRunning = false;
gui.logError("Unable to create necessary sub-folder in the backup folder");
throw new IllegalArgumentException("Unable to create necessary sub-folder in the backup folder");
}
//Backup
try {
alreadyCopied += backupFolder(srcFolder, destinationFolder, backupFileSize, alreadyCopied);
} catch (IOException e) {
gui.showError("Backup caused an error: " + e.getMessage());
gui.logError(Arrays.toString(e.getStackTrace()));
BackupGUI.destroy();
backupRunning = false;
}
return alreadyCopied;
}
/**
* Gets the size of a list of folders
*
* @param gui <p>The GUI to write any errors to</p>
* @param serverFolders <p>The folder to find the size of</p>
* @return <p>The size of the given folders</p>
*/
private static long getFolderSize(GUI gui, List<List<File>> serverFolders) {
long folderSize = 0;
for (List<File> serverFolder : serverFolders) {
File srcFolder = serverFolder.get(0);
try (Stream<Path> walk = Files.walk(srcFolder.toPath())) {
folderSize += walk.filter(Files::isRegularFile).mapToLong(BackupUtil::getFileSize).sum();
} catch (IOException e) {
gui.setStatus(String.format("IO errors %s", e));
}
}
return folderSize;
}
/**
* Gets the input and output folders for enabled servers
*
* <p>The input folders are the folders to copy, while the output folders are the folders to write the backup to.
* Each list element contains a list of exactly two File items. The first one is the input folder, and the second
* one is the output folder</p>
*
* @param path <p>The path of the backup folder, as given by the user</p>
* @return <p>The folders to copy from/to</p>
*/
private static List<List<File>> getFoldersOfEnabledServers(File path) {
List<List<File>> serverFolders = new ArrayList<>();
//Get folders of servers to back up
List<Collection> collections = Main.getController().getCurrentProfile().getCollections();
for (Collection collection : collections) {
//Ignore disabled and invalid servers
if (collection.getServer().getPath().equals("") || !collection.getServer().isEnabled()) {
continue;
}
//Decide sub-folders
Server targetServer = collection.getServer();
String name = targetServer.getName();
File srcFolder = new File(targetServer.getPath());
File destinationFolder = new File(path, name);
List<File> serverFolder = new ArrayList<>();
serverFolder.add(srcFolder);
serverFolder.add(destinationFolder);
serverFolders.add(serverFolder);
}
return serverFolders;
}
/**
* Gets the size of a file given its path
*
* @param path <p>The path to a file</p>
* @return <p>The size of the file in bytes, or 0 if an exception is thrown</p>
*/
private static long getFileSize(Path path) {
try {
return Files.size(path);
} catch (IOException exception) {
System.out.printf("Failed to get size of %s%n%s", path, exception);
return 0L;
}
}
}

View File

@ -1,9 +1,6 @@
package net.knarcraft.minecraftserverlauncher.utility;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.userinterface.GUI;
import net.knarcraft.minecraftserverlauncher.userinterface.WebBrowser;
import java.io.BufferedReader;
@ -16,7 +13,6 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.net.URISyntaxException;
@ -53,7 +49,7 @@ public final class CommonFunctions {
* @param folder <p>The folder to create</p>
* @throws FileNotFoundException <p>If unable to create the folder</p>
*/
protected static void createFolder(File folder) throws FileNotFoundException {
public static void createFolder(File folder) throws FileNotFoundException {
if (!folder.exists()) {
if (!folder.mkdirs()) {
throw new FileNotFoundException("Cannot create necessary directory.");
@ -130,6 +126,7 @@ public final class CommonFunctions {
/**
* Gets a buffered writer for writing to a given file
*
* @param path <p>The path to the file to write to</p>
* @return <p>A buffered writer for writing to the file</p>
* @throws FileNotFoundException <p>If the file does not exist</p>
@ -158,7 +155,7 @@ public final class CommonFunctions {
* @throws IOException <p>If unable to write to the file</p>
*/
public static void writeFile(String path, String text) throws IOException {
writeFile(path, text, true);
writeFile(path, text, !text.equals(""));
}
/**
@ -210,55 +207,6 @@ public final class CommonFunctions {
}
}
/**
* Recursively copies a folder to another location
*
* @param source <p>The folder to copy</p>
* @param destination <p>Target destination</p>
* @throws IOException <p>If we can't start a file stream</p>
*/
private static void copyFolder(GUI serverLauncherGui, File source, File destination) throws IOException {
if (!source.isDirectory()) {
copyFile(serverLauncherGui, source, destination);
} else {
serverLauncherGui.setStatus("Copying directory " + source);
if (!destination.exists() && !destination.mkdir()) {
return;
}
String[] files = source.list();
if (files != null) {
for (String file : files) {
File srcFile = new File(source, file);
File destinationFile = new File(destination, file);
copyFolder(serverLauncherGui, srcFile, destinationFile);
}
}
serverLauncherGui.setStatus("Copied directory " + source);
}
}
/**
* Copies a file from one location to another
*
* @param serverLauncherGui <p>The serverLauncherGui to use for alerting the user</p>
* @param source <p>The file to copy</p>
* @param destination <p>The location of the copied file</p>
* @throws IOException <p>If reading or writing fails</p>
*/
private static void copyFile(GUI serverLauncherGui, File source, File destination) throws IOException {
serverLauncherGui.setStatus("Copying file " + source + "...");
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(destination);
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
serverLauncherGui.setStatus("Copied file " + source);
}
/**
* Opens an url in the user's default application.
*
@ -295,34 +243,6 @@ public final class CommonFunctions {
return text.toString().trim();
}
/**
* Copies all server directories to a folder specified by the user.
*/
public static void backup(GUI gui) {
File path = gui.askForDirectory("Backup folder");
for (Collection collection : Main.getController().getCurrentProfile().getCollections()) {
//Ignore disabled and invalid servers
if (collection.getServer().getPath().equals("") || !collection.getServer().isEnabled()) {
continue;
}
//Decide sub-folders
Server targetServer = collection.getServer();
String name = targetServer.getName();
File srcFolder = new File(targetServer.getPath());
File destinationFolder = new File(path, name);
//Create child folders
if (!destinationFolder.exists() && !destinationFolder.mkdirs()) {
throw new IllegalArgumentException("Unable to create necessary sub-folder in the backup folder");
}
//Backup
try {
CommonFunctions.copyFolder(gui, srcFolder, destinationFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
gui.setStatus("Backup finished");
}
/**
* Validates that a name is not empty and does not contain invalid characters
@ -331,7 +251,7 @@ public final class CommonFunctions {
* @return <p>True if the name is valid</p>
*/
public static boolean nameIsValid(String name) {
return !name.equals("") && name.matches("[^!?;,]+");
return name != null && !name.equals("") && name.matches("[^!?;,]+");
}
/**
@ -339,7 +259,7 @@ public final class CommonFunctions {
*
* @param target <p>The folder to delete from</p>
*/
static void removeFilesRecursively(File target) {
public static void removeFilesRecursively(File target) {
File[] oldFiles = target.listFiles();
if (oldFiles == null) {
throw new IllegalArgumentException("Unable to list files in directory");

View File

@ -29,13 +29,12 @@ public final class Updater {
private static final String targetFile = "minecraft-server-launcher.jar";
/**
* Checks if a newer version is available
* Gets the version channel and version of this software instance
*
* @param updateURL <p>The URL used for checking for updates</p>
* @param updateChannel <p>The release channel to use</p>
* @throws IOException <p>If the update data cannot be read</p>
* @return <p>An array of Channel, Version</p>
* @throws FileNotFoundException <p>If unable to find or read currentversion.csv</p>
*/
public static void checkForUpdate(String updateURL, String updateChannel) throws IOException {
public static String[] getCurrentVersion() throws FileNotFoundException {
Scanner file = getResourceAsScanner("currentversion.csv");
if (!file.hasNextLine()) {
throw new FileNotFoundException("File currentversion.csv is invalid");
@ -46,6 +45,20 @@ public final class Updater {
}
String oldVer = file.nextLine();
file.close();
return new String[]{oldType, oldVer};
}
/**
* Checks if a newer version is available
*
* @param updateURL <p>The URL used for checking for updates</p>
* @param updateChannel <p>The release channel to use</p>
* @throws IOException <p>If the update data cannot be read</p>
*/
public static void checkForUpdate(String updateURL, String updateChannel) throws IOException {
String[] oldData = getCurrentVersion();
String oldType = oldData[0];
String oldVer = oldData[1];
String data = readRemoteFile(updateURL);
JsonObject jsonObject = new JsonParser().parse(data).getAsJsonObject();

View File

@ -1,2 +1,2 @@
beta
1.3.2
1.4.2
1 beta
2 1.3.2 1.4.2

View File

@ -1,11 +1,11 @@
Vanilla;Latest,Snapshot,1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.8.9,1.7.10,1.6.4,1.5.2,1.4.7,1.3.2,1.2.5;https://launchermeta.mojang.com/mc/game/version_manifest.json;"release":";";https://s3.amazonaws.com/Minecraft.Download/versions/
Vanilla;Latest,Snapshot,1.18,1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.8.9,1.7.10,1.6.4,1.5.2,1.4.7,1.3.2,1.2.5;https://launchermeta.mojang.com/mc/game/version_manifest.json;"release":";";https://s3.amazonaws.com/Minecraft.Download/versions/
Spigot;Latest,1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.9.4,1.8.8,1.7.10,1.6.4-R2.1,1.5.2-R1.1,1.4.7-R1.1;https://static.knarcraft.net/archive/downloads/minecraftserverjars/Spigot/;spigot-
Paper;1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.8.8;https://static.knarcraft.net/archive/downloads/minecraftserverjars/Paper/;Paper-
Paper;1.18,1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.8.8;https://papermc.io/api/v2/projects/paper/versions/;"builds":[;];https://papermc.io/api/v2/projects/paper/versions/
#SpongeVanilla;1.12.2,1.11.2,1.10.2,1.8.9;https://dl-api.spongepowered.org/v1/org.spongepowered/spongevanilla/downloads?type=stable&minecraft=;"version":";",;https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/;/spongevanilla-
Craftbukkit;Latest,1.17.1,1.16.5,1.15.2,1.14.4,1.13.2,1.12.2,1.11.2,1.10.2,1.9.4,1.8.8,1.7.10-R0.1,1.6.4-R2.0,1.5.2-R1.0,1.4.6-R0.3,1.3.2-R3.0,1.2.5-R2.0,1.1-R6,1.0.1-R1,b1.8.1,b1.7.3;https://static.knarcraft.net/archive/downloads/minecraftserverjars/Bukkit/;craftbukkit-
#SpongeForge;1.12.2,1.11.2,1.10.2;https://dl-api.spongepowered.org/v1/org.spongepowered/spongeforge/downloads?type=stable&minecraft=;"version":";",;https://repo.spongepowered.org/maven/org/spongepowered/spongeforge/;/spongeforge-
MCPCplus;1.6.4,1.6.2,1.5.2,1.4.7;https://static.knarcraft.net/archive/downloads/minecraftserverjars/MCPC+/;mcpcplus
Bungee;Latest,1.7.10,1.6.4,1.5.2,1.4.7;https://ci.md-5.net/job/BungeeCord/lastSuccessfulBuild/artifact/bootstrap/target/;Artifacts of BungeeCord #; ;http://ci.md-5.net/job/BungeeCord/lastSuccessfulBuild/artifact/bootstrap/target/BungeeCord.jar;https://static.knarcraft.net/archive/downloads/minecraftserverjars/BungeeCord/;BungeeCord-
Waterfall;1.17,1.16,1.15,1.14,1.13,1.12,1.11;https://papermc.io/api/v1/waterfall/;"latest":;,;https://papermc.io/api/v1/waterfall/
Travertine;1.16,1.15,1.14,1.13,1.12;https://papermc.io/api/v1/travertine/;"latest":;,;https://papermc.io/api/v1/travertine/
Waterfall;1.18,1.17,1.16,1.15,1.14,1.13,1.12,1.11;https://papermc.io/api/v2/projects/waterfall/versions/;"builds":[;];https://papermc.io/api/v2/projects/waterfall/versions/
Travertine;1.16,1.15,1.14,1.13,1.12;https://papermc.io/api/v2/projects/travertine/versions/;"builds":[;];https://papermc.io/api/v2/projects/travertine/versions/
Custom;
Can't render this file because it contains an unexpected character in line 1 and column 200.

View File

@ -1,8 +1,9 @@
package net.knarcraft.minecraftserverlauncher.utility;
package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.userinterface.FakeGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

View File

@ -11,7 +11,7 @@ public class ServerTest {
@Test
public void fromStringTest() throws ConfigurationException {
Server server = Server.fromString("asd;/home/;false;Bukkit;1.10.2;4G;");
Server server = ServerHandler.fromString("asd;/home/;false;Bukkit;1.10.2;4G;");
assertEquals("asd", server.getName());
assertEquals("/home/", server.getPath());
assertFalse(server.isEnabled());

View File

@ -38,7 +38,7 @@ public class ServerVersionContainerTest {
serverVersionContainer.reset();
System.out.println(serverVersionContainer.toString());
assertEquals("vanillaVersion;null\nsnapshotVersion;null\nbungeeVersion;null\nwaterfallVersions;\n" +
"travertineVersions;\nspongeVanillaVersions;\nspongeForgeVersions;\ndownloadedBuildToolsVersion;null",
"travertineVersions;\npaperVersions;\nspongeVanillaVersions;\nspongeForgeVersions;\ndownloadedBuildToolsVersion;null",
serverVersionContainer.toString());
}